nitrojs / nitro

Next Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.

Home Page:https://nitro.build

Repository from Github https://github.comnitrojs/nitroRepository from Github https://github.comnitrojs/nitro

Use generator with nitro openapi

maximersetec opened this issue · comments

Describe the feature

Question

Is it possible to configure Nitro OpenAPI to use a generator from @asteasolutions/zod-to-openapi to automatically add OpenAPI objects to one or more routes?

Example Code

import { extendZodWithOpenApi, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';

extendZodWithOpenApi(z);

const fieldDefinitions = {
  'title': {
    label: 'Project name',
    placeholder: 'Project name',
    validation: z
      .string()
      .min(1, "Project name is required")
      .openapi({ description: 'Project name' }),
  },
  'case-number': {
    label: 'Case-number',
    placeholder: 'Case-number',
    validation: z
      .string()
      .min(1, "Case-number is required")
      .openapi({ description: 'Case-number' }),
  },
  'case-manager': {
    label: 'Case-manager',
    placeholder: 'Case-manager',
    validation: z
      .string()
      .min(1, "Case-manager is required")
      .openapi({ description: 'Case-manager' }),
  },
  'contributor': {
    label: 'Contributor',
    placeholder: 'Contributor',
    validation: z
      .string()
      .min(1, "Contributor is required")
      .openapi({ description: 'Project contributor' }),
  },
};

export const metadataSchema = z
  .object(
    Object.fromEntries(
      Object.entries(fieldDefinitions).map(([key, { validation }]) => [
        key,
        validation,
      ])
    )
  )
  .openapi('Metadata');

export const formFields = Object.entries(fieldDefinitions).map(
  ([key, { label, placeholder }]) => ({
    id: key,
    label,
    type: 'text',
    placeholder,
  })
);

const generator = new OpenApiGeneratorV3([metadataSchema]);


### Additional information

- [ ] Would you be willing to help implement this feature?

@maximersetec Can you provide a simple repo with @asteasolutions/zod-to-openapi usage? I don't know this tool at all so would be grateful for some example.

@maximersetec Can you provide a simple repo with @asteasolutions/zod-to-openapi usage? I don't know this tool at all so would be grateful for some example.

Actually what he has used (I mean that Zod or something) is not important. The important thing is, we need dynamic variables work in DefineRouteMeta. Now in DefineRouteMeta, it only accept static string. For example:

defineRouteMeta({
  openAPI: {
    description: 'Handle AT Protocol OAuth callback'
    }
})

This will work. Because is doesn't contains any variables.

However if:

let des = 'Handle AT Protocol OAuth callback';

defineRouteMeta({
  openAPI: {
    description: des
    }
})

Or even:

let routeMeta = {
  openAPI: {
    description: 'Handle AT Protocol OAuth callback'
    }
}
defineRouteMeta(routeMeta)

In both situation, they won't work because of variables.

@maximersetec Can you provide a simple repo with @asteasolutions/zod-to-openapi usage? I don't know this tool at all so would be grateful for some example.

import { z } from 'zod'
import { createSchema, extendZodWithOpenApi } from 'zod-openapi'

extendZodWithOpenApi(z)

const TestLoginDto = z.object({
  username: z.string().openapi({ description: 'Username' }),
  password: z.string().openapi({ description: 'Password' }),
})

const { schema: TestLoginSchema } = createSchema(TestLoginDto)

defineRouteMeta({
  openAPI: {
    summary: 'Login',
    requestBody: {
      content: {
        'application/json': {
          schema: TestLoginSchema,
        },
      },
    },
  },
})

For your information, I have copied an example that I use in another issue.

#2870
#2641
#2490
#2274

Acutally, with this issue, there are 5 issues that are requiring for dynamic DefineRouteMeta function. That's why I said this function is very necessary.

@AkarinServer I will try to dive into it :)

Ok, @AkarinServer @maximersetec, I found the root cause of this issue. Unfortunately, this can't be resolved without core changes 😢 Let me explain why.

The defineRouteMeta function is transformed using AST as a Rollup plugin for Nitro. Here's the transformation:
https://github.com/nitrojs/nitro/blob/v2/src/rollup/plugins/handlers-meta.ts#L58

This code takes the defineRouteMeta expression statement and generates an object from it. The resulting code is injected into the bundled output and looks like this:

export default {"openAPI":{"summary":"Login","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}}}};

This code is then injected into the index.mjs file here:
https://github.com/nitrojs/nitro/blob/v2/src/rollup/plugins/handlers.ts#L102

The AST could export these variables as additional exports from the file, but this would require significantly more changes than I initially anticipated. I have some ideas for how to approach this, but @pi0 should take a look and decide if it's worth pursuing.