asteasolutions / zod-to-openapi

A library that generates OpenAPI (Swagger) docs from Zod schemas
MIT License
788 stars 52 forks source link

custom path parameters generate incorrect "required" value #244

Closed veganbeef closed 3 days ago

veganbeef commented 1 week ago

Thanks for all your work on this super handy library!

I'm encountering the following issue with the generated OpenAPI JSON file when using zod-to-openapi version 6.4.0:

When generating an openapi.json file for a schema with path parameters that use a zod schema defined with z.custom(), the JSON output lists the parameter as "required": "false".

However, this must be set to true according to the OpenAPI Specification.

Example:

import { Router } from 'express';

const router = Router();

const ethAddress = z
  .custom<`0x${string}`>((val: any) => /^0x[a-fA-F0-9]{40}$/.test(val))
  .openapi({ type: 'string', pattern: '^0x[a-fA-F0-9]{40}$' });

router.get('/tokens/:chainId/:contractAddress', validate(
  {
    tag: 'metrics',
    summary: 'token details',
    description: 'Get the details for a given token',
    params: z.object({ chainId: z.string(), contractAddress: ethAddress }),
    response: z.boolean(), // simplified for this example
  },
  async (req, res) => {
    return res.json(true); // simplified for this example
  }
));

The above code results in the following generated openapi.json segment with the incorrect false value:

"/tokens/{chainId}/{contractAddress}": {
  "get": {
    "tags": [
      "metrics"
    ],
    "summary": "token details",
    "description": "Get the details for a given token",
    "parameters": [
      {
        "schema": {
          "type": "string"
        },
        "required": true,
        "name": "chainId",
        "in": "path"
      },
      {
        "schema": {
          "type": "string",
          "pattern": "^0x[a-fA-F0-9]{40}$"
        },
        "required": false,
        "name": "contractAddress",
        "in": "path"
      }
    ],
    "responses": {...}
  }
}

And that in turn results in the following schema validation error:

Swagger schema validation failed.
  #/paths/~1metrics~1tokens~1{chainId}~1{contractAddress}/get/parameters/1/required must be equal to one of the allowed values
AGalabov commented 3 days ago

@veganbeef thank you for the kind word. Your issue is that z.custom basically means anything (including undefined) as far as zod is concerned => the required field is false by default and so is the type. You've correctly addded the type manually. And now all you need to do is to add the required manually as well through the params prop that we've exposed. So you schema should look like this:

const ethAddress = z
  .custom<`0x${string}`>((val: any) => /^0x[a-fA-F0-9]{40}$/.test(val))
  .openapi({
    type: 'string',
    pattern: '^0x[a-fA-F0-9]{40}$',
    param: { required: true },
  });

I am closing the issue since I've tested that this would produce the following result:

"parameters": [
                    {
                        "schema": {
                            "type": "string"
                        },
                        "required": true,
                        "name": "chainId",
                        "in": "path"
                    },
                    {
                        "schema": {
                            "type": "string",
                            "pattern": "^0x[a-fA-F0-9]{40}$"
                        },
                        "required": true,
                        "name": "contractAddress",
                        "in": "path"
                    }
                ],

If there are still problems - let us know and feel free to reopen the issue

veganbeef commented 2 days ago

@AGalabov this works, thank you for the assistance!