Skip to content

v5.0.0

Compare
Choose a tag to compare
@AGalabov AGalabov released this 23 May 10:16
· 123 commits to master since this release

zod-to-openapi v5.0.0 is finally here 🎉

What's Changed

  • Automatic registration of schemas through the .openapi method
  • Introduced separate generators for different OpenAPI versions

Full Changelog: v4.8.0...v5.0.0

Migrating to v5.0.0

How to use the separated generators

Lets say you had something like this:

import { OpenAPIRegistry, OpenAPIGenerator } from '@asteasolutions/zod-to-openapi';

const registry = new OpenAPIRegistry();
// Register definitions here

const generator = new OpenAPIGenerator(registry.definitions, '3.0.0');

generator.generateDocument({
    info: {},
    servers: [{ ... }],
  });

When migrating to v5.0.0 it would start to look like this:

// Change the import from OpenAPIGenerator to OpenAPIGeneratorV3
import { OpenAPIRegistry, OpenAPIGeneratorV3 } from '@asteasolutions/zod-to-openapi';

const registry = new OpenAPIRegistry();
// Register definitions here

// Do not pass the specific OpenAPI version to the constructor - just definitions
const generator = new OpenAPIGeneratorV3(registry.definitions);

generator.generateDocument({
    // Pass the specific OpenAPI version here in the config instead. This would not influence the
    // behavior of the generator - it is just the `openapi` value to be returned in tour document.
    openapi: '3.0.0',
    info: {},
    servers: [{ ... }],
  });

And similarly if you were using the generator as:

const generator = new OpenAPIGenerator(registry.definitions, '3.1.0');

the respective change would be to start using:

const generator = new OpenAPIGeneratorV31(registry.definitions);

generator.generateDocument({ openapi: '3.1.0', ... })`

Taking advantage of the automatic registration and how to use it

Registering schemas has never been easier.

Let's say you had an example registered schema like

const schema = registry.register(
  'Schema',
  z.string().openapi({ description: 'Some string' })
);

This is still valid. However an alternative approach to writing this can now be used:

const schema = z.string().openapi('Schema', { description: 'Some string' });

Notice how there is no registry involved. And that is the idea. Thanks to this approach we can do the following:

const entrySchema = z
  .object({
    key: z.string().openapi('Key', { description: 'Some string' }),
    person: z.object({ name: z.string(), age: z.number() }).openapi('User'),
  })
  .openapi('UserEntry');

// and you can directly pass the schema to the generator if you want:
const generator = new OpenApiGeneratorV3([entrySchema]);
const generateDocument = generator.generateComponents();

This would produce the following result:

{
  "components": {
    "schemas": {
      "Key": { "type": "string", "description": "Some string" },
      "User": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "age": { "type": "number" }
        },
        "required": ["name", "age"]
      },
      "UserEntry": {
        "type": "object",
        "properties": {
          "key": { "$ref": "#/components/schemas/Key" },
          "person": { "$ref": "#/components/schemas/User" }
        },
        "required": ["key", "person"]
      }
    }
  }
}

The key part here is that in the example above we've only passed the entrySchema. However @asteasolutions/zod-to-openapi would generate and reference any schema that has an identifier (the first parameter of .openapi) - in the example above - Key and User.

Going one step further this would allow any user to not pass in zod schemas to the generator at all. You can only pass route definitions and @asteasolutions/zod-to-openapi would take care of all the referenced schemas on it's own:

For example, using the example above but changing the generator constructor we can have:

registry.registerPath({
  method: 'get',
  path: '/users',
  responses: {
    200: {
      description: 'User results',
      content: {
        'application/json': {
          schema: z.array(entrySchema),
        },
      },
    },
  },
});

// Notice how entrySchema is not passed at all
const generator = new OpenApiGeneratorV3(registry.definitions);
const generateDocument = generator.generateDocument({
  /* your document config here*/
});

This would produce the exact same result for component/schemas and the path would just look like this

"paths": {
    "/users": {
        "get": {
            "responses": {
                "200": {
                    "description": "Success",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "array",
                                "items": {
                                    "$ref": "#/components/schemas/UserEntry"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}