Skip to content

Commit 0bffa8c

Browse files
committed
feat(zod): add types option
1 parent 078ee9d commit 0bffa8c

File tree

21 files changed

+1546
-608
lines changed

21 files changed

+1546
-608
lines changed

docs/openapi-ts/plugins/zod.md

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,23 @@ The Zod plugin will generate the following artifacts, depending on the input spe
7272

7373
A single request schema is generated for each endpoint. It may contain a request body, parameters, and headers.
7474

75-
```ts
75+
::: code-group
76+
77+
```js [config]
78+
export default {
79+
input: 'https://get.heyapi.dev/hey-api/backend',
80+
output: 'src/client',
81+
plugins: [
82+
// ...other plugins
83+
{
84+
name: 'zod',
85+
requests: true, // [!code ++]
86+
},
87+
],
88+
};
89+
```
90+
91+
```ts [output]
7692
const zData = z.object({
7793
body: z
7894
.object({
@@ -87,6 +103,8 @@ const zData = z.object({
87103
});
88104
```
89105

106+
:::
107+
90108
::: tip
91109
If you need to access individual fields, you can do so using the [`.shape`](https://zod.dev/api?id=shape) API. For example, we can get the request body schema with `zData.shape.body`.
92110
:::
@@ -97,7 +115,23 @@ You can customize the naming and casing pattern for `requests` schemas using the
97115

98116
A single Zod schema is generated for all endpoint's responses. If the endpoint describes multiple responses, the generated schema is a union of all possible response shapes.
99117

100-
```ts
118+
::: code-group
119+
120+
```js [config]
121+
export default {
122+
input: 'https://get.heyapi.dev/hey-api/backend',
123+
output: 'src/client',
124+
plugins: [
125+
// ...other plugins
126+
{
127+
name: 'zod',
128+
responses: true, // [!code ++]
129+
},
130+
],
131+
};
132+
```
133+
134+
```ts [output]
101135
const zResponse = z.union([
102136
z.object({
103137
foo: z.string().optional(),
@@ -108,40 +142,103 @@ const zResponse = z.union([
108142
]);
109143
```
110144

145+
:::
146+
111147
You can customize the naming and casing pattern for `responses` schemas using the `.name` and `.case` options.
112148

113149
## Definitions
114150

115151
A Zod schema is generated for every reusable definition from your input.
116152

117-
```ts
153+
::: code-group
154+
155+
```js [config]
156+
export default {
157+
input: 'https://get.heyapi.dev/hey-api/backend',
158+
output: 'src/client',
159+
plugins: [
160+
// ...other plugins
161+
{
162+
name: 'zod',
163+
definitions: true, // [!code ++]
164+
},
165+
],
166+
};
167+
```
168+
169+
```ts [output]
118170
const zFoo = z.number().int();
119171

120172
const zBar = z.object({
121173
bar: z.array(z.number().int()).optional(),
122174
});
123175
```
124176

177+
:::
178+
125179
You can customize the naming and casing pattern for `definitions` schemas using the `.name` and `.case` options.
126180

127181
## Metadata
128182

129183
It's often useful to associate a schema with some additional [metadata](https://zod.dev/metadata) for documentation, code generation, AI structured outputs, form validation, and other purposes. If this is your use case, you can set `metadata` to `true` to generate additional metadata about schemas.
130184

131-
```js
185+
::: code-group
186+
187+
```js [config]
132188
export default {
133189
input: 'https://get.heyapi.dev/hey-api/backend',
134190
output: 'src/client',
135191
plugins: [
136192
// ...other plugins
137193
{
194+
name: 'zod',
138195
metadata: true, // [!code ++]
196+
},
197+
],
198+
};
199+
```
200+
201+
```ts [output]
202+
export const zFoo = z.string().describe('Additional metadata');
203+
```
204+
205+
:::
206+
207+
## Types
208+
209+
In addition to Zod schemas, you can generate schema-specific types. These can be generated for all schemas or for specific resources.
210+
211+
::: code-group
212+
213+
```js [config]
214+
export default {
215+
input: 'https://get.heyapi.dev/hey-api/backend',
216+
output: 'src/client',
217+
plugins: [
218+
// ...other plugins
219+
{
139220
name: 'zod',
221+
types: {
222+
infer: false, // by default, no `z.infer` types [!code ++]
223+
},
224+
responses: {
225+
types: {
226+
infer: true, // `z.infer` types only for response schemas [!code ++]
227+
},
228+
},
140229
},
141230
],
142231
};
143232
```
144233

234+
```ts [output]
235+
export type ResponseZodType = z.infer<typeof zResponse>;
236+
```
237+
238+
:::
239+
240+
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
241+
145242
## Config API
146243

147244
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface.

packages/openapi-ts-tests/test/3.1.x.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,24 @@ describe(`OpenAPI ${version}`, () => {
782782
}),
783783
description: 'generates validator schemas with metadata',
784784
},
785+
{
786+
config: createConfig({
787+
input: 'validators.yaml',
788+
output: 'validators-types',
789+
plugins: [
790+
{
791+
name: 'valibot',
792+
},
793+
{
794+
name: 'zod',
795+
types: {
796+
infer: true,
797+
},
798+
},
799+
],
800+
}),
801+
description: 'generates validator schemas with types',
802+
},
785803
{
786804
config: createConfig({
787805
input: 'validators-bigint-min-max.json',
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as v from 'valibot';
4+
5+
/**
6+
* This is Bar schema.
7+
*/
8+
export const vBar: v.GenericSchema = v.object({
9+
foo: v.optional(v.lazy(() => {
10+
return vFoo;
11+
}))
12+
});
13+
14+
/**
15+
* This is Foo schema.
16+
*/
17+
export const vFoo: v.GenericSchema = v.optional(v.union([
18+
v.object({
19+
foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))),
20+
bar: v.optional(vBar),
21+
baz: v.optional(v.array(v.lazy(() => {
22+
return vFoo;
23+
}))),
24+
qux: v.optional(v.pipe(v.number(), v.integer(), v.gtValue(0)), 0)
25+
}),
26+
v.null()
27+
]), null);
28+
29+
export const vBaz = v.optional(v.pipe(v.pipe(v.string(), v.regex(/foo\nbar/)), v.readonly()), 'baz');
30+
31+
export const vQux = v.record(v.string(), v.object({
32+
qux: v.optional(v.string())
33+
}));
34+
35+
/**
36+
* This is Foo parameter.
37+
*/
38+
export const vFoo2 = v.string();
39+
40+
export const vFoo3 = v.object({
41+
foo: v.optional(vBar)
42+
});
43+
44+
export const vPatchFooData = v.object({
45+
body: v.object({
46+
foo: v.optional(v.string())
47+
}),
48+
path: v.optional(v.never()),
49+
query: v.optional(v.object({
50+
foo: v.optional(v.string()),
51+
bar: v.optional(vBar),
52+
baz: v.optional(v.object({
53+
baz: v.optional(v.string())
54+
})),
55+
qux: v.optional(v.pipe(v.string(), v.isoDate())),
56+
quux: v.optional(v.pipe(v.string(), v.isoTimestamp()))
57+
}))
58+
});
59+
60+
export const vPostFooData = v.object({
61+
body: vFoo3,
62+
path: v.optional(v.never()),
63+
query: v.optional(v.never())
64+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
/**
6+
* This is Bar schema.
7+
*/
8+
export const zBar: z.AnyZodObject = z.object({
9+
foo: z.lazy(() => {
10+
return zFoo;
11+
}).optional()
12+
});
13+
14+
export type BarZodType = z.infer<typeof zBar>;
15+
16+
/**
17+
* This is Foo schema.
18+
*/
19+
export const zFoo: z.ZodTypeAny = z.union([
20+
z.object({
21+
foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(),
22+
bar: zBar.optional(),
23+
baz: z.array(z.lazy(() => {
24+
return zFoo;
25+
})).optional(),
26+
qux: z.number().int().gt(0).optional().default(0)
27+
}),
28+
z.null()
29+
]).default(null);
30+
31+
export type FooZodType = z.infer<typeof zFoo>;
32+
33+
export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz');
34+
35+
export type BazZodType = z.infer<typeof zBaz>;
36+
37+
export const zQux = z.record(z.object({
38+
qux: z.string().optional()
39+
}));
40+
41+
export type QuxZodType = z.infer<typeof zQux>;
42+
43+
/**
44+
* This is Foo parameter.
45+
*/
46+
export const zFoo2 = z.string();
47+
48+
export type FooZodType2 = z.infer<typeof zFoo2>;
49+
50+
export const zFoo3 = z.object({
51+
foo: zBar.optional()
52+
});
53+
54+
export type FooZodType3 = z.infer<typeof zFoo3>;
55+
56+
export const zPatchFooData = z.object({
57+
body: z.object({
58+
foo: z.string().optional()
59+
}),
60+
path: z.never().optional(),
61+
query: z.object({
62+
foo: z.string().optional(),
63+
bar: zBar.optional(),
64+
baz: z.object({
65+
baz: z.string().optional()
66+
}).optional(),
67+
qux: z.string().date().optional(),
68+
quux: z.string().datetime().optional()
69+
}).optional()
70+
});
71+
72+
export type PatchFooDataZodType = z.infer<typeof zPatchFooData>;
73+
74+
export const zPostFooData = z.object({
75+
body: zFoo3,
76+
path: z.never().optional(),
77+
query: z.never().optional()
78+
});
79+
80+
export type PostFooDataZodType = z.infer<typeof zPostFooData>;

0 commit comments

Comments
 (0)