zod-openapi 4.2.4 → 5.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <h1 align="center">zod-openapi</h1>
4
4
  </p>
5
5
  <p align="center">
6
- A TypeScript library for using <a href="https://github.com/colinhacks/zod">Zod</a> schemas to generate OpenAPI v3.x documentation.
6
+ A TypeScript library which uses <a href="https://github.com/colinhacks/zod">Zod</a> schemas to generate OpenAPI v3.x documentation.
7
7
  </p>
8
8
  <div align="center">
9
9
  <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/v/zod-openapi"/></a>
@@ -29,46 +29,22 @@ pnpm install zod zod-openapi
29
29
 
30
30
  ## Usage
31
31
 
32
- ### Extending Zod
32
+ ### `.meta()`
33
33
 
34
- This package extends Zod by adding an `.openapi()` method. Call this at the top of your entry point(s). You can apply this extension in two ways:
34
+ Use the `.meta()` method to add metadata to a Zod schema. It accepts an object with the following options:
35
35
 
36
- #### Subpath Import
36
+ | Option | Description |
37
+ | ---------- | ---------------------------------------------------------------------------------------------------------------- |
38
+ | `id` | Registers a schema as a reusable OpenAPI component. |
39
+ | `header` | Adds metadata for [response headers](#response-headers). |
40
+ | `param` | Adds metadata for [request parameters](#parameters). |
41
+ | `override` | Allows you to override the rendered OpenAPI schema. This takes either an object or a function |
42
+ | `outputId` | Allows you to set a different ID for the output schema. This is useful when the input and output schemas differ. |
43
+ | `unusedIO` | Allows you to set the `io` for an unused schema added to the components section. Defaults to `output` |
37
44
 
38
- ```ts
39
- import 'zod-openapi/extend';
40
- import { z } from 'zod';
41
-
42
- z.string().openapi({ description: 'Hello world!', example: 'Hello world' });
43
- ```
45
+ `````typescript
44
46
 
45
- #### Manual Extension
46
-
47
- This method is useful if you have a specific instance of Zod or a Zod instance from another library that you want to extend.
48
-
49
- ```typescript
50
- import { z } from 'zod';
51
- import { extendZodWithOpenApi } from 'zod-openapi';
52
-
53
- extendZodWithOpenApi(z);
54
-
55
- z.string().openapi({ description: 'Hello world!', example: 'hello world' });
56
- ```
57
-
58
- ### `.openapi()`
59
-
60
- Use the `.openapi()` method to add metadata to a Zod schema. It accepts an object with the following options:
61
-
62
- | Option | Description |
63
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
64
- | `OpenAPI Options` | Accepts any option available for a [SchemaObject](https://swagger.io/docs/specification/data-models/data-types/). |
65
- | `effectType` | Overrides the creation type for a [Zod Effect](#zod-effects). |
66
- | `header` | Adds metadata for [response headers](#response-headers). |
67
- | `param` | Adds metadata for [request parameters](#parameters). |
68
- | `ref` | Registers a schema as a reusable OpenAPI component. |
69
- | `refType` | Defines the creation type for a component not explicitly referenced in the document. |
70
- | `type` | Overrides the generated type. If provided, no metadata will be generated. |
71
- | `unionOneOf` | Forces a `oneOf` instead of `anyOf` for unions. See [CreateDocumentOptions](#CreateDocumentOptions) for a global setting. |
47
+ ````typescript
72
48
 
73
49
  ### `createDocument`
74
50
 
@@ -82,7 +58,7 @@ import { createDocument } from 'zod-openapi';
82
58
  const jobId = z.string().openapi({
83
59
  description: 'A unique identifier for a job',
84
60
  example: '12345',
85
- ref: 'jobId',
61
+ id: 'jobId',
86
62
  });
87
63
 
88
64
  const title = z.string().openapi({
@@ -117,7 +93,7 @@ const document = createDocument({
117
93
  },
118
94
  },
119
95
  });
120
- ```
96
+ `````
121
97
 
122
98
  <details>
123
99
  <summary>Creates the following object:</summary>
@@ -204,9 +180,12 @@ const document = createDocument({
204
180
 
205
181
  ```typescript
206
182
  const document = createDocument(details, {
207
- defaultDateSchema: { type: 'string', format: 'date-time' }, // defaults to { type: 'string' }
208
- unionOneOf: true, // defaults to false. Forces all ZodUnions to output oneOf instead of anyOf. An `.openapi()` `unionOneOf` value takes precedence over this one.
209
- enforceDiscriminatedUnionComponents: true, // defaults to false. Throws an error if a Discriminated Union member is not registered as a component.
183
+ override: ({ jsonSchema, zodSchema }) => {
184
+ if (jsonSchema.anyOf) {
185
+ ctx.jsonSchema.oneOf = ctx.jsonSchema.anyOf;
186
+ delete ctx.jsonSchema.anyOf;
187
+ }
188
+ },
210
189
  });
211
190
  ```
212
191
 
@@ -222,7 +201,7 @@ import { createSchema } from 'zod-openapi';
222
201
  const jobId = z.string().openapi({
223
202
  description: 'A unique identifier for a job',
224
203
  example: '12345',
225
- ref: 'jobId',
204
+ id: 'jobId',
226
205
  });
227
206
 
228
207
  const title = z.string().openapi({
@@ -424,7 +403,7 @@ If we take the example in `createDocument` and instead create `title` as follows
424
403
  const title = z.string().openapi({
425
404
  description: 'Job title',
426
405
  example: 'My job',
427
- ref: 'jobTitle', // <- new field
406
+ id: 'jobTitle', // <- new field
428
407
  });
429
408
  ```
430
409
 
@@ -472,30 +451,6 @@ createDocument({
472
451
  });
473
452
  ```
474
453
 
475
- Unfortunately, as a limitation of this library, you should attach an `.openapi()` field or `.describe()` to the schema that you are passing into the components or you will not reap the full benefits of component generation.
476
-
477
- ```ts
478
- // ❌ Avoid this
479
- const title = z.string();
480
-
481
- // ✅ Recommended ways
482
- const title = z.string().describe('Job title');
483
- const title = z.string().openapi({
484
- description: 'Job title',
485
- example: 'My job',
486
- });
487
-
488
- createDocument({
489
- components: {
490
- schemas: {
491
- jobTitle: title,
492
- },
493
- },
494
- });
495
- ```
496
-
497
- Overall, I recommend utilising the auto registering components over manual registration.
498
-
499
454
  #### Parameters
500
455
 
501
456
  Query, Path, Header & Cookie parameters can be similarly registered:
@@ -505,7 +460,7 @@ Query, Path, Header & Cookie parameters can be similarly registered:
505
460
  const jobId = z.string().openapi({
506
461
  description: 'Job ID',
507
462
  example: '1234',
508
- param: { ref: 'jobRef' },
463
+ param: { id: 'jobRef' },
509
464
  });
510
465
 
511
466
  createDocument({
@@ -526,7 +481,7 @@ createDocument({
526
481
  const jobId = z.string().openapi({
527
482
  description: 'Job ID',
528
483
  example: '1234',
529
- param: { in: 'header', name: 'jobId', ref: 'jobRef' },
484
+ param: { in: 'header', name: 'jobId', id: 'jobRef' },
530
485
  });
531
486
 
532
487
  createDocument({
@@ -563,7 +518,7 @@ Response headers can be similarly registered:
563
518
  const header = z.string().openapi({
564
519
  description: 'Job ID',
565
520
  example: '1234',
566
- header: { ref: 'some-header' },
521
+ header: { id: 'some-header' },
567
522
  });
568
523
 
569
524
  // or
@@ -594,7 +549,7 @@ const response: ZodOpenApiResponseObject = {
594
549
  schema: z.object({ a: z.string() }),
595
550
  },
596
551
  },
597
- ref: 'some-response',
552
+ id: 'some-response',
598
553
  };
599
554
 
600
555
  //or
@@ -623,7 +578,7 @@ Callbacks can also be registered
623
578
 
624
579
  ```typescript
625
580
  const callback: ZodOpenApiCallbackObject = {
626
- ref: 'some-callback'
581
+ id: 'some-callback'
627
582
  post: {
628
583
  responses: {
629
584
  200: {
@@ -664,99 +619,77 @@ createDocument({
664
619
  });
665
620
  ```
666
621
 
667
- ### Zod Effects
622
+ ### Zod Types
623
+
624
+ Zod types are composed of two different parts: the input and the output. This library decides which type to create based on if it is used in a request or response context.
668
625
 
669
- `.transform()`, `.catch()`, `.default()` and `.pipe()` are complicated because they all comprise of two different types that we could generate (input & output).
626
+ Input:
670
627
 
671
- We attempt to determine what type of schema to create based on the following contexts:
628
+ - Request Parameters (query, path, header, cookie)
629
+ - Request Body
672
630
 
673
- _Input_: Request Bodies, Request Parameters, Headers
631
+ Output:
674
632
 
675
- _Output_: Responses, Response Headers
633
+ - Response Body
634
+ - Response Headers
676
635
 
677
- As an example:
636
+ In general, you want to avoid using a registered input schema in an output context and vice versa. This is because the rendered input and output schemas of a simple Zod schema will differ, even with a simple Zod schema like `z.object()`.
678
637
 
679
638
  ```ts
680
- z.object({
681
- a: z.string().default('a'),
639
+ const schema = z.object({
640
+ name: z.string(),
682
641
  });
683
642
  ```
684
643
 
685
- In a request context, this would render the following OpenAPI schema:
686
-
687
- ```yaml
688
- type: 'object'
689
- properties:
690
- - a:
691
- type: 'string'
692
- default: 'a'
693
- ```
694
-
695
- or the following for a response:
696
-
697
- ```yaml
698
- type: 'object'
699
- properties:
700
- - a:
701
- type: 'string'
702
- default: 'a'
703
- required:
704
- - a
705
- ```
706
-
707
- Note how the response schema created an extra `required` field. This means, if you were to register a Zod schema with `.default()` as a component and use it in both a request or response, your schema would be invalid. Zod OpenAPI keeps track of this usage and will throw an error if this occurs.
708
-
709
- #### EffectType
644
+ Input:
710
645
 
711
- ```ts
712
- z.string().transform((str) => str.trim());
646
+ ```json
647
+ {
648
+ "type": "object",
649
+ "properties": {
650
+ "name": {
651
+ "type": "string"
652
+ }
653
+ },
654
+ "required": ["name"]
655
+ }
713
656
  ```
714
657
 
715
- Whilst the TypeScript compiler can understand that the result is still a `string`, unfortunately we cannot introspect this as your transform function may be far more complicated than this example. To address this, you can set the `effectType` on the schema to `same`, `input` or `output`.
658
+ Output:
716
659
 
717
- `same` - This informs Zod OpenAPI to pick either the input schema or output schema to generate with because they should be the same.
718
-
719
- ```ts
720
- z.string()
721
- .transform((str) => str.trim())
722
- .openapi({ effectType: 'same' });
660
+ ```json
661
+ {
662
+ "type": "object",
663
+ "properties": {
664
+ "name": {
665
+ "type": "string"
666
+ }
667
+ },
668
+ "required": ["name"],
669
+ "additionalProperties": false
670
+ }
723
671
  ```
724
672
 
725
- If the transform were to drift from this, you will receive a TypeScript error:
673
+ Unless you are strictly using `z.looseObject()`s or `z.strictObject()`s throughout your codebase you will likely run into issues where the input and output schemas differ. This library will do a best effort to check equality using a simple JSON.stringify() === JSON.stringify() check. If your registered schema contains dynamically created components, this will always fail.
726
674
 
727
- ```ts
728
- z.string()
729
- .transform((str) => str.length)
730
- .openapi({ effectType: 'same' });
731
- // ~~~~~~~~~~
732
- // Type 'same' is not assignable to type 'CreationType | undefined'.ts(2322)
733
- ```
675
+ If the schemas are not equal, it will automatically handle this by outputting the `output` schema with an `Output` suffix.
734
676
 
735
- `input` or `output` - This tells Zod OpenAPI to pick a specific schema to create whenever we run into this schema, regardless of it is a request or response schema.
677
+ You can override this by setting the `outputId` field with the `.meta()` method.
736
678
 
737
679
  ```ts
738
- z.string()
739
- .transform((str) => str.length)
740
- .openapi({ effectType: 'input' });
680
+ const schema = z
681
+ .object({
682
+ name: z.string(),
683
+ })
684
+ .meta({ id: 'MyObject', outputId: 'MyObjectResponse' });
741
685
  ```
742
686
 
743
- #### Preprocess
744
-
745
- `.preprocess()` will always return the `output` type even if we are creating an input schema. If a different input type is required you can achieve this with a `.transform()` combined with a `.pipe()` or simply declare a manual `type` in `.openapi()`.
746
-
747
- #### Component Effects
748
-
749
- If you are adding a ZodSchema directly to the `components` section which is not referenced anywhere in the document, additional context may be required to create either an input or output schema. You can do this by setting the `refType` field to `input` or `output` in `.openapi()`. This defaults to `output` by default.
750
-
751
687
  ## Supported OpenAPI Versions
752
688
 
753
689
  Currently the following versions of OpenAPI are supported
754
690
 
755
- - `3.0.0`
756
- - `3.0.1`
757
- - `3.0.2`
758
- - `3.0.3`
759
691
  - `3.1.0`
692
+ - `3.1.1`
760
693
 
761
694
  Setting the `openapi` field will change how the some of the components are rendered.
762
695
 
@@ -785,72 +718,6 @@ As an example `z.string().nullable()` will be rendered differently
785
718
  }
786
719
  ```
787
720
 
788
- ## Supported Zod Schema
789
-
790
- - ZodAny
791
- - ZodArray
792
- - `minItems`/`maxItems` mapping for `.length()`, `.min()`, `.max()`
793
- - ZodBigInt
794
- - `integer` `type` and `int64` `format` mapping
795
- - ZodBoolean
796
- - ZodBranded
797
- - ZodCatch
798
- - Treated as ZodDefault
799
- - ZodCustom
800
- - ZodDate
801
- - `type` is mapped as `string` by default
802
- - ZodDefault
803
- - ZodDiscriminatedUnion
804
- - `discriminator` mapping when all schemas in the union are [registered](#creating-components). The discriminator must be a `ZodLiteral`, `ZodEnum` or `ZodNativeEnum` with string values. Only values wrapped in `ZodBranded`, `ZodReadOnly` and `ZodCatch` are supported.
805
- - ZodEffects
806
- - `transform` support for request schemas. See [Zod Effects](#zod-effects) for how to enable response schema support
807
- - `pre-process` support. We assume that the input type is the same as the output type. Otherwise pipe and transform can be used instead.
808
- - `refine` full support
809
- - ZodEnum
810
- - ZodIntersection
811
- - ZodLazy
812
- - The recursive schema within the ZodLazy or the ZodLazy _**must**_ be registered as a component. See [Creating Components](#creating-components) for more information.
813
- - ZodLiteral
814
- - ZodNativeEnum
815
- - supporting `string`, `number` and combined enums.
816
- - ZodNever
817
- - ZodNull
818
- - ZodNullable
819
- - ZodNumber
820
- - `integer` `type` mapping for `.int()`
821
- - `exclusiveMin`/`min`/`exclusiveMax`/`max` mapping for `.min()`, `.max()`, `lt()`, `gt()`, `.positive()`, `.negative()`, `.nonnegative()`, `.nonpositive()`.
822
- - `multipleOf` mapping for `.multipleOf()`
823
- - ZodObject
824
- - `additionalProperties` mapping for `.catchall()`, `.strict()`
825
- - `allOf` mapping for `.extend()` when the base object is registered and does not have `catchall()`, `strict()` and extension does not override a field.
826
- - ZodOptional
827
- - ZodPipeline
828
- - See [Zod Effects](#zod-effects) for more information.
829
- - ZodReadonly
830
- - ZodRecord
831
- - ZodSet
832
- - Treated as an array with `uniqueItems` (you may need to add a pre-process to convert it to a set)
833
- - ZodString
834
- - `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()`, `.date()`, `.time()`, `.duration()`, `.ip({ version: 'v4' })`, `.ip({ version: 'v6' })`, `.cidr({ version: 'v4' })`, `.cidr({ version: 'v6' })`
835
- - `minLength`/`maxLength` mapping for `.length()`, `.min()`, `.max()`
836
- - `pattern` mapping for `.regex()`, `.startsWith()`, `.endsWith()`, `.includes()`
837
- - `contentEncoding` mapping for `.base64()` for OpenAPI 3.1.0+
838
- - ZodTuple
839
- - `items` mapping for `.rest()`
840
- - `prefixItems` mapping for OpenAPI 3.1.0+
841
- - ZodUndefined
842
- - ZodUnion
843
- - By default it outputs an `anyOf` schema. Use `unionOneOf` to change this to output `oneOf` instead.
844
- - ZodUnknown
845
-
846
- If this library cannot determine a type for a Zod Schema, it will throw an error. To avoid this, declare a manual `type` in the `.openapi()` section of that schema.
847
-
848
- eg.
849
-
850
- ```typescript
851
- z.custom().openapi({ type: 'string' });
852
- ```
853
-
854
721
  ## Examples
855
722
 
856
723
  See the library in use in the [examples](./examples/) folder.
package/dist/api.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const components = require("./components.chunk.cjs");
4
4
  exports.createComponents = components.createComponents;
5
- exports.createMediaTypeSchema = components.createMediaTypeSchema;
6
- exports.createParamOrRef = components.createParamOrRef;
7
- exports.getDefaultComponents = components.getDefaultComponents;
8
- exports.getZodObject = components.getZodObject;
5
+ exports.createMediaTypeObject = components.createMediaTypeObject;
6
+ exports.createParameter = components.createParameter;
7
+ exports.createRegistry = components.createRegistry;
8
+ exports.unwrapZodObject = components.unwrapZodObject;
package/dist/api.d.mts CHANGED
@@ -1,3 +1,5 @@
1
- export { ComponentsObject, createComponents, getDefaultComponents } from './create/components.js';
2
- export { createMediaTypeSchema } from './create/content.js';
3
- export { createParamOrRef, getZodObject } from './create/parameters.js';
1
+ export { createComponents, createRegistry } from './create/components.js';
2
+ export { createMediaTypeObject } from './create/content.js';
3
+ export { createParameter } from './create/parameters.js';
4
+ export { unwrapZodObject } from './create/object.js';
5
+ export { Override, isAnyZodType } from './zod.js';
package/dist/api.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { ComponentsObject, createComponents, getDefaultComponents } from './create/components.js';
2
- export { createMediaTypeSchema } from './create/content.js';
3
- export { createParamOrRef, getZodObject } from './create/parameters.js';
1
+ export { createComponents, createRegistry } from './create/components.js';
2
+ export { createMediaTypeObject } from './create/content.js';
3
+ export { createParameter } from './create/parameters.js';
4
+ export { unwrapZodObject } from './create/object.js';
5
+ export { Override, isAnyZodType } from './zod.js';
package/dist/api.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { createComponents, createMediaTypeSchema, createParamOrRef, getDefaultComponents, getZodObject } from "./components.chunk.mjs";
1
+ import { createComponents, createMediaTypeObject, createParameter, createRegistry, unwrapZodObject } from "./components.chunk.mjs";
2
2
  export {
3
3
  createComponents,
4
- createMediaTypeSchema,
5
- createParamOrRef,
6
- getDefaultComponents,
7
- getZodObject
4
+ createMediaTypeObject,
5
+ createParameter,
6
+ createRegistry,
7
+ unwrapZodObject
8
8
  };