zod-openapi was created while trying to add a feature to support auto registering schemas to zod-to-openapi. This proved to be extra challenging given the overall structure of the library so I decided re-write the whole thing. I was a big contributor to this library and love everything it's done, however I could not go past a few issues.
-
**Inaccurate** schema generation. This is because the library is written without considering that Zod Types can produce different schemas depending on if they are an
input
oroutput
type. This means that when you use aZodTransform
,ZodPipeline
orZodDefault
it may generate incorrect documentation depending on if you are creating schema for a request ot a response. -
No input/output validation on components. Registered schema for inputs and outputs should NOT be used if they contain a ZodEffect such as
ZodTransform
,ZodPipeline
orZodDefault
in both a request and response schema. This is because they will be inaccurate for reasons stated above. -
No transform support or safety. You can use a
type
to override the transform type but what happens when that transform logic changes? We solve this by introducingeffectType
. -
No lazy/recursive schema support.
-
Wider and richer generation of OpenAPI types for Zod Types.
-
The underlying structure of the library consists of tightly coupled classes which require you to create an awkward Registry class to create references. This would mean you would need to ship a registry class instance along with your types which makes sharing types difficult.
-
Previosuly, zod-to-openapi did not support auto-registering schema, however, more recently they added a solution which is less clear as they are using named parameters.
z.string().openapi('foo');
z.string().openapi('foo', { description: 'foo' });
// vs
z.string().openapi({ ref: 'foo' });
z.string().openapi({ description: 'foo', ref: 'foo' });
- None of the large number of issues, known issues, or discussion threads apply to this library.
Did I really rewrite an entire library just for this? Absolutely. I believe that creating documentation and types should be as simple and as frictionless as possible.
- Delete the OpenAPIRegistry and OpenAPIGenerator classes
- Replace any
.register()
call made and replace them withref
in.openapi()
or alternatively, add them directly to the components section of the schema.
const registry = new OpenAPIRegistry();
const foo = registry.register(
'foo',
z.string().openapi({ description: 'foo' }),
);
const bar = z.object({ foo });
// Replace with:
const foo = z.string().openapi({ ref: 'foo', description: 'foo' });
const bar = z.object({ foo });
// or
const foo = z.string().openapi({ description: 'foo' });
const bar = z.object({ foo });
const document = createDocument({
components: {
schemas: {
foo,
},
},
});
- Replace
registry.registerComponent()
with a regular OpenAPI component in the document.
const registry = new OpenAPIRegistry();
registry.registerComponent('securitySchemes', 'auth', {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'An auth token issued by oauth',
});
// Replace with regular component declaration
const document = createDocument({
components: {
// declare directly in components
securitySchemes: {
auth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'An auth token issued by oauth',
},
},
},
});
- Replace
registry.registerPath()
with a regular OpenAPI paths in the document.
const registry = new OpenAPIRegistry();
registry.registerPath({
method: 'get',
path: '/foo',
request: {
query: z.object({ a: z.string() }),
params: z.object({ b: z.string() }),
body: z.object({ c: z.string() }),
headers: z.object({ d: z.string() })
},
responses: {},
});
// Replace with regular path declaration
const getFoo: ZodOpenApiPathItemObject = {
get: {
requestParams: {
query: z.object({ a: z.string() }),
path: z.object({ b: z.string() }), // params -> path
header: z.object({ c: z.string() }) // headers -> header
}, // renamed from request -> requestParams
requestBody: z.object({c: z.string() }) // request.body -> requestBody
responses: {},
},
};
const document = createDocument({
paths: {
'/foo': getFoo,
},
});