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

Configure Feed

Select the types of activity you want to include in your feed.

chore: Zod 4 plugin

Lubos 2926c1d9 3fbc04bf

+472 -127
+6 -1
docs/.vitepress/theme/components/VersionSwitcher.vue
··· 5 5 6 6 type Option = { 7 7 label: string; 8 + short?: string; 8 9 value: string; 9 10 }; 10 11 ··· 49 50 :isClearable="false" 50 51 :options="props.values" 51 52 @option-selected="switchVersion" 52 - /> 53 + > 54 + <template #value="{ option }"> 55 + {{ option.short || option.label }} 56 + </template> 57 + </VueSelect> 53 58 </template>
+9 -5
docs/.vitepress/theme/custom.css
··· 310 310 margin-top: 1.3rem; 311 311 } 312 312 313 - .heading-with-version { 314 - display: flex; 315 - column-gap: 1rem; 316 - } 317 - 318 313 .vue-select .control { 319 314 cursor: pointer; 320 315 transition: border-color 0.25s; ··· 328 323 padding: 0; 329 324 } 330 325 326 + .vue-select .control .value-container.has-value:has(.single-value) { 327 + padding-inline-end: 0; 328 + } 329 + 331 330 .vue-select .control .indicators-container button.dropdown-icon { 332 331 height: 100%; 333 332 padding: var(--vs-padding); ··· 336 335 ); 337 336 } 338 337 338 + .vue-select .menu { 339 + width: fit-content !important; 340 + } 341 + 339 342 .vue-select .menu .menu-option { 340 343 border-left: 2px solid transparent; 341 344 border-right: 2px solid transparent; 345 + white-space: nowrap; 342 346 transition: 343 347 background-color 0.25s, 344 348 border-color 0.25s,
+2 -5
docs/openapi-ts/plugins/zod.md
··· 5 5 6 6 <script setup lang="ts"> 7 7 // import { embedProject } from '../../embed' 8 - import ZodVersionSwitcher from './zod/ZodVersionSwitcher.vue'; 8 + import ZodHeading from './zod/ZodHeading.vue'; 9 9 </script> 10 10 11 - <div class="heading-with-version"> 12 - <h1>Zod</h1> 13 - <ZodVersionSwitcher /> 14 - </div> 11 + <ZodHeading /> 15 12 16 13 ### About 17 14
+34
docs/openapi-ts/plugins/zod/ZodHeading.vue
··· 1 + <script setup lang="ts"> 2 + const versions = [ 3 + { 4 + label: 'Zod 4', 5 + short: 'v4', 6 + value: 'v4', 7 + }, 8 + { 9 + label: 'Zod Mini', 10 + short: 'Mini', 11 + value: 'mini', 12 + }, 13 + { 14 + label: 'Zod 3', 15 + short: 'v3', 16 + value: 'v3', 17 + }, 18 + ]; 19 + </script> 20 + 21 + <template> 22 + <div class="container"> 23 + <h1>Zod</h1> 24 + <VersionSwitcher :values="versions" default="v4" /> 25 + </div> 26 + </template> 27 + 28 + <style scoped> 29 + .container { 30 + align-items: center; 31 + column-gap: 1rem; 32 + display: flex; 33 + } 34 + </style>
-16
docs/openapi-ts/plugins/zod/ZodVersionSwitcher.vue
··· 1 - <script setup lang="ts"> 2 - const versions = [ 3 - { 4 - label: 'v3', 5 - value: 'v3', 6 - }, 7 - { 8 - label: 'v4', 9 - value: 'v4', 10 - }, 11 - ]; 12 - </script> 13 - 14 - <template> 15 - <VersionSwitcher :values="versions" default="v4" /> 16 - </template>
+244
docs/openapi-ts/plugins/zod/mini.md
··· 1 + --- 2 + title: Zod 3 + description: Zod plugin for Hey API. Compatible with all our features. 4 + --- 5 + 6 + <script setup lang="ts"> 7 + // import { embedProject } from '../../embed' 8 + import ZodHeading from './ZodHeading.vue'; 9 + </script> 10 + 11 + <ZodHeading /> 12 + 13 + ### About 14 + 15 + [Zod](https://zod.dev) is a TypeScript-first schema validation library with static type inference. 16 + 17 + <!-- ### Demo 18 + 19 + <button class="buttonLink" @click="(event) => embedProject('hey-api-client-fetch-plugin-zod-example')(event)"> 20 + Launch demo 21 + </button> --> 22 + 23 + ## Features 24 + 25 + - seamless integration with `@hey-api/openapi-ts` ecosystem 26 + - Zod schemas for requests, responses, and reusable definitions 27 + 28 + ## Installation 29 + 30 + In your [configuration](/openapi-ts/get-started), add `zod` to your plugins and you'll be ready to generate Zod artifacts. :tada: 31 + 32 + ```js 33 + export default { 34 + input: 'https://get.heyapi.dev/hey-api/backend', 35 + output: 'src/client', 36 + plugins: [ 37 + // ...other plugins 38 + 'zod', // [!code ++] 39 + ], 40 + }; 41 + ``` 42 + 43 + ### SDKs 44 + 45 + To add data validators to your SDKs, set `sdk.validator` to `true`. 46 + 47 + ```js 48 + export default { 49 + input: 'https://get.heyapi.dev/hey-api/backend', 50 + output: 'src/client', 51 + plugins: [ 52 + // ...other plugins 53 + 'zod', 54 + { 55 + name: '@hey-api/sdk', // [!code ++] 56 + validator: true, // [!code ++] 57 + }, 58 + ], 59 + }; 60 + ``` 61 + 62 + Learn more about data validators in your SDKs on the [SDKs](/openapi-ts/output/sdk#validators) page. 63 + 64 + ## Output 65 + 66 + The Zod plugin will generate the following artifacts, depending on the input specification. 67 + 68 + ## Requests 69 + 70 + A single request schema is generated for each endpoint. It may contain a request body, parameters, and headers. 71 + 72 + ::: code-group 73 + 74 + ```js [config] 75 + export default { 76 + input: 'https://get.heyapi.dev/hey-api/backend', 77 + output: 'src/client', 78 + plugins: [ 79 + // ...other plugins 80 + { 81 + name: 'zod', 82 + requests: true, // [!code ++] 83 + }, 84 + ], 85 + }; 86 + ``` 87 + 88 + ```ts [output] 89 + const zData = z.object({ 90 + body: z 91 + .object({ 92 + foo: z.string().optional(), 93 + bar: z.union([z.number(), z.null()]).optional(), 94 + }) 95 + .optional(), 96 + path: z.object({ 97 + baz: z.string(), 98 + }), 99 + query: z.never().optional(), 100 + }); 101 + ``` 102 + 103 + ::: 104 + 105 + ::: tip 106 + 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`. 107 + ::: 108 + 109 + You can customize the naming and casing pattern for `requests` schemas using the `.name` and `.case` options. 110 + 111 + ## Responses 112 + 113 + 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. 114 + 115 + ::: code-group 116 + 117 + ```js [config] 118 + export default { 119 + input: 'https://get.heyapi.dev/hey-api/backend', 120 + output: 'src/client', 121 + plugins: [ 122 + // ...other plugins 123 + { 124 + name: 'zod', 125 + responses: true, // [!code ++] 126 + }, 127 + ], 128 + }; 129 + ``` 130 + 131 + ```ts [output] 132 + const zResponse = z.union([ 133 + z.object({ 134 + foo: z.string().optional(), 135 + }), 136 + z.object({ 137 + bar: z.number().optional(), 138 + }), 139 + ]); 140 + ``` 141 + 142 + ::: 143 + 144 + You can customize the naming and casing pattern for `responses` schemas using the `.name` and `.case` options. 145 + 146 + ## Definitions 147 + 148 + A Zod schema is generated for every reusable definition from your input. 149 + 150 + ::: code-group 151 + 152 + ```js [config] 153 + export default { 154 + input: 'https://get.heyapi.dev/hey-api/backend', 155 + output: 'src/client', 156 + plugins: [ 157 + // ...other plugins 158 + { 159 + name: 'zod', 160 + definitions: true, // [!code ++] 161 + }, 162 + ], 163 + }; 164 + ``` 165 + 166 + ```ts [output] 167 + const zFoo = z.number().int(); 168 + 169 + const zBar = z.object({ 170 + bar: z.array(z.number().int()).optional(), 171 + }); 172 + ``` 173 + 174 + ::: 175 + 176 + You can customize the naming and casing pattern for `definitions` schemas using the `.name` and `.case` options. 177 + 178 + ## Metadata 179 + 180 + 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. 181 + 182 + ::: code-group 183 + 184 + ```js [config] 185 + export default { 186 + input: 'https://get.heyapi.dev/hey-api/backend', 187 + output: 'src/client', 188 + plugins: [ 189 + // ...other plugins 190 + { 191 + name: 'zod', 192 + metadata: true, // [!code ++] 193 + }, 194 + ], 195 + }; 196 + ``` 197 + 198 + ```ts [output] 199 + export const zFoo = z.string().describe('Additional metadata'); 200 + ``` 201 + 202 + ::: 203 + 204 + ## Types 205 + 206 + In addition to Zod schemas, you can generate schema-specific types. These can be generated for all schemas or for specific resources. 207 + 208 + ::: code-group 209 + 210 + ```js [config] 211 + export default { 212 + input: 'https://get.heyapi.dev/hey-api/backend', 213 + output: 'src/client', 214 + plugins: [ 215 + // ...other plugins 216 + { 217 + name: 'zod', 218 + types: { 219 + infer: false, // by default, no `z.infer` types [!code ++] 220 + }, 221 + responses: { 222 + types: { 223 + infer: true, // `z.infer` types only for response schemas [!code ++] 224 + }, 225 + }, 226 + }, 227 + ], 228 + }; 229 + ``` 230 + 231 + ```ts [output] 232 + export type ResponseZodType = z.infer<typeof zResponse>; 233 + ``` 234 + 235 + ::: 236 + 237 + You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options. 238 + 239 + ## Config API 240 + 241 + 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. 242 + 243 + <!--@include: ../../../partials/examples.md--> 244 + <!--@include: ../../../partials/sponsors.md-->
+2 -5
docs/openapi-ts/plugins/zod/v3.md
··· 5 5 6 6 <script setup lang="ts"> 7 7 // import { embedProject } from '../../embed' 8 - import ZodVersionSwitcher from './ZodVersionSwitcher.vue'; 8 + import ZodHeading from './ZodHeading.vue'; 9 9 </script> 10 10 11 - <div class="heading-with-version"> 12 - <h1>Zod</h1> 13 - <ZodVersionSwitcher /> 14 - </div> 11 + <ZodHeading /> 15 12 16 13 ### About 17 14
+2 -2
packages/openapi-ts-tests/test/openapi-ts.config.ts
··· 43 43 '3.1.x', 44 44 // 'case.yaml', 45 45 // 'enum-inline.yaml', 46 - // 'full.yaml', 46 + 'full.yaml', 47 47 // 'object-property-names.yaml', 48 48 // 'transformers-all-of.yaml', 49 - 'validators.yaml', 49 + // 'validators.yaml', 50 50 ), 51 51 // path: path.resolve(__dirname, 'spec', 'v3-transforms.json'), 52 52 // path: path.resolve(__dirname, 'spec', 'v3.json'),
+7
packages/openapi-ts/src/plugins/zod/constants.ts
··· 6 6 bigint: compiler.identifier({ text: 'bigint' }), 7 7 boolean: compiler.identifier({ text: 'boolean' }), 8 8 coerce: compiler.identifier({ text: 'coerce' }), 9 + date: compiler.identifier({ text: 'date' }), 9 10 datetime: compiler.identifier({ text: 'datetime' }), 10 11 default: compiler.identifier({ text: 'default' }), 11 12 describe: compiler.identifier({ text: 'describe' }), 13 + email: compiler.identifier({ text: 'email' }), 12 14 enum: compiler.identifier({ text: 'enum' }), 13 15 gt: compiler.identifier({ text: 'gt' }), 14 16 gte: compiler.identifier({ text: 'gte' }), ··· 16 18 int: compiler.identifier({ text: 'int' }), 17 19 intersection: compiler.identifier({ text: 'intersection' }), 18 20 ip: compiler.identifier({ text: 'ip' }), 21 + ipv4: compiler.identifier({ text: 'ipv4' }), 22 + ipv6: compiler.identifier({ text: 'ipv6' }), 23 + iso: compiler.identifier({ text: 'iso' }), 19 24 lazy: compiler.identifier({ text: 'lazy' }), 20 25 length: compiler.identifier({ text: 'length' }), 21 26 literal: compiler.identifier({ text: 'literal' }), ··· 34 39 record: compiler.identifier({ text: 'record' }), 35 40 regex: compiler.identifier({ text: 'regex' }), 36 41 string: compiler.identifier({ text: 'string' }), 42 + time: compiler.identifier({ text: 'time' }), 37 43 tuple: compiler.identifier({ text: 'tuple' }), 38 44 undefined: compiler.identifier({ text: 'undefined' }), 39 45 union: compiler.identifier({ text: 'union' }), 40 46 unknown: compiler.identifier({ text: 'unknown' }), 41 47 url: compiler.identifier({ text: 'url' }), 48 + uuid: compiler.identifier({ text: 'uuid' }), 42 49 void: compiler.identifier({ text: 'void' }), 43 50 z: compiler.identifier({ text: 'z' }), 44 51 };
+39 -18
packages/openapi-ts/src/plugins/zod/mini/plugin.ts
··· 9 9 import { identifiers, zodId } from '../constants'; 10 10 import { exportZodSchema } from '../export'; 11 11 import { getZodModule } from '../shared/module'; 12 - import type { ZodSchema } from '../shared/types'; 12 + import { operationToZodSchema } from '../shared/operation'; 13 + import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 13 14 import type { ZodPlugin } from '../types'; 14 - import { operationToZodSchema } from '../v3/operation'; 15 - 16 - interface SchemaWithType<T extends Required<IR.SchemaObject>['type']> 17 - extends Omit<IR.SchemaObject, 'type'> { 18 - type: Extract<Required<IR.SchemaObject>['type'], T>; 19 - } 20 - 21 - export type State = { 22 - circularReferenceTracker: Array<string>; 23 - hasCircularReference: boolean; 24 - }; 25 15 26 16 const arrayTypeToZodSchema = ({ 27 17 plugin, ··· 462 452 463 453 if (schema.format) { 464 454 switch (schema.format) { 455 + case 'date': 456 + stringExpression = compiler.callExpression({ 457 + functionName: compiler.propertyAccessExpression({ 458 + expression: stringExpression, 459 + name: identifiers.date, 460 + }), 461 + }); 462 + break; 465 463 case 'date-time': 466 464 stringExpression = compiler.callExpression({ 467 465 functionName: compiler.propertyAccessExpression({ ··· 482 480 : [], 483 481 }); 484 482 break; 483 + case 'email': 484 + stringExpression = compiler.callExpression({ 485 + functionName: compiler.propertyAccessExpression({ 486 + expression: stringExpression, 487 + name: identifiers.email, 488 + }), 489 + }); 490 + break; 485 491 case 'ipv4': 486 492 case 'ipv6': 487 493 stringExpression = compiler.callExpression({ ··· 491 497 }), 492 498 }); 493 499 break; 500 + case 'time': 501 + stringExpression = compiler.callExpression({ 502 + functionName: compiler.propertyAccessExpression({ 503 + expression: stringExpression, 504 + name: identifiers.time, 505 + }), 506 + }); 507 + break; 494 508 case 'uri': 495 509 stringExpression = compiler.callExpression({ 496 510 functionName: compiler.propertyAccessExpression({ ··· 499 513 }), 500 514 }); 501 515 break; 502 - case 'date': 503 - case 'email': 504 - case 'time': 505 516 case 'uuid': 506 517 stringExpression = compiler.callExpression({ 507 518 functionName: compiler.propertyAccessExpression({ 508 519 expression: stringExpression, 509 - name: compiler.identifier({ text: schema.format }), 520 + name: identifiers.uuid, 510 521 }), 511 522 }); 512 523 break; ··· 746 757 } 747 758 }; 748 759 749 - export const schemaToZodSchema = ({ 760 + const schemaToZodSchema = ({ 750 761 optional, 751 762 plugin, 752 763 schema, ··· 1010 1021 1011 1022 plugin.forEach('operation', 'parameter', 'requestBody', 'schema', (event) => { 1012 1023 if (event.type === 'operation') { 1013 - operationToZodSchema({ operation: event.operation, plugin }); 1024 + operationToZodSchema({ 1025 + getZodSchema: (schema) => { 1026 + const state: State = { 1027 + circularReferenceTracker: [], 1028 + hasCircularReference: false, 1029 + }; 1030 + return schemaToZodSchema({ plugin, schema, state }); 1031 + }, 1032 + operation: event.operation, 1033 + plugin, 1034 + }); 1014 1035 } else if (event.type === 'parameter') { 1015 1036 handleComponent({ 1016 1037 id: event.$ref,
+12
packages/openapi-ts/src/plugins/zod/shared/types.d.ts
··· 1 1 import type ts from 'typescript'; 2 2 3 + import type { IR } from '../../../ir/types'; 4 + 5 + export interface SchemaWithType<T extends Required<IR.SchemaObject>['type']> 6 + extends Omit<IR.SchemaObject, 'type'> { 7 + type: Extract<Required<IR.SchemaObject>['type'], T>; 8 + } 9 + 10 + export type State = { 11 + circularReferenceTracker: Array<string>; 12 + hasCircularReference: boolean; 13 + }; 14 + 3 15 export type ZodSchema = { 4 16 expression: ts.Expression; 5 17 typeName?: string;
+5 -17
packages/openapi-ts/src/plugins/zod/v3/operation.ts packages/openapi-ts/src/plugins/zod/shared/operation.ts
··· 4 4 import { zodId } from '../constants'; 5 5 import { exportZodSchema } from '../export'; 6 6 import type { ZodPlugin } from '../types'; 7 - import type { State } from './plugin'; 8 - import { schemaToZodSchema } from './plugin'; 7 + import type { ZodSchema } from './types'; 9 8 10 9 export const operationToZodSchema = ({ 10 + getZodSchema, 11 11 operation, 12 12 plugin, 13 13 }: { 14 + getZodSchema: (schema: IR.SchemaObject) => ZodSchema; 14 15 operation: IR.OperationObject; 15 16 plugin: ZodPlugin['Instance']; 16 17 }) => { 17 - const state: State = { 18 - circularReferenceTracker: [], 19 - hasCircularReference: false, 20 - }; 21 - 22 18 const file = plugin.context.file({ id: zodId })!; 23 19 24 20 if (plugin.config.requests.enabled) { ··· 119 115 120 116 schemaData.required = [...requiredProperties]; 121 117 122 - const zodSchema = schemaToZodSchema({ 123 - plugin, 124 - schema: schemaData, 125 - state, 126 - }); 118 + const zodSchema = getZodSchema(schemaData); 127 119 const schemaId = plugin.api.getId({ operation, type: 'data' }); 128 120 const typeInferId = plugin.config.requests.types.infer.enabled 129 121 ? plugin.api.getId({ operation, type: 'type-infer-data' }) ··· 158 150 const { response } = operationResponsesMap(operation); 159 151 160 152 if (response) { 161 - const zodSchema = schemaToZodSchema({ 162 - plugin, 163 - schema: response, 164 - state, 165 - }); 153 + const zodSchema = getZodSchema(response); 166 154 const schemaId = plugin.api.getId({ operation, type: 'responses' }); 167 155 const typeInferId = plugin.config.responses.types.infer.enabled 168 156 ? plugin.api.getId({ operation, type: 'type-infer-responses' })
+39 -18
packages/openapi-ts/src/plugins/zod/v3/plugin.ts
··· 9 9 import { identifiers, zodId } from '../constants'; 10 10 import { exportZodSchema } from '../export'; 11 11 import { getZodModule } from '../shared/module'; 12 - import type { ZodSchema } from '../shared/types'; 12 + import { operationToZodSchema } from '../shared/operation'; 13 + import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 13 14 import type { ZodPlugin } from '../types'; 14 - import { operationToZodSchema } from './operation'; 15 - 16 - interface SchemaWithType<T extends Required<IR.SchemaObject>['type']> 17 - extends Omit<IR.SchemaObject, 'type'> { 18 - type: Extract<Required<IR.SchemaObject>['type'], T>; 19 - } 20 - 21 - export type State = { 22 - circularReferenceTracker: Array<string>; 23 - hasCircularReference: boolean; 24 - }; 25 15 26 16 const arrayTypeToZodSchema = ({ 27 17 plugin, ··· 462 452 463 453 if (schema.format) { 464 454 switch (schema.format) { 455 + case 'date': 456 + stringExpression = compiler.callExpression({ 457 + functionName: compiler.propertyAccessExpression({ 458 + expression: stringExpression, 459 + name: identifiers.date, 460 + }), 461 + }); 462 + break; 465 463 case 'date-time': 466 464 stringExpression = compiler.callExpression({ 467 465 functionName: compiler.propertyAccessExpression({ ··· 482 480 : [], 483 481 }); 484 482 break; 483 + case 'email': 484 + stringExpression = compiler.callExpression({ 485 + functionName: compiler.propertyAccessExpression({ 486 + expression: stringExpression, 487 + name: identifiers.email, 488 + }), 489 + }); 490 + break; 485 491 case 'ipv4': 486 492 case 'ipv6': 487 493 stringExpression = compiler.callExpression({ ··· 491 497 }), 492 498 }); 493 499 break; 500 + case 'time': 501 + stringExpression = compiler.callExpression({ 502 + functionName: compiler.propertyAccessExpression({ 503 + expression: stringExpression, 504 + name: identifiers.time, 505 + }), 506 + }); 507 + break; 494 508 case 'uri': 495 509 stringExpression = compiler.callExpression({ 496 510 functionName: compiler.propertyAccessExpression({ ··· 499 513 }), 500 514 }); 501 515 break; 502 - case 'date': 503 - case 'email': 504 - case 'time': 505 516 case 'uuid': 506 517 stringExpression = compiler.callExpression({ 507 518 functionName: compiler.propertyAccessExpression({ 508 519 expression: stringExpression, 509 - name: compiler.identifier({ text: schema.format }), 520 + name: identifiers.uuid, 510 521 }), 511 522 }); 512 523 break; ··· 746 757 } 747 758 }; 748 759 749 - export const schemaToZodSchema = ({ 760 + const schemaToZodSchema = ({ 750 761 optional, 751 762 plugin, 752 763 schema, ··· 1010 1021 1011 1022 plugin.forEach('operation', 'parameter', 'requestBody', 'schema', (event) => { 1012 1023 if (event.type === 'operation') { 1013 - operationToZodSchema({ operation: event.operation, plugin }); 1024 + operationToZodSchema({ 1025 + getZodSchema: (schema) => { 1026 + const state: State = { 1027 + circularReferenceTracker: [], 1028 + hasCircularReference: false, 1029 + }; 1030 + return schemaToZodSchema({ plugin, schema, state }); 1031 + }, 1032 + operation: event.operation, 1033 + plugin, 1034 + }); 1014 1035 } else if (event.type === 'parameter') { 1015 1036 handleComponent({ 1016 1037 id: event.$ref,
+71 -40
packages/openapi-ts/src/plugins/zod/v4/plugin.ts
··· 9 9 import { identifiers, zodId } from '../constants'; 10 10 import { exportZodSchema } from '../export'; 11 11 import { getZodModule } from '../shared/module'; 12 - import type { ZodSchema } from '../shared/types'; 12 + import { operationToZodSchema } from '../shared/operation'; 13 + import type { SchemaWithType, State, ZodSchema } from '../shared/types'; 13 14 import type { ZodPlugin } from '../types'; 14 - import { operationToZodSchema } from '../v3/operation'; 15 - 16 - interface SchemaWithType<T extends Required<IR.SchemaObject>['type']> 17 - extends Omit<IR.SchemaObject, 'type'> { 18 - type: Extract<Required<IR.SchemaObject>['type'], T>; 19 - } 20 - 21 - export type State = { 22 - circularReferenceTracker: Array<string>; 23 - hasCircularReference: boolean; 24 - }; 25 15 26 16 const arrayTypeToZodSchema = ({ 27 17 plugin, ··· 295 285 if (!isBigInt && schema.type === 'integer') { 296 286 numberExpression = compiler.callExpression({ 297 287 functionName: compiler.propertyAccessExpression({ 298 - expression: numberExpression, 288 + expression: identifiers.z, 299 289 name: identifiers.int, 300 290 }), 301 291 }); ··· 353 343 schema: SchemaWithType<'object'>; 354 344 state: State; 355 345 }): { 356 - anyType: string; 357 346 expression: ts.CallExpression; 358 347 } => { 359 348 // TODO: parser - handle constants ··· 414 403 expression: identifiers.z, 415 404 name: identifiers.record, 416 405 }), 417 - parameters: [zodSchema], 406 + parameters: [ 407 + compiler.callExpression({ 408 + functionName: compiler.propertyAccessExpression({ 409 + expression: identifiers.z, 410 + name: identifiers.string, 411 + }), 412 + parameters: [], 413 + }), 414 + zodSchema, 415 + ], 418 416 }); 419 417 return { 420 - anyType: 'AnyZodObject', 421 418 expression, 422 419 }; 423 420 } ··· 430 427 parameters: [ts.factory.createObjectLiteralExpression(properties, true)], 431 428 }); 432 429 return { 433 - anyType: 'AnyZodObject', 434 430 expression, 435 431 }; 436 432 }; ··· 462 458 463 459 if (schema.format) { 464 460 switch (schema.format) { 461 + case 'date': 462 + stringExpression = compiler.callExpression({ 463 + functionName: compiler.propertyAccessExpression({ 464 + expression: compiler.propertyAccessExpression({ 465 + expression: identifiers.z, 466 + name: identifiers.iso, 467 + }), 468 + name: identifiers.date, 469 + }), 470 + }); 471 + break; 465 472 case 'date-time': 466 473 stringExpression = compiler.callExpression({ 467 474 functionName: compiler.propertyAccessExpression({ 468 - expression: stringExpression, 475 + expression: compiler.propertyAccessExpression({ 476 + expression: identifiers.z, 477 + name: identifiers.iso, 478 + }), 469 479 name: identifiers.datetime, 470 480 }), 471 481 parameters: plugin.config.dates.offset ··· 482 492 : [], 483 493 }); 484 494 break; 495 + case 'email': 496 + stringExpression = compiler.callExpression({ 497 + functionName: compiler.propertyAccessExpression({ 498 + expression: identifiers.z, 499 + name: identifiers.email, 500 + }), 501 + }); 502 + break; 485 503 case 'ipv4': 504 + stringExpression = compiler.callExpression({ 505 + functionName: compiler.propertyAccessExpression({ 506 + expression: identifiers.z, 507 + name: identifiers.ipv4, 508 + }), 509 + }); 510 + break; 486 511 case 'ipv6': 487 512 stringExpression = compiler.callExpression({ 488 513 functionName: compiler.propertyAccessExpression({ 489 - expression: stringExpression, 490 - name: identifiers.ip, 514 + expression: identifiers.z, 515 + name: identifiers.ipv6, 516 + }), 517 + }); 518 + break; 519 + case 'time': 520 + stringExpression = compiler.callExpression({ 521 + functionName: compiler.propertyAccessExpression({ 522 + expression: compiler.propertyAccessExpression({ 523 + expression: identifiers.z, 524 + name: identifiers.iso, 525 + }), 526 + name: identifiers.time, 491 527 }), 492 528 }); 493 529 break; 494 530 case 'uri': 495 531 stringExpression = compiler.callExpression({ 496 532 functionName: compiler.propertyAccessExpression({ 497 - expression: stringExpression, 533 + expression: identifiers.z, 498 534 name: identifiers.url, 499 535 }), 500 536 }); 501 537 break; 502 - case 'date': 503 - case 'email': 504 - case 'time': 505 538 case 'uuid': 506 539 stringExpression = compiler.callExpression({ 507 540 functionName: compiler.propertyAccessExpression({ 508 - expression: stringExpression, 509 - name: compiler.identifier({ text: schema.format }), 541 + expression: identifiers.z, 542 + name: identifiers.uuid, 510 543 }), 511 544 }); 512 545 break; ··· 661 694 schema: IR.SchemaObject; 662 695 state: State; 663 696 }): { 664 - anyType?: string; 665 697 expression: ts.Expression; 666 698 } => { 667 699 switch (schema.type as Required<IR.SchemaObject>['type']) { ··· 746 778 } 747 779 }; 748 780 749 - export const schemaToZodSchema = ({ 781 + const schemaToZodSchema = ({ 750 782 optional, 751 783 plugin, 752 784 schema, ··· 789 821 }), 790 822 ], 791 823 }); 792 - state.hasCircularReference = true; 793 824 } else if (!file.getName(id)) { 794 825 // if $ref hasn't been processed yet, inline it to avoid the 795 826 // "Block-scoped variable used before its declaration." error ··· 814 845 } else if (schema.type) { 815 846 const zSchema = schemaTypeToZodSchema({ plugin, schema, state }); 816 847 zodSchema.expression = zSchema.expression; 817 - zodSchema.typeName = zSchema.anyType; 818 848 819 849 if (plugin.config.metadata && schema.description) { 820 850 zodSchema.expression = compiler.callExpression({ ··· 892 922 state, 893 923 }); 894 924 zodSchema.expression = zSchema.expression; 895 - zodSchema.typeName = zSchema.anyType; 896 925 } 897 926 898 927 if (zodSchema.expression) { ··· 932 961 } 933 962 } 934 963 935 - if (state.hasCircularReference) { 936 - if (!zodSchema.typeName) { 937 - zodSchema.typeName = 'ZodTypeAny'; 938 - } 939 - } else { 940 - zodSchema.typeName = undefined; 941 - } 942 - 943 964 return zodSchema as ZodSchema; 944 965 }; 945 966 ··· 1010 1031 1011 1032 plugin.forEach('operation', 'parameter', 'requestBody', 'schema', (event) => { 1012 1033 if (event.type === 'operation') { 1013 - operationToZodSchema({ operation: event.operation, plugin }); 1034 + operationToZodSchema({ 1035 + getZodSchema: (schema) => { 1036 + const state: State = { 1037 + circularReferenceTracker: [], 1038 + hasCircularReference: false, 1039 + }; 1040 + return schemaToZodSchema({ plugin, schema, state }); 1041 + }, 1042 + operation: event.operation, 1043 + plugin, 1044 + }); 1014 1045 } else if (event.type === 'parameter') { 1015 1046 handleComponent({ 1016 1047 id: event.$ref,