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.

Merge pull request #2280 from hey-api/fix/zod-dates-offset

fix(zod): add dates.offset option

authored by

Lubos and committed by
GitHub
202acd78 b85f406e

+187 -23
+5
.changeset/wicked-tables-shout.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(zod): add `dates.offset` option
+19
packages/openapi-ts-tests/test/3.1.x.test.ts
··· 767 767 { 768 768 config: createConfig({ 769 769 input: 'validators.yaml', 770 + output: 'validators-dates', 771 + plugins: [ 772 + // Valibot doesn't allow configuring offset 773 + // { 774 + // name: 'valibot', 775 + // }, 776 + { 777 + dates: { 778 + offset: true, 779 + }, 780 + name: 'zod', 781 + }, 782 + ], 783 + }), 784 + description: 'generates validator schemas with any offset', 785 + }, 786 + { 787 + config: createConfig({ 788 + input: 'validators.yaml', 770 789 output: 'validators-metadata', 771 790 plugins: [ 772 791 {
+66
packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-dates/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 + }); 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}$/).optional(), 20 + bar: zBar.optional(), 21 + baz: z.array(z.lazy(() => { 22 + return zFoo; 23 + })).optional(), 24 + qux: z.number().int().gt(0).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 + export const zQux = z.record(z.object({ 32 + qux: z.string().optional() 33 + })); 34 + 35 + /** 36 + * This is Foo parameter. 37 + */ 38 + export const zFoo2 = z.string(); 39 + 40 + export const zFoo3 = z.object({ 41 + foo: zBar.optional() 42 + }); 43 + 44 + export const zPatchFooData = z.object({ 45 + body: z.object({ 46 + foo: z.string().optional() 47 + }), 48 + path: z.never().optional(), 49 + query: z.object({ 50 + foo: z.string().optional(), 51 + bar: zBar.optional(), 52 + baz: z.object({ 53 + baz: z.string().optional() 54 + }).optional(), 55 + qux: z.string().date().optional(), 56 + quux: z.string().datetime({ 57 + offset: true 58 + }).optional() 59 + }).optional() 60 + }); 61 + 62 + export const zPostFooData = z.object({ 63 + body: zFoo3, 64 + path: z.never().optional(), 65 + query: z.never().optional() 66 + });
+4 -1
packages/openapi-ts-tests/test/openapi-ts.config.ts
··· 37 37 // 'invalid', 38 38 // 'servers-entry.yaml', 39 39 // ), 40 - path: path.resolve(__dirname, 'spec', '3.1.x', 'full.yaml'), 40 + path: path.resolve(__dirname, 'spec', '3.1.x', 'validators.yaml'), 41 41 // path: path.resolve(__dirname, 'spec', 'v3-transforms.json'), 42 42 // path: path.resolve(__dirname, 'spec', 'v3.json'), 43 43 // path: 'http://localhost:4000/', ··· 206 206 { 207 207 // case: 'snake_case', 208 208 // comments: false, 209 + // dates: { 210 + // // offset: false, 211 + // }, 209 212 definitions: 'z{{name}}Definition', 210 213 // exportFromIndex: true, 211 214 // metadata: true,
+37 -22
packages/openapi-ts/src/config/utils.ts
··· 3 3 ? Record<string, any> 4 4 : Extract<T, Record<string, any>>; 5 5 6 + type MappersType<T> = { 7 + boolean: T extends boolean 8 + ? (value: boolean) => Partial<ObjectType<T>> 9 + : never; 10 + number: T extends number ? (value: number) => Partial<ObjectType<T>> : never; 11 + object?: (value: Partial<ObjectType<T>>) => Partial<ObjectType<T>>; 12 + string: T extends string ? (value: string) => Partial<ObjectType<T>> : never; 13 + } extends infer U 14 + ? { [K in keyof U as U[K] extends never ? never : K]: U[K] } 15 + : never; 16 + 17 + type IsObjectOnly<T> = T extends Record<string, any> | undefined 18 + ? Extract<T, string | boolean | number> extends never 19 + ? true 20 + : false 21 + : false; 22 + 6 23 export type ValueToObject = < 7 24 T extends undefined | string | boolean | number | Record<string, any>, 8 - >(args: { 9 - defaultValue: ObjectType<T>; 10 - mappers: { 11 - boolean: T extends boolean 12 - ? (value: boolean) => Partial<ObjectType<T>> 13 - : never; 14 - number: T extends number 15 - ? (value: number) => Partial<ObjectType<T>> 16 - : never; 17 - object?: (value: Partial<ObjectType<T>>) => Partial<ObjectType<T>>; 18 - string: T extends string 19 - ? (value: string) => Partial<ObjectType<T>> 20 - : never; 21 - } extends infer U 22 - ? { [K in keyof U as U[K] extends never ? never : K]: U[K] } 23 - : never; 24 - value: T; 25 - }) => ObjectType<T>; 25 + >( 26 + args: { 27 + defaultValue: ObjectType<T>; 28 + value: T; 29 + } & (IsObjectOnly<T> extends true 30 + ? { 31 + mappers?: MappersType<T>; 32 + } 33 + : { 34 + mappers: MappersType<T>; 35 + }), 36 + ) => ObjectType<T>; 26 37 27 38 const mergeResult = <T>( 28 39 result: ObjectType<T>, ··· 45 56 46 57 switch (typeof value) { 47 58 case 'boolean': 48 - if ('boolean' in mappers) { 59 + if (mappers && 'boolean' in mappers) { 49 60 const mapper = mappers.boolean as ( 50 61 value: boolean, 51 62 ) => Record<string, any>; ··· 53 64 } 54 65 break; 55 66 case 'number': 56 - if ('number' in mappers) { 67 + if (mappers && 'number' in mappers) { 57 68 const mapper = mappers.number as (value: number) => Record<string, any>; 58 69 result = mergeResult(result, mapper(value)); 59 70 } 60 71 break; 61 72 case 'string': 62 - if ('string' in mappers) { 73 + if (mappers && 'string' in mappers) { 63 74 const mapper = mappers.string as (value: string) => Record<string, any>; 64 75 result = mergeResult(result, mapper(value)); 65 76 } 66 77 break; 67 78 case 'object': 68 79 if (value !== null) { 69 - if ('object' in mappers && typeof mappers.object === 'function') { 80 + if ( 81 + mappers && 82 + 'object' in mappers && 83 + typeof mappers.object === 'function' 84 + ) { 70 85 const mapper = mappers.object as ( 71 86 value: Record<string, any>, 72 87 ) => Partial<ObjectType<any>>;
+7
packages/openapi-ts/src/plugins/zod/config.ts
··· 15 15 name: 'zod', 16 16 output: 'zod', 17 17 resolveConfig: (plugin, context) => { 18 + plugin.config.dates = context.valueToObject({ 19 + defaultValue: { 20 + offset: false, 21 + }, 22 + value: plugin.config.dates, 23 + }); 24 + 18 25 plugin.config.definitions = context.valueToObject({ 19 26 defaultValue: { 20 27 case: plugin.config.case ?? 'camelCase',
+15
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 455 455 }; 456 456 457 457 const stringTypeToZodSchema = ({ 458 + plugin, 458 459 schema, 459 460 }: { 461 + plugin: ZodPlugin['Instance']; 460 462 schema: SchemaWithType<'string'>; 461 463 }) => { 462 464 if (typeof schema.const === 'string') { ··· 485 487 expression: stringExpression, 486 488 name: compiler.identifier({ text: 'datetime' }), 487 489 }), 490 + parameters: plugin.config.dates.offset 491 + ? [ 492 + compiler.objectExpression({ 493 + obj: [ 494 + { 495 + key: 'offset', 496 + value: true, 497 + }, 498 + ], 499 + }), 500 + ] 501 + : [], 488 502 }); 489 503 break; 490 504 case 'ipv4': ··· 718 732 case 'string': 719 733 return { 720 734 expression: stringTypeToZodSchema({ 735 + plugin, 721 736 schema: schema as SchemaWithType<'string'>, 722 737 }), 723 738 };
+34
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 16 16 */ 17 17 comments?: boolean; 18 18 /** 19 + * Configuration for date handling in generated Zod schemas. 20 + * 21 + * Controls how date values are processed and validated using Zod's 22 + * date validation features. 23 + */ 24 + dates?: { 25 + /** 26 + * Whether to include timezone offset information when handling dates. 27 + * 28 + * When enabled, date strings will preserve timezone information. 29 + * When disabled, dates will be treated as local time. 30 + * 31 + * @default false 32 + */ 33 + offset?: boolean; 34 + }; 35 + /** 19 36 * Configuration for reusable schema definitions. 20 37 * 21 38 * Controls generation of shared Zod schemas that can be referenced across ··· 156 173 * @default true 157 174 */ 158 175 comments: boolean; 176 + /** 177 + * Configuration for date handling in generated Zod schemas. 178 + * 179 + * Controls how date values are processed and validated using Zod's 180 + * date validation features. 181 + */ 182 + dates: { 183 + /** 184 + * Whether to include timezone offset information when handling dates. 185 + * 186 + * When enabled, date strings will preserve timezone information. 187 + * When disabled, dates will be treated as local time. 188 + * 189 + * @default false 190 + */ 191 + offset: boolean; 192 + }; 159 193 /** 160 194 * Configuration for reusable schema definitions. 161 195 *