fork of hey-api/openapi-ts because I need some additional things

Merge pull request #2163 from hey-api/fix/zod-metadata

fix(zod): add metadata option

authored by

Lubos and committed by
GitHub
32a8d2c9 c9f3245f

+208 -23
+5
.changeset/few-apples-yawn.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(zod): add `metadata` option to generate additional metadata for documentation, code generation, AI structured outputs, form validation, and other purposes
+1 -1
docs/index.md
··· 3 3 4 4 hero: 5 5 name: High-quality tools for interacting with APIs 6 - tagline: Codegen for your TypeScript projects. Trusted over 1,250,000 times each month to generate reliable API clients and SDKs. 6 + tagline: Codegen for your TypeScript projects. Trusted over 1,500,000 times each month to generate reliable API clients and SDKs. 7 7 actions: 8 8 - link: /openapi-ts/get-started 9 9 text: Get Started
+1 -1
docs/openapi-ts/get-started.md
··· 13 13 This package is in initial development. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues). 14 14 ::: 15 15 16 - [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) is an OpenAPI to TypeScript codegen trusted over 1,250,000 times each month to generate reliable API clients and SDKs. The code is [MIT-licensed](/license) and free to use. Discover available features below or view our [roadmap](https://github.com/orgs/hey-api/discussions/1495) to learn what's coming next. 16 + [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) is an OpenAPI to TypeScript codegen trusted over 1,500,000 times each month to generate reliable API clients and SDKs. The code is [MIT-licensed](/license) and free to use. Discover available features below or view our [roadmap](https://github.com/orgs/hey-api/discussions/1495) to learn what's coming next. 17 17 18 18 ### Demo 19 19
+20 -6
docs/openapi-ts/plugins/zod.md
··· 33 33 In your [configuration](/openapi-ts/get-started), add `zod` to your plugins and you'll be ready to generate Zod artifacts. :tada: 34 34 35 35 ```js 36 - import { defaultPlugins } from '@hey-api/openapi-ts'; 37 - 38 36 export default { 39 37 input: 'https://get.heyapi.dev/hey-api/backend', 40 38 output: 'src/client', 41 39 plugins: [ 42 - ...defaultPlugins, 40 + // ...other plugins 43 41 '@hey-api/client-fetch', 44 42 'zod', // [!code ++] 45 43 ], ··· 51 49 To automatically validate response data in your SDKs, set `sdk.validator` to `true`. 52 50 53 51 ```js 54 - import { defaultPlugins } from '@hey-api/openapi-ts'; 55 - 56 52 export default { 57 53 input: 'https://get.heyapi.dev/hey-api/backend', 58 54 output: 'src/client', 59 55 plugins: [ 60 - ...defaultPlugins, 56 + // ...other plugins 61 57 '@hey-api/client-fetch', 62 58 'zod', 63 59 { ··· 118 114 const zBar = z.object({ 119 115 bar: z.array(z.number().int()).optional(), 120 116 }); 117 + ``` 118 + 119 + ## Metadata 120 + 121 + It's often useful to associate a schema with some additional 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. 122 + 123 + ```js 124 + export default { 125 + input: 'https://get.heyapi.dev/hey-api/backend', 126 + output: 'src/client', 127 + plugins: [ 128 + // ...other plugins 129 + { 130 + metadata: true, // [!code ++] 131 + name: 'zod', 132 + }, 133 + ], 134 + }; 121 135 ``` 122 136 123 137 <!--@include: ../../examples.md-->
+14
packages/openapi-ts-tests/test/3.1.x.test.ts
··· 719 719 }, 720 720 { 721 721 config: createConfig({ 722 + input: 'validators.yaml', 723 + output: 'validators-metadata', 724 + plugins: [ 725 + 'valibot', 726 + { 727 + metadata: true, 728 + name: 'zod', 729 + }, 730 + ], 731 + }), 732 + description: 'generates validator schemas with metadata', 733 + }, 734 + { 735 + config: createConfig({ 722 736 input: 'validators-bigint-min-max.json', 723 737 output: 'validators-bigint-min-max', 724 738 plugins: ['valibot', 'zod'],
+59
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts
··· 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 + /** 32 + * This is Foo parameter. 33 + */ 34 + export const vFoo2 = v.string(); 35 + 36 + export const vFoo3 = v.object({ 37 + foo: v.optional(vBar) 38 + }); 39 + 40 + export const vPatchFooData = v.object({ 41 + foo: v.optional(v.string()) 42 + }); 43 + 44 + /** 45 + * This is Foo parameter. 46 + */ 47 + export const vPatchFooParameterFoo = v.string(); 48 + 49 + export const vPatchFooParameterBar = vBar; 50 + 51 + export const vPatchFooParameterBaz = v.object({ 52 + baz: v.optional(v.string()) 53 + }); 54 + 55 + export const vPatchFooParameterQux = v.pipe(v.string(), v.isoDate()); 56 + 57 + export const vPatchFooParameterQuux = v.pipe(v.string(), v.isoDateTime()); 58 + 59 + export const vPostFooData = vFoo3;
+59
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/zod.gen.ts
··· 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 + }).describe('This is Bar schema.'); 13 + 14 + /** 15 + * This is Foo schema. 16 + */ 17 + export const zFoo: z.ZodTypeAny = z.union([ 18 + z.object({ 19 + foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).describe('This is foo property.').optional(), 20 + bar: zBar.optional(), 21 + baz: z.array(z.lazy(() => { 22 + return zFoo; 23 + })).describe('This is baz property.').optional(), 24 + qux: z.number().int().gt(0).describe('This is qux property.').optional().default(0) 25 + }), 26 + z.null() 27 + ]).default(null); 28 + 29 + export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); 30 + 31 + /** 32 + * This is Foo parameter. 33 + */ 34 + export const zFoo2 = z.string().describe('This is Foo parameter.'); 35 + 36 + export const zFoo3 = z.object({ 37 + foo: zBar.optional() 38 + }); 39 + 40 + export const zPatchFooData = z.object({ 41 + foo: z.string().optional() 42 + }); 43 + 44 + /** 45 + * This is Foo parameter. 46 + */ 47 + export const zPatchFooParameterFoo = z.string().describe('This is Foo parameter.'); 48 + 49 + export const zPatchFooParameterBar = zBar; 50 + 51 + export const zPatchFooParameterBaz = z.object({ 52 + baz: z.string().optional() 53 + }); 54 + 55 + export const zPatchFooParameterQux = z.string().date(); 56 + 57 + export const zPatchFooParameterQuux = z.string().datetime(); 58 + 59 + export const zPostFooData = zFoo3;
+8 -2
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts
··· 2 2 3 3 import * as v from 'valibot'; 4 4 5 + /** 6 + * This is Bar schema. 7 + */ 5 8 export const vBar: v.GenericSchema = v.object({ 6 9 foo: v.optional(v.lazy(() => { 7 10 return vFoo; 8 11 })) 9 12 }); 10 13 14 + /** 15 + * This is Foo schema. 16 + */ 11 17 export const vFoo: v.GenericSchema = v.optional(v.union([ 12 18 v.object({ 13 19 foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))), ··· 23 29 export const vBaz = v.optional(v.pipe(v.pipe(v.string(), v.regex(/foo\nbar/)), v.readonly()), 'baz'); 24 30 25 31 /** 26 - * aaaaa 32 + * This is Foo parameter. 27 33 */ 28 34 export const vFoo2 = v.string(); 29 35 ··· 36 42 }); 37 43 38 44 /** 39 - * aaaaa 45 + * This is Foo parameter. 40 46 */ 41 47 export const vPatchFooParameterFoo = v.string(); 42 48
+8 -2
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/zod.gen.ts
··· 2 2 3 3 import { z } from 'zod'; 4 4 5 + /** 6 + * This is Bar schema. 7 + */ 5 8 export const zBar: z.AnyZodObject = z.object({ 6 9 foo: z.lazy(() => { 7 10 return zFoo; 8 11 }).optional() 9 12 }); 10 13 14 + /** 15 + * This is Foo schema. 16 + */ 11 17 export const zFoo: z.ZodTypeAny = z.union([ 12 18 z.object({ 13 19 foo: z.string().regex(/^\d{3}-\d{2}-\d{4}$/).optional(), ··· 23 29 export const zBaz = z.string().regex(/foo\nbar/).readonly().default('baz'); 24 30 25 31 /** 26 - * aaaaa 32 + * This is Foo parameter. 27 33 */ 28 34 export const zFoo2 = z.string(); 29 35 ··· 36 42 }); 37 43 38 44 /** 39 - * aaaaa 45 + * This is Foo parameter. 40 46 */ 41 47 export const zPatchFooParameterFoo = z.string(); 42 48
+4 -8
packages/openapi-ts-tests/test/openapi-ts.config.ts
··· 51 51 // 'invalid', 52 52 // 'servers-entry.yaml', 53 53 // ), 54 - path: path.resolve( 55 - __dirname, 56 - 'spec', 57 - '3.1.x', 58 - 'object-property-names.yaml', 59 - ), 54 + path: path.resolve(__dirname, 'spec', '3.1.x', 'validators.yaml'), 60 55 // path: 'http://localhost:4000/', 61 56 // path: 'https://get.heyapi.dev/', 62 57 // path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0', ··· 150 145 { 151 146 // comments: false, 152 147 // exportFromIndex: true, 153 - // name: 'valibot', 148 + name: 'valibot', 154 149 }, 155 150 { 156 151 // comments: false, 157 152 // exportFromIndex: true, 158 - // name: 'zod', 153 + metadata: true, 154 + name: 'zod', 159 155 }, 160 156 ], 161 157 // useOptions: false,
+6 -1
packages/openapi-ts-tests/test/spec/3.1.x/validators.yaml
··· 53 53 components: 54 54 parameters: 55 55 Foo: 56 - description: aaaaa 56 + description: 'This is Foo parameter.' 57 57 name: foo 58 58 in: query 59 59 required: false ··· 72 72 schemas: 73 73 Foo: 74 74 default: null 75 + description: 'This is Foo schema.' 75 76 properties: 76 77 foo: 78 + description: 'This is foo property.' 77 79 pattern: ^\d{3}-\d{2}-\d{4}$ 78 80 type: string 79 81 bar: 80 82 $ref: '#/components/schemas/Bar' 81 83 baz: 84 + description: 'This is baz property.' 82 85 items: 83 86 $ref: '#/components/schemas/Foo' 84 87 type: array 85 88 qux: 89 + description: 'This is qux property.' 86 90 default: 0 87 91 exclusiveMinimum: 0 88 92 type: integer ··· 90 94 - object 91 95 - 'null' 92 96 Bar: 97 + description: 'This is Bar schema.' 93 98 properties: 94 99 foo: 95 100 $ref: '#/components/schemas/Foo'
+1
packages/openapi-ts/src/plugins/zod/config.ts
··· 8 8 _tags: ['validator'], 9 9 comments: true, 10 10 exportFromIndex: false, 11 + metadata: false, 11 12 name: 'zod', 12 13 output: 'zod', 13 14 };
+14 -2
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 24 24 25 25 // frequently used identifiers 26 26 const andIdentifier = compiler.identifier({ text: 'and' }); 27 + const arrayIdentifier = compiler.identifier({ text: 'array' }); 27 28 const coerceIdentifier = compiler.identifier({ text: 'coerce' }); 28 29 const defaultIdentifier = compiler.identifier({ text: 'default' }); 30 + const describeIdentifier = compiler.identifier({ text: 'describe' }); 29 31 const intersectionIdentifier = compiler.identifier({ text: 'intersection' }); 30 32 const lazyIdentifier = compiler.identifier({ text: 'lazy' }); 31 33 const lengthIdentifier = compiler.identifier({ text: 'length' }); ··· 54 56 }): ts.CallExpression => { 55 57 const functionName = compiler.propertyAccessExpression({ 56 58 expression: zIdentifier, 57 - name: compiler.identifier({ text: 'array' }), 59 + name: arrayIdentifier, 58 60 }); 59 61 60 62 let arrayExpression: ts.CallExpression | undefined; ··· 100 102 arrayExpression = compiler.callExpression({ 101 103 functionName: compiler.propertyAccessExpression({ 102 104 expression: zIdentifier, 103 - name: compiler.identifier({ text: 'array' }), 105 + name: arrayIdentifier, 104 106 }), 105 107 parameters: [ 106 108 compiler.callExpression({ ··· 987 989 }); 988 990 anyType = zodSchema.anyType; 989 991 expression = zodSchema.expression; 992 + 993 + if (plugin.metadata && schema.description) { 994 + expression = compiler.callExpression({ 995 + functionName: compiler.propertyAccessExpression({ 996 + expression, 997 + name: describeIdentifier, 998 + }), 999 + parameters: [compiler.stringLiteral({ text: schema.description })], 1000 + }); 1001 + } 990 1002 } else if (schema.items) { 991 1003 schema = deduplicateSchema({ schema }); 992 1004
+8
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 16 16 */ 17 17 exportFromIndex?: boolean; 18 18 /** 19 + * Enable Zod metadata support? It's often useful to associate a schema with 20 + * some additional metadata for documentation, code generation, AI 21 + * structured outputs, form validation, and other purposes. 22 + * 23 + * @default false 24 + */ 25 + metadata?: boolean; 26 + /** 19 27 * Customise the Zod schema name. By default, `z{{name}}` is used, 20 28 * where `name` is a definition name or an operation name. 21 29 */