asteasolutions / zod-to-openapi

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

Required field not filled in for z.custom() #209

Closed tionkje closed 4 months ago

tionkje commented 4 months ago

Hello,

The required field of the openApi spec does not seem to be filled in when i use z.custom().

Where does this required array get filled in? If somebody can point me in the right direction i could take a look.

A minimal example:

const registry = new OpenAPIRegistry();

const testchema = z.object({
  custom: z
    .custom<number>(val => typeof val == 'number')
    .openapi({ type: 'string' }),
  number: z.number(),
});

registry.register('testschema', testchema);
const generator = new OpenApiGeneratorV3(registry.definitions);
console.log(generator.generateComponents().components?.schemas?.['testschema']);
{
  type: 'object',
  properties: { custom: { type: 'string' }, number: { type: 'number' } },
  required: [ 'number' ]
} 

Seems like zod/typescript the default is to be required and in openapi the default is not to be required. Also the required field is on the parent object. Any tips would be apprecieated.

AGalabov commented 4 months ago

Hey @tionkje,

z.custom() is essentially a pure JS/TS code ran through the zod framework. Our library is looking up the zod internals (the underlying classes) and using those as information for what the schema is. As such the underlying structure of a zod.custom is a ZodAny that is optional and nullable and also has no type - hence why you've also had to pass a type: 'string'

To be honest the same happens when you use just a schema like:

const testchema = z.object({
  custom: z.any(),
});

so the problem is generally with z.any(). I am not sure how we could handle that in a meaningful way.

So my only suggestion here would be to define the required properties manually - i.e pass the required: ['number', 'custom'] to the object and hopefully this schema is not reusable in too many places.