zod-openapi 4.2.3 → 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,76 +3,52 @@
3
3
  <h1 align="center">zod-openapi</h1>
4
4
  </p>
5
5
  <p align="center">
6
- A Typescript library to use <a href="https://github.com/colinhacks/zod">Zod</a> Schemas to create 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
- <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/v/zod-openapi"/><a>
10
- <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/dm/zod-openapi"/><a>
11
- <a href="https://nodejs.org/en/"><img src="https://img.shields.io/badge/node-%3E%3D%2018-brightgreen"/><a>
12
- <a href="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml/badge.svg"/><a>
13
- <a href="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml/badge.svg"/><a>
14
- <a href="https://github.com/seek-oss/skuba"><img src="https://img.shields.io/badge/🤿%20skuba-powered-009DC4"/><a>
9
+ <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/v/zod-openapi"/></a>
10
+ <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/dm/zod-openapi"/></a>
11
+ <a href="https://nodejs.org/en/"><img src="https://img.shields.io/badge/node-%3E%3D%2018-brightgreen"/></a>
12
+ <a href="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml/badge.svg"/></a>
13
+ <a href="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml/badge.svg"/></a>
14
+ <a href="https://github.com/seek-oss/skuba"><img src="https://img.shields.io/badge/🤿%20skuba-powered-009DC4"/></a>
15
15
  </div>
16
16
  <br>
17
17
 
18
- ## Install
18
+ ## Installation
19
19
 
20
- Install via `npm`, `yarn` or `pnpm`:
20
+ Install via `npm`, `yarn`, or `pnpm`:
21
21
 
22
22
  ```bash
23
23
  npm install zod zod-openapi
24
- ## or
24
+ # or
25
25
  yarn add zod zod-openapi
26
- ## or
26
+ # or
27
27
  pnpm install zod zod-openapi
28
28
  ```
29
29
 
30
30
  ## Usage
31
31
 
32
- ### Extend Zod
32
+ ### `.meta()`
33
33
 
34
- This mutates Zod to add an extra `.openapi()` method. Call this at the top of your entry point(s). You can achieve this in two different ways, depending on your preference.
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
- ```
44
-
45
- #### Manual Extension
46
-
47
- This is useful if you have a specific instance of Zod or a Zod instance from another library that you would like to target.
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()`
45
+ `````typescript
59
46
 
60
- Use the `.openapi()` method to add metadata to a specific Zod type. The `.openapi()` method takes an object with the following options:
61
-
62
- | Option | Description |
63
- | :-------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------: |
64
- | OpenAPI Options | This will take any option you would put on a [SchemaObject](https://swagger.io/docs/specification/data-models/data-types/). |
65
- | `effectType` | Use to override the creation type for a [Zod Effect](#zod-effects) |
66
- | `header` | Use to provide metadata for [response headers](#response-headers) |
67
- | `param` | Use to provide metadata for [request parameters](#parameters) |
68
- | `ref` | Use this to [auto register a schema as a re-usable component](#creating-components) |
69
- | `refType` | Use this to set the creation type for a component which is not referenced in the document. |
70
- | `type` | Use this to override the generated type. If this is provided no metadata will be generated. |
71
- | `unionOneOf` | Set to `true` to force a single ZodUnion to output `oneOf` instead of `anyOf`. See [CreateDocumentOptions](#CreateDocumentOptions) for a global option |
47
+ ````typescript
72
48
 
73
49
  ### `createDocument`
74
50
 
75
- Creates an OpenAPI documentation object
51
+ Generates an OpenAPI documentation object.
76
52
 
77
53
  ```typescript
78
54
  import 'zod-openapi/extend';
@@ -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
 
@@ -459,6 +438,10 @@ Another way to register schema instead of adding a `ref` is to add it to the com
459
438
  eg.
460
439
 
461
440
  ```typescript
441
+ const title = z.string().openapi({
442
+ description: 'Job title',
443
+ example: 'My job',
444
+ });
462
445
  createDocument({
463
446
  components: {
464
447
  schemas: {
@@ -468,8 +451,6 @@ createDocument({
468
451
  });
469
452
  ```
470
453
 
471
- Unfortunately, as a limitation of this library, you will need to 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. As a result, I recommend utilising the auto registering components over manual registration.
472
-
473
454
  #### Parameters
474
455
 
475
456
  Query, Path, Header & Cookie parameters can be similarly registered:
@@ -479,7 +460,7 @@ Query, Path, Header & Cookie parameters can be similarly registered:
479
460
  const jobId = z.string().openapi({
480
461
  description: 'Job ID',
481
462
  example: '1234',
482
- param: { ref: 'jobRef' },
463
+ param: { id: 'jobRef' },
483
464
  });
484
465
 
485
466
  createDocument({
@@ -500,7 +481,7 @@ createDocument({
500
481
  const jobId = z.string().openapi({
501
482
  description: 'Job ID',
502
483
  example: '1234',
503
- param: { in: 'header', name: 'jobId', ref: 'jobRef' },
484
+ param: { in: 'header', name: 'jobId', id: 'jobRef' },
504
485
  });
505
486
 
506
487
  createDocument({
@@ -537,7 +518,7 @@ Response headers can be similarly registered:
537
518
  const header = z.string().openapi({
538
519
  description: 'Job ID',
539
520
  example: '1234',
540
- header: { ref: 'some-header' },
521
+ header: { id: 'some-header' },
541
522
  });
542
523
 
543
524
  // or
@@ -568,7 +549,7 @@ const response: ZodOpenApiResponseObject = {
568
549
  schema: z.object({ a: z.string() }),
569
550
  },
570
551
  },
571
- ref: 'some-response',
552
+ id: 'some-response',
572
553
  };
573
554
 
574
555
  //or
@@ -597,7 +578,7 @@ Callbacks can also be registered
597
578
 
598
579
  ```typescript
599
580
  const callback: ZodOpenApiCallbackObject = {
600
- ref: 'some-callback'
581
+ id: 'some-callback'
601
582
  post: {
602
583
  responses: {
603
584
  200: {
@@ -638,99 +619,77 @@ createDocument({
638
619
  });
639
620
  ```
640
621
 
641
- ### Zod Effects
622
+ ### Zod Types
642
623
 
643
- `.transform()`, `.catch()`, `.default()` and `.pipe()` are complicated because they all comprise of two different types that we could generate (input & output).
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.
644
625
 
645
- We attempt to determine what type of schema to create based on the following contexts:
626
+ Input:
646
627
 
647
- _Input_: Request Bodies, Request Parameters, Headers
628
+ - Request Parameters (query, path, header, cookie)
629
+ - Request Body
648
630
 
649
- _Output_: Responses, Response Headers
631
+ Output:
650
632
 
651
- As an example:
633
+ - Response Body
634
+ - Response Headers
635
+
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()`.
652
637
 
653
638
  ```ts
654
- z.object({
655
- a: z.string().default('a'),
639
+ const schema = z.object({
640
+ name: z.string(),
656
641
  });
657
642
  ```
658
643
 
659
- In a request context, this would render the following OpenAPI schema:
660
-
661
- ```yaml
662
- type: 'object'
663
- properties:
664
- - a:
665
- type: 'string'
666
- default: 'a'
667
- ```
668
-
669
- or the following for a response:
670
-
671
- ```yaml
672
- type: 'object'
673
- properties:
674
- - a:
675
- type: 'string'
676
- default: 'a'
677
- required:
678
- - a
679
- ```
680
-
681
- 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.
644
+ Input:
682
645
 
683
- #### EffectType
684
-
685
- ```ts
686
- 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
+ }
687
656
  ```
688
657
 
689
- 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:
690
659
 
691
- `same` - This informs Zod OpenAPI to pick either the input schema or output schema to generate with because they should be the same.
692
-
693
- ```ts
694
- z.string()
695
- .transform((str) => str.trim())
696
- .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
+ }
697
671
  ```
698
672
 
699
- 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.
700
674
 
701
- ```ts
702
- z.string()
703
- .transform((str) => str.length)
704
- .openapi({ effectType: 'same' });
705
- // ~~~~~~~~~~
706
- // Type 'same' is not assignable to type 'CreationType | undefined'.ts(2322)
707
- ```
675
+ If the schemas are not equal, it will automatically handle this by outputting the `output` schema with an `Output` suffix.
708
676
 
709
- `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.
710
678
 
711
679
  ```ts
712
- z.string()
713
- .transform((str) => str.length)
714
- .openapi({ effectType: 'input' });
680
+ const schema = z
681
+ .object({
682
+ name: z.string(),
683
+ })
684
+ .meta({ id: 'MyObject', outputId: 'MyObjectResponse' });
715
685
  ```
716
686
 
717
- #### Preprocess
718
-
719
- `.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()`.
720
-
721
- #### Component Effects
722
-
723
- 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.
724
-
725
687
  ## Supported OpenAPI Versions
726
688
 
727
689
  Currently the following versions of OpenAPI are supported
728
690
 
729
- - `3.0.0`
730
- - `3.0.1`
731
- - `3.0.2`
732
- - `3.0.3`
733
691
  - `3.1.0`
692
+ - `3.1.1`
734
693
 
735
694
  Setting the `openapi` field will change how the some of the components are rendered.
736
695
 
@@ -759,72 +718,6 @@ As an example `z.string().nullable()` will be rendered differently
759
718
  }
760
719
  ```
761
720
 
762
- ## Supported Zod Schema
763
-
764
- - ZodAny
765
- - ZodArray
766
- - `minItems`/`maxItems` mapping for `.length()`, `.min()`, `.max()`
767
- - ZodBigInt
768
- - `integer` `type` and `int64` `format` mapping
769
- - ZodBoolean
770
- - ZodBranded
771
- - ZodCatch
772
- - Treated as ZodDefault
773
- - ZodCustom
774
- - ZodDate
775
- - `type` is mapped as `string` by default
776
- - ZodDefault
777
- - ZodDiscriminatedUnion
778
- - `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.
779
- - ZodEffects
780
- - `transform` support for request schemas. See [Zod Effects](#zod-effects) for how to enable response schema support
781
- - `pre-process` support. We assume that the input type is the same as the output type. Otherwise pipe and transform can be used instead.
782
- - `refine` full support
783
- - ZodEnum
784
- - ZodIntersection
785
- - ZodLazy
786
- - The recursive schema within the ZodLazy or the ZodLazy _**must**_ be registered as a component. See [Creating Components](#creating-components) for more information.
787
- - ZodLiteral
788
- - ZodNativeEnum
789
- - supporting `string`, `number` and combined enums.
790
- - ZodNever
791
- - ZodNull
792
- - ZodNullable
793
- - ZodNumber
794
- - `integer` `type` mapping for `.int()`
795
- - `exclusiveMin`/`min`/`exclusiveMax`/`max` mapping for `.min()`, `.max()`, `lt()`, `gt()`, `.positive()`, `.negative()`, `.nonnegative()`, `.nonpositive()`.
796
- - `multipleOf` mapping for `.multipleOf()`
797
- - ZodObject
798
- - `additionalProperties` mapping for `.catchall()`, `.strict()`
799
- - `allOf` mapping for `.extend()` when the base object is registered and does not have `catchall()`, `strict()` and extension does not override a field.
800
- - ZodOptional
801
- - ZodPipeline
802
- - See [Zod Effects](#zod-effects) for more information.
803
- - ZodReadonly
804
- - ZodRecord
805
- - ZodSet
806
- - Treated as an array with `uniqueItems` (you may need to add a pre-process to convert it to a set)
807
- - ZodString
808
- - `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()`, `.date()`, `.time()`, `.duration()`, `.ip({ version: 'v4' })`, `.ip({ version: 'v6' })`, `.cidr({ version: 'v4' })`, `.cidr({ version: 'v6' })`
809
- - `minLength`/`maxLength` mapping for `.length()`, `.min()`, `.max()`
810
- - `pattern` mapping for `.regex()`, `.startsWith()`, `.endsWith()`, `.includes()`
811
- - `contentEncoding` mapping for `.base64()` for OpenAPI 3.1.0+
812
- - ZodTuple
813
- - `items` mapping for `.rest()`
814
- - `prefixItems` mapping for OpenAPI 3.1.0+
815
- - ZodUndefined
816
- - ZodUnion
817
- - By default it outputs an `anyOf` schema. Use `unionOneOf` to change this to output `oneOf` instead.
818
- - ZodUnknown
819
-
820
- 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.
821
-
822
- eg.
823
-
824
- ```typescript
825
- z.custom().openapi({ type: 'string' });
826
- ```
827
-
828
721
  ## Examples
829
722
 
830
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
  };