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

refactor: clean up zod resolvers

Lubos 34381cc4 edf99ed8

Changed files
+780 -517
dev
packages
+49 -31
dev/openapi-ts.config.ts
··· 444 // return $(v).attr('instance').call(big); 445 // }, 446 // object(ctx) { 447 - // const { $ } = ctx; 448 // const additional = ctx.nodes.additionalProperties(ctx); 449 - // const shape = ctx.nodes.shape(ctx); 450 // if (additional === undefined) { 451 - // return $('v').attr('looseObject').call(shape); 452 // } 453 - // return; 454 // }, 455 // string(ctx) { 456 - // const { $, schema } = ctx; 457 // if (schema.format === 'date' || schema.format === 'date-time') { 458 - // ctx.nodes.format = () => $('v').attr('isoDateTime').call(); 459 // } 460 // }, 461 // validator(ctx) { ··· 482 { 483 // case: 'snake_case', 484 // comments: false, 485 - compatibilityVersion: 3, 486 dates: { 487 // local: true, 488 // offset: true, ··· 531 }, 532 }, 533 '~resolvers': { 534 - object: { 535 - // base({ $, additional, shape }) { 536 - // if (!additional) { 537 - // // return $('z').attr('object').call(shape).attr('passthrough').call() 538 - // return $('z').attr('object').call(shape).attr('strict').call(); 539 - // } 540 - // return; 541 - // }, 542 }, 543 - string: { 544 - formats: { 545 - // date: ({ $ }) => $('z').attr('date').call(), 546 - // 'date-time': ({ $ }) => $('z').attr('date').call(), 547 - // int64: ({ $ }) => $('z').attr('string').call().attr('refine').call( 548 - // $.func().param('val').do( 549 - // $.try( 550 - // $('z').attr('int64').call().attr('parse').call($('BigInt').call('val')), 551 - // $.return($.literal(true)) 552 - // ).catch( 553 - // $.return($.literal(false)) 554 - // ), 555 - // ), 556 - // $.object().prop('message', $.literal('Must be a valid int64 string')) 557 - // ), 558 - }, 559 }, 560 // validator({ $, schema }) { 561 // return [
··· 444 // return $(v).attr('instance').call(big); 445 // }, 446 // object(ctx) { 447 + // const { $, symbols } = ctx; 448 + // const { v } = symbols; 449 // const additional = ctx.nodes.additionalProperties(ctx); 450 // if (additional === undefined) { 451 + // const shape = ctx.nodes.shape(ctx); 452 + // ctx.nodes.base = () => $(v).attr('looseObject').call(shape); 453 // } 454 // }, 455 // string(ctx) { 456 + // const { $, schema, symbols } = ctx; 457 + // const { v } = symbols; 458 // if (schema.format === 'date' || schema.format === 'date-time') { 459 + // ctx.nodes.format = () => $(v).attr('isoDateTime').call(); 460 // } 461 // }, 462 // validator(ctx) { ··· 483 { 484 // case: 'snake_case', 485 // comments: false, 486 + compatibilityVersion: 'mini', 487 dates: { 488 // local: true, 489 // offset: true, ··· 532 }, 533 }, 534 '~resolvers': { 535 + object(ctx) { 536 + const { $, symbols } = ctx; 537 + const { z } = symbols; 538 + const additional = ctx.nodes.additionalProperties(ctx); 539 + if (additional === undefined) { 540 + const shape = ctx.nodes.shape(ctx); 541 + // return $('z').attr('object').call(shape).attr('passthrough').call() 542 + ctx.nodes.base = () => 543 + $(z).attr('object').call(shape).attr('strict').call(); 544 + } 545 }, 546 + string(ctx) { 547 + const { $, schema, symbols } = ctx; 548 + const { z } = symbols; 549 + if (schema.format === 'date' || schema.format === 'date-time') { 550 + ctx.nodes.format = () => $(z).attr('date').call(); 551 + } 552 + if (schema.format === 'int64') { 553 + ctx.nodes.format = () => 554 + $(z) 555 + .attr('string') 556 + .call() 557 + .attr('refine') 558 + .call( 559 + $.func() 560 + .param('val') 561 + .do( 562 + $.try( 563 + $(z) 564 + .attr('int64') 565 + .call() 566 + .attr('parse') 567 + .call($('BigInt').call('val')), 568 + $.return($.literal(true)), 569 + ).catch($.return($.literal(false))), 570 + ), 571 + $.object().prop( 572 + 'message', 573 + $.literal('Must be a valid int64 string'), 574 + ), 575 + ); 576 + } 577 }, 578 // validator({ $, schema }) { 579 // return [
+11 -8
packages/openapi-ts/src/plugins/valibot/types.d.ts
··· 332 */ 333 pipes: PipesUtils & { 334 /** 335 - * The current builder state being processed by this resolver. 336 * 337 - * In Valibot, this represents the current list of call expressions ("pipes") 338 * being assembled to form a schema definition. 339 * 340 - * Each pipe can be extended, modified, or replaced to customize how the 341 - * resulting schema is constructed. 342 */ 343 current: Pipes; 344 }; 345 plugin: ValibotPlugin['Instance']; 346 /** 347 - * Provides access to commonly used symbols within the Valibot plugin. 348 */ 349 symbols: { 350 v: Symbol; ··· 416 export interface ValidatorResolverContext extends BaseResolverContext { 417 operation: IR.Operation; 418 /** 419 - * Provides access to commonly used symbols within the Valibot plugin. 420 */ 421 symbols: BaseResolverContext['symbols'] & { 422 schema: Symbol; ··· 424 } 425 426 type ValidatorResolver = ( 427 - args: ValidatorResolverContext, 428 ) => PipeResult | null | undefined; 429 430 type Resolvers = Plugin.Resolvers<{ ··· 435 * 436 * Returning `undefined` will execute the default resolver logic. 437 */ 438 - number?: (args: NumberResolverContext) => PipeResult | undefined; 439 /** 440 * Resolver for object schemas. 441 *
··· 332 */ 333 pipes: PipesUtils & { 334 /** 335 + * The current pipe. 336 * 337 + * In Valibot, this represents a list of call expressions ("pipes") 338 * being assembled to form a schema definition. 339 * 340 + * Each pipe can be extended, modified, or replaced to customize 341 + * the resulting schema. 342 */ 343 current: Pipes; 344 }; 345 + /** 346 + * The plugin instance. 347 + */ 348 plugin: ValibotPlugin['Instance']; 349 /** 350 + * Provides access to commonly used symbols within the plugin. 351 */ 352 symbols: { 353 v: Symbol; ··· 419 export interface ValidatorResolverContext extends BaseResolverContext { 420 operation: IR.Operation; 421 /** 422 + * Provides access to commonly used symbols within the plugin. 423 */ 424 symbols: BaseResolverContext['symbols'] & { 425 schema: Symbol; ··· 427 } 428 429 type ValidatorResolver = ( 430 + ctx: ValidatorResolverContext, 431 ) => PipeResult | null | undefined; 432 433 type Resolvers = Plugin.Resolvers<{ ··· 438 * 439 * Returning `undefined` will execute the default resolver logic. 440 */ 441 + number?: (ctx: NumberResolverContext) => PipeResult | undefined; 442 /** 443 * Resolver for object schemas. 444 *
+2 -2
packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts
··· 13 } 14 15 function constNode(ctx: StringResolverContext): PipeResult | undefined { 16 - const { schema } = ctx; 17 - const { v } = ctx.symbols; 18 if (typeof schema.const !== 'string') return; 19 return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)); 20 }
··· 13 } 14 15 function constNode(ctx: StringResolverContext): PipeResult | undefined { 16 + const { schema, symbols } = ctx; 17 + const { v } = symbols; 18 if (typeof schema.const !== 'string') return; 19 return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)); 20 }
+27 -23
packages/openapi-ts/src/plugins/zod/mini/api.ts
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 - import type { ValidatorResolverArgs } from '../types'; 6 7 - const validatorResolver = ({ 8 - schema, 9 - }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 - $(schema).attr(identifiers.parseAsync).call('data').await().return(); 11 12 export const createRequestValidatorMini = ({ 13 operation, ··· 22 }); 23 if (!symbol) return; 24 25 - const z = plugin.referenceSymbol({ 26 - category: 'external', 27 - resource: 'zod.z', 28 - }); 29 - const args: ValidatorResolverArgs = { 30 $, 31 - chain: undefined, 32 operation, 33 plugin, 34 - schema: symbol, 35 - z, 36 }; 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 - const statements = candidate?.(args); 43 if (statements === null) return; 44 if (statements !== undefined) { 45 return $.func() ··· 64 }); 65 if (!symbol) return; 66 67 - const z = plugin.referenceSymbol({ 68 - category: 'external', 69 - resource: 'zod.z', 70 - }); 71 - const args: ValidatorResolverArgs = { 72 $, 73 - chain: undefined, 74 operation, 75 plugin, 76 - schema: symbol, 77 - z, 78 }; 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 - const statements = candidate?.(args); 85 if (statements === null) return; 86 if (statements !== undefined) { 87 return $.func()
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 + import type { ValidatorResolverContext } from '../types'; 6 7 + const validatorResolver = ( 8 + ctx: ValidatorResolverContext, 9 + ): ReturnType<typeof $.return> => { 10 + const { schema } = ctx.symbols; 11 + return $(schema).attr(identifiers.parseAsync).call('data').await().return(); 12 + }; 13 14 export const createRequestValidatorMini = ({ 15 operation, ··· 24 }); 25 if (!symbol) return; 26 27 + const z = plugin.external('zod.z'); 28 + const ctx: ValidatorResolverContext = { 29 $, 30 + chain: { 31 + current: $(z), 32 + }, 33 operation, 34 plugin, 35 + symbols: { 36 + schema: symbol, 37 + z, 38 + }, 39 }; 40 const validator = plugin.config['~resolvers']?.validator; 41 const resolver = 42 typeof validator === 'function' ? validator : validator?.request; 43 const candidates = [resolver, validatorResolver]; 44 for (const candidate of candidates) { 45 + const statements = candidate?.(ctx); 46 if (statements === null) return; 47 if (statements !== undefined) { 48 return $.func() ··· 67 }); 68 if (!symbol) return; 69 70 + const z = plugin.external('zod.z'); 71 + const ctx: ValidatorResolverContext = { 72 $, 73 + chain: { 74 + current: $(z), 75 + }, 76 operation, 77 plugin, 78 + symbols: { 79 + schema: symbol, 80 + z, 81 + }, 82 }; 83 const validator = plugin.config['~resolvers']?.validator; 84 const resolver = 85 typeof validator === 'function' ? validator : validator?.response; 86 const candidates = [resolver, validatorResolver]; 87 for (const candidate of candidates) { 88 + const statements = candidate?.(ctx); 89 if (statements === null) return; 90 if (statements !== undefined) { 91 return $.func()
+2 -2
packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 - import { stringToAst } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 64 ...args, 65 schema: { ...schema, type: 'number' }, 66 }) 67 - : stringToAst({ 68 ...args, 69 schema: schema as SchemaWithType<'string'>, 70 });
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 + import { stringToNode } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 64 ...args, 65 schema: { ...schema, type: 'number' }, 66 }) 67 + : stringToNode({ 68 ...args, 69 schema: schema as SchemaWithType<'string'>, 70 });
+2 -1
packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts
··· 7 import { $ } from '~/ts-dsl'; 8 9 import { identifiers } from '../../constants'; 10 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 11 12 export const numberToAst = ({ ··· 41 } 42 } 43 44 - const checks: Array<ReturnType<typeof $.call>> = []; 45 46 let hasLowerBound = false; 47 let hasUpperBound = false;
··· 7 import { $ } from '~/ts-dsl'; 8 9 import { identifiers } from '../../constants'; 10 + import type { Chain } from '../../shared/chain'; 11 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 12 13 export const numberToAst = ({ ··· 42 } 43 } 44 45 + const checks: Array<Chain> = []; 46 47 let hasLowerBound = false; 48 let hasUpperBound = false;
+72 -55
packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 - import type { ObjectBaseResolverArgs } from '../../types'; 9 import { irSchemaToAst } from '../plugin'; 10 11 - function defaultBaseResolver({ 12 - additional, 13 - shape, 14 - z, 15 - }: ObjectBaseResolverArgs): ReturnType<typeof $.call> { 16 if (additional) { 17 return $(z) 18 .attr(identifiers.record) ··· 22 return $(z).attr(identifiers.object).call(shape); 23 } 24 25 - export const objectToAst = ({ 26 - plugin, 27 - schema, 28 - state, 29 - }: IrSchemaToAstOptions & { 30 - schema: SchemaWithType<'object'>; 31 - }): Omit<Ast, 'typeName'> => { 32 - const z = plugin.referenceSymbol({ 33 - category: 'external', 34 - resource: 'zod.z', 35 - }); 36 - 37 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 38 - 39 // TODO: parser - handle constants 40 41 const shape = $.object().pretty(); 42 - const required = schema.required ?? []; 43 44 for (const name in schema.properties) { 45 const property = schema.properties[name]!; 46 - const isRequired = required.includes(name); 47 48 const propertyAst = irSchemaToAst({ 49 - optional: !isRequired, 50 plugin, 51 schema: property, 52 state: { 53 - ...state, 54 - path: ref([...fromRef(state.path), 'properties', name]), 55 }, 56 }); 57 if (propertyAst.hasLazyExpression) { 58 - result.hasLazyExpression = true; 59 - } 60 - 61 - if (propertyAst.hasLazyExpression) { 62 shape.getter(name, propertyAst.expression.return()); 63 } else { 64 shape.prop(name, propertyAst.expression); 65 } 66 } 67 68 - let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined; 69 - if ( 70 - schema.additionalProperties && 71 - (!schema.properties || !Object.keys(schema.properties).length) 72 - ) { 73 - const additionalAst = irSchemaToAst({ 74 - plugin, 75 - schema: schema.additionalProperties, 76 - state: { 77 - ...state, 78 - path: ref([...fromRef(state.path), 'additionalProperties']), 79 - }, 80 - }); 81 - if (additionalAst.hasLazyExpression) result.hasLazyExpression = true; 82 - additional = additionalAst.expression; 83 - } 84 85 - const args: ObjectBaseResolverArgs = { 86 $, 87 - additional, 88 - chain: undefined, 89 plugin, 90 schema, 91 - shape, 92 - z, 93 }; 94 - const resolver = plugin.config['~resolvers']?.object?.base; 95 - const chain = resolver?.(args) ?? defaultBaseResolver(args); 96 - result.expression = chain; 97 - 98 - return result as Omit<Ast, 'typeName'>; 99 };
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 + import type { Chain } from '../../shared/chain'; 8 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 9 + import type { ObjectResolverContext } from '../../types'; 10 import { irSchemaToAst } from '../plugin'; 11 12 + function additionalPropertiesNode( 13 + ctx: ObjectResolverContext, 14 + ): Chain | null | undefined { 15 + const { plugin, schema } = ctx; 16 + 17 + if ( 18 + !schema.additionalProperties || 19 + (schema.properties && Object.keys(schema.properties).length > 0) 20 + ) 21 + return; 22 + 23 + const additionalAst = irSchemaToAst({ 24 + plugin, 25 + schema: schema.additionalProperties, 26 + state: { 27 + ...ctx.utils.state, 28 + path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']), 29 + }, 30 + }); 31 + if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; 32 + return additionalAst.expression; 33 + } 34 + 35 + function baseNode(ctx: ObjectResolverContext): Chain { 36 + const { nodes, symbols } = ctx; 37 + const { z } = symbols; 38 + 39 + const additional = nodes.additionalProperties(ctx); 40 + const shape = nodes.shape(ctx); 41 + 42 if (additional) { 43 return $(z) 44 .attr(identifiers.record) ··· 48 return $(z).attr(identifiers.object).call(shape); 49 } 50 51 + function objectResolver(ctx: ObjectResolverContext): Chain { 52 // TODO: parser - handle constants 53 + return ctx.nodes.base(ctx); 54 + } 55 56 + function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> { 57 + const { plugin, schema } = ctx; 58 const shape = $.object().pretty(); 59 60 for (const name in schema.properties) { 61 const property = schema.properties[name]!; 62 63 const propertyAst = irSchemaToAst({ 64 + optional: !schema.required?.includes(name), 65 plugin, 66 schema: property, 67 state: { 68 + ...ctx.utils.state, 69 + path: ref([...fromRef(ctx.utils.state.path), 'properties', name]), 70 }, 71 }); 72 if (propertyAst.hasLazyExpression) { 73 + ctx.utils.ast.hasLazyExpression = true; 74 shape.getter(name, propertyAst.expression.return()); 75 } else { 76 shape.prop(name, propertyAst.expression); 77 } 78 } 79 80 + return shape; 81 + } 82 83 + export const objectToAst = ({ 84 + plugin, 85 + schema, 86 + state, 87 + }: IrSchemaToAstOptions & { 88 + schema: SchemaWithType<'object'>; 89 + }): Omit<Ast, 'typeName'> => { 90 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 91 + const z = plugin.external('zod.z'); 92 + const ctx: ObjectResolverContext = { 93 $, 94 + chain: { 95 + current: $(z), 96 + }, 97 + nodes: { 98 + additionalProperties: additionalPropertiesNode, 99 + base: baseNode, 100 + shape: shapeNode, 101 + }, 102 plugin, 103 schema, 104 + symbols: { 105 + z, 106 + }, 107 + utils: { 108 + ast, 109 + state, 110 + }, 111 }; 112 + const resolver = plugin.config['~resolvers']?.object; 113 + const node = resolver?.(ctx) ?? objectResolver(ctx); 114 + ast.expression = node; 115 + return ast as Omit<Ast, 'typeName'>; 116 };
+104 -57
packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 6 - import type { FormatResolverArgs } from '../../types'; 7 8 - const defaultFormatResolver = ({ 9 - chain, 10 - plugin, 11 - schema, 12 - }: FormatResolverArgs): ReturnType<typeof $.call> => { 13 - const z = plugin.referenceSymbol({ 14 - category: 'external', 15 - resource: 'zod.z', 16 - }); 17 18 switch (schema.format) { 19 case 'date': ··· 43 return $(z).attr(identifiers.url).call(); 44 case 'uuid': 45 return $(z).attr(identifiers.uuid).call(); 46 - default: 47 - return chain; 48 } 49 - }; 50 51 - export const stringToAst = ({ 52 - plugin, 53 - schema, 54 - }: IrSchemaToAstOptions & { 55 - schema: SchemaWithType<'string'>; 56 - }): Omit<Ast, 'typeName'> => { 57 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 58 - let chain: ReturnType<typeof $.call>; 59 60 - const z = plugin.referenceSymbol({ 61 - category: 'external', 62 - resource: 'zod.z', 63 - }); 64 65 - if (typeof schema.const === 'string') { 66 - chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); 67 - result.expression = chain; 68 - return result as Omit<Ast, 'typeName'>; 69 } 70 71 - chain = $(z).attr(identifiers.string).call(); 72 73 - if (schema.format) { 74 - const args: FormatResolverArgs = { $, chain, plugin, schema, z }; 75 - const resolver = 76 - plugin.config['~resolvers']?.string?.formats?.[schema.format]; 77 - chain = resolver?.(args) ?? defaultFormatResolver(args); 78 - } 79 80 - const checks: Array<ReturnType<typeof $.call>> = []; 81 82 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 83 - checks.push( 84 - $(z).attr(identifiers.length).call($.literal(schema.minLength)), 85 - ); 86 } else { 87 - if (schema.minLength !== undefined) { 88 - checks.push( 89 - $(z).attr(identifiers.minLength).call($.literal(schema.minLength)), 90 - ); 91 - } 92 93 - if (schema.maxLength !== undefined) { 94 - checks.push( 95 - $(z).attr(identifiers.maxLength).call($.literal(schema.maxLength)), 96 - ); 97 - } 98 } 99 100 - if (schema.pattern) { 101 - checks.push($(z).attr(identifiers.regex).call($.regexp(schema.pattern))); 102 - } 103 104 if (checks.length) { 105 - chain = chain.attr(identifiers.check).call(...checks); 106 } 107 108 - result.expression = chain; 109 - return result as Omit<Ast, 'typeName'>; 110 };
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 + import type { Chain } from '../../shared/chain'; 6 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import type { StringResolverContext } from '../../types'; 8 + 9 + function baseNode(ctx: StringResolverContext): Chain { 10 + const { z } = ctx.symbols; 11 + return $(z).attr(identifiers.string).call(); 12 + } 13 + 14 + function constNode(ctx: StringResolverContext): Chain | undefined { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (typeof schema.const !== 'string') return; 18 + return $(z).attr(identifiers.literal).call($.literal(schema.const)); 19 + } 20 21 + function formatNode(ctx: StringResolverContext): Chain | undefined { 22 + const { plugin, schema, symbols } = ctx; 23 + const { z } = symbols; 24 25 switch (schema.format) { 26 case 'date': ··· 50 return $(z).attr(identifiers.url).call(); 51 case 'uuid': 52 return $(z).attr(identifiers.uuid).call(); 53 } 54 + 55 + return; 56 + } 57 + 58 + function lengthNode(ctx: StringResolverContext): Chain | undefined { 59 + const { schema, symbols } = ctx; 60 + const { z } = symbols; 61 + if (schema.minLength === undefined || schema.minLength !== schema.maxLength) 62 + return; 63 + return $(z).attr(identifiers.length).call($.literal(schema.minLength)); 64 + } 65 + 66 + function maxLengthNode(ctx: StringResolverContext): Chain | undefined { 67 + const { schema, symbols } = ctx; 68 + const { z } = symbols; 69 + if (schema.maxLength === undefined) return; 70 + return $(z).attr(identifiers.maxLength).call($.literal(schema.maxLength)); 71 + } 72 73 + function minLengthNode(ctx: StringResolverContext): Chain | undefined { 74 + const { schema, symbols } = ctx; 75 + const { z } = symbols; 76 + if (schema.minLength === undefined) return; 77 + return $(z).attr(identifiers.minLength).call($.literal(schema.minLength)); 78 + } 79 80 + function patternNode(ctx: StringResolverContext): Chain | undefined { 81 + const { schema, symbols } = ctx; 82 + const { z } = symbols; 83 + if (!schema.pattern) return; 84 + return $(z).attr(identifiers.regex).call($.regexp(schema.pattern)); 85 + } 86 87 + function stringResolver(ctx: StringResolverContext): Chain { 88 + const constNode = ctx.nodes.const(ctx); 89 + if (constNode) { 90 + ctx.chain.current = constNode; 91 + return ctx.chain.current; 92 } 93 94 + const baseNode = ctx.nodes.base(ctx); 95 + if (baseNode) ctx.chain.current = baseNode; 96 97 + const formatNode = ctx.nodes.format(ctx); 98 + if (formatNode) ctx.chain.current = formatNode; 99 100 + const checks: Array<Chain> = []; 101 102 + const lengthNode = ctx.nodes.length(ctx); 103 + if (lengthNode) { 104 + checks.push(lengthNode); 105 } else { 106 + const minLengthNode = ctx.nodes.minLength(ctx); 107 + if (minLengthNode) checks.push(minLengthNode); 108 109 + const maxLengthNode = ctx.nodes.maxLength(ctx); 110 + if (maxLengthNode) checks.push(maxLengthNode); 111 } 112 113 + const patternNode = ctx.nodes.pattern(ctx); 114 + if (patternNode) checks.push(patternNode); 115 116 if (checks.length) { 117 + ctx.chain.current = ctx.chain.current 118 + .attr(identifiers.check) 119 + .call(...checks); 120 } 121 122 + return ctx.chain.current; 123 + } 124 + 125 + export const stringToNode = ({ 126 + plugin, 127 + schema, 128 + }: IrSchemaToAstOptions & { 129 + schema: SchemaWithType<'string'>; 130 + }): Omit<Ast, 'typeName'> => { 131 + const z = plugin.external('zod.z'); 132 + const ctx: StringResolverContext = { 133 + $, 134 + chain: { 135 + current: $(z), 136 + }, 137 + nodes: { 138 + base: baseNode, 139 + const: constNode, 140 + format: formatNode, 141 + length: lengthNode, 142 + maxLength: maxLengthNode, 143 + minLength: minLengthNode, 144 + pattern: patternNode, 145 + }, 146 + plugin, 147 + schema, 148 + symbols: { 149 + z, 150 + }, 151 + }; 152 + const resolver = plugin.config['~resolvers']?.string; 153 + const node = resolver?.(ctx) ?? stringResolver(ctx); 154 + return { 155 + expression: node, 156 + }; 157 };
+3
packages/openapi-ts/src/plugins/zod/shared/chain.ts
···
··· 1 + import type { $ } from '~/ts-dsl'; 2 + 3 + export type Chain = ReturnType<typeof $.call | typeof $.expr>;
+94 -74
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 1 - import type { Symbol } from '@hey-api/codegen-core'; 2 import type ts from 'typescript'; 3 4 import type { IR } from '~/ir/types'; 5 - import type { DefinePlugin, Plugin } from '~/plugins'; 6 import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl'; 7 import type { StringCase, StringName } from '~/types/case'; 8 import type { MaybeArray } from '~/types/utils'; 9 10 import type { IApi } from './api'; 11 12 export type UserConfig = Plugin.Name<'zod'> & 13 Plugin.Hooks & ··· 747 }; 748 }; 749 750 - type SharedResolverArgs = DollarTsDsl & { 751 /** 752 - * The current fluent builder chain under construction for this resolver. 753 - * 754 - * Represents the in-progress call sequence (e.g., a Zod or DSL chain) 755 - * that defines the current schema or expression being generated. 756 - * 757 - * This chain can be extended, transformed, or replaced entirely to customize 758 - * the resulting output of the resolver. 759 */ 760 - chain?: ReturnType<typeof $.call>; 761 plugin: ZodPlugin['Instance']; 762 - z: Symbol; 763 - }; 764 765 - export type FormatResolverArgs = Required<SharedResolverArgs> & { 766 - schema: IR.SchemaObject; 767 - }; 768 - 769 - export type ObjectBaseResolverArgs = SharedResolverArgs & { 770 - /** Null = never */ 771 - additional?: ReturnType<typeof $.call | typeof $.expr> | null; 772 - schema: IR.SchemaObject; 773 - shape: ReturnType<typeof $.object>; 774 - }; 775 776 type ResolverResult = boolean | number; 777 778 - export type ValidatorResolverArgs = SharedResolverArgs & { 779 operation: IR.Operation; 780 - schema: Symbol; 781 - }; 782 783 type ValidatorResolver = ( 784 - args: ValidatorResolverArgs, 785 ) => MaybeArray<TsDsl<ts.Statement>> | null | undefined; 786 787 type Resolvers = Plugin.Resolvers<{ 788 /** 789 - * Resolvers for number schemas. 790 * 791 - * Allows customization of how number types are rendered, including 792 - * per-format handling. 793 */ 794 number?: { 795 /** ··· 797 * 798 * Returning `undefined` will execute the default resolver logic. 799 */ 800 - base?: (args: FormatResolverArgs) => ResolverResult | undefined; 801 /** 802 * Resolvers for number formats (e.g., `float`, `double`, `int32`). 803 * ··· 811 */ 812 formats?: Record< 813 string, 814 - (args: FormatResolverArgs) => ResolverResult | undefined 815 >; 816 }; 817 /** 818 - * Resolvers for object schemas. 819 * 820 * Allows customization of how object types are rendered. 821 * 822 - * Example path: `~resolvers.object.base` 823 - * 824 - * Returning `undefined` from a resolver will apply the default 825 - * generation behavior for the object schema. 826 */ 827 - object?: { 828 - /** 829 - * Controls how object schemas are constructed. 830 - * 831 - * Called with the fully assembled shape (properties) and any additional 832 - * property schema, allowing the resolver to choose the correct Zod 833 - * base constructor and modify the schema chain if needed. 834 - * 835 - * Returning `undefined` will execute the default resolver logic. 836 - */ 837 - base?: ( 838 - args: ObjectBaseResolverArgs, 839 - ) => ReturnType<typeof $.call> | undefined; 840 - }; 841 /** 842 - * Resolvers for string schemas. 843 * 844 - * Allows customization of how string types are rendered, including 845 - * per-format handling. 846 */ 847 - string?: { 848 - /** 849 - * Resolvers for string formats (e.g., `uuid`, `email`, `date-time`). 850 - * 851 - * Each key represents a specific format name with a custom 852 - * resolver function that controls how that format is rendered. 853 - * 854 - * Example path: `~resolvers.string.formats.uuid` 855 - * 856 - * Returning `undefined` from a resolver will apply the default 857 - * generation logic for that format. 858 - */ 859 - formats?: Record< 860 - string, 861 - (args: FormatResolverArgs) => ReturnType<typeof $.call> | undefined 862 - >; 863 - }; 864 /** 865 * Resolvers for request and response validators. 866 * ··· 868 * 869 * Example path: `~resolvers.validator.request` or `~resolvers.validator.response` 870 * 871 - * Returning `undefined` from a resolver will apply the default generation logic. 872 */ 873 validator?: 874 | ValidatorResolver ··· 876 /** 877 * Controls how the request validator function body is generated. 878 * 879 - * Returning `undefined` will fall back to the default `.await().return()` logic. 880 */ 881 request?: ValidatorResolver; 882 /** 883 * Controls how the response validator function body is generated. 884 * 885 - * Returning `undefined` will fall back to the default `.await().return()` logic. 886 */ 887 response?: ValidatorResolver; 888 };
··· 1 + import type { Refs, Symbol } from '@hey-api/codegen-core'; 2 import type ts from 'typescript'; 3 4 import type { IR } from '~/ir/types'; 5 + import type { DefinePlugin, Plugin, SchemaWithType } from '~/plugins'; 6 import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl'; 7 import type { StringCase, StringName } from '~/types/case'; 8 import type { MaybeArray } from '~/types/utils'; 9 10 import type { IApi } from './api'; 11 + import type { Chain } from './shared/chain'; 12 + import type { Ast, PluginState } from './shared/types'; 13 14 export type UserConfig = Plugin.Name<'zod'> & 15 Plugin.Hooks & ··· 749 }; 750 }; 751 752 + interface BaseResolverContext extends DollarTsDsl { 753 + /** 754 + * Functions for working with chains. 755 + */ 756 + chain: { 757 + /** 758 + * The current chain. 759 + * 760 + * In Zod, this represents a chain of call expressions ("chains") 761 + * being assembled to form a schema definition. 762 + * 763 + * Each chain can be extended, modified, or replaced to customize 764 + * the resulting schema. 765 + */ 766 + current: Chain; 767 + }; 768 /** 769 + * The plugin instance. 770 */ 771 plugin: ZodPlugin['Instance']; 772 + /** 773 + * Provides access to commonly used symbols within the plugin. 774 + */ 775 + symbols: { 776 + z: Symbol; 777 + }; 778 + } 779 780 + export interface ObjectResolverContext extends BaseResolverContext { 781 + /** 782 + * Nodes used to build different parts of the object schema. 783 + */ 784 + nodes: { 785 + /** 786 + * If `additionalProperties` is `false` or `{ type: 'never' }`, returns `null` 787 + * to indicate no additional properties are allowed. 788 + */ 789 + additionalProperties: ( 790 + ctx: ObjectResolverContext, 791 + ) => Chain | null | undefined; 792 + base: (ctx: ObjectResolverContext) => Chain; 793 + shape: (ctx: ObjectResolverContext) => ReturnType<typeof $.object>; 794 + }; 795 + schema: SchemaWithType<'object'>; 796 + /** 797 + * Utility functions for object schema processing. 798 + */ 799 + utils: { 800 + ast: Partial<Omit<Ast, 'typeName'>>; 801 + state: Refs<PluginState>; 802 + }; 803 + } 804 805 type ResolverResult = boolean | number; 806 807 + export interface StringResolverContext extends BaseResolverContext { 808 + /** 809 + * Nodes used to build different parts of the string schema. 810 + */ 811 + nodes: { 812 + base: (ctx: StringResolverContext) => Chain; 813 + const: (ctx: StringResolverContext) => Chain | undefined; 814 + format: (ctx: StringResolverContext) => Chain | undefined; 815 + length: (ctx: StringResolverContext) => Chain | undefined; 816 + maxLength: (ctx: StringResolverContext) => Chain | undefined; 817 + minLength: (ctx: StringResolverContext) => Chain | undefined; 818 + pattern: (ctx: StringResolverContext) => Chain | undefined; 819 + }; 820 + schema: SchemaWithType<'string'>; 821 + } 822 + 823 + export interface ValidatorResolverContext extends BaseResolverContext { 824 operation: IR.Operation; 825 + /** 826 + * Provides access to commonly used symbols within the plugin. 827 + */ 828 + symbols: BaseResolverContext['symbols'] & { 829 + schema: Symbol; 830 + }; 831 + } 832 833 type ValidatorResolver = ( 834 + ctx: ValidatorResolverContext, 835 ) => MaybeArray<TsDsl<ts.Statement>> | null | undefined; 836 837 type Resolvers = Plugin.Resolvers<{ 838 /** 839 + * Resolver for number schemas. 840 + * 841 + * Allows customization of how number types are rendered. 842 * 843 + * Returning `undefined` will execute the default resolver logic. 844 */ 845 number?: { 846 /** ··· 848 * 849 * Returning `undefined` will execute the default resolver logic. 850 */ 851 + base?: (ctx: StringResolverContext) => ResolverResult | undefined; 852 /** 853 * Resolvers for number formats (e.g., `float`, `double`, `int32`). 854 * ··· 862 */ 863 formats?: Record< 864 string, 865 + (ctx: StringResolverContext) => ResolverResult | undefined 866 >; 867 }; 868 /** 869 + * Resolver for object schemas. 870 * 871 * Allows customization of how object types are rendered. 872 * 873 + * Returning `undefined` will execute the default resolver logic. 874 */ 875 + object?: (ctx: ObjectResolverContext) => Chain | undefined; 876 /** 877 + * Resolver for string schemas. 878 + * 879 + * Allows customization of how string types are rendered. 880 * 881 + * Returning `undefined` will execute the default resolver logic. 882 */ 883 + string?: (ctx: StringResolverContext) => Chain | undefined; 884 /** 885 * Resolvers for request and response validators. 886 * ··· 888 * 889 * Example path: `~resolvers.validator.request` or `~resolvers.validator.response` 890 * 891 + * Returning `undefined` will execute the default resolver logic. 892 */ 893 validator?: 894 | ValidatorResolver ··· 896 /** 897 * Controls how the request validator function body is generated. 898 * 899 + * Returning `undefined` will execute the default resolver logic. 900 */ 901 request?: ValidatorResolver; 902 /** 903 * Controls how the response validator function body is generated. 904 * 905 + * Returning `undefined` will execute the default resolver logic. 906 */ 907 response?: ValidatorResolver; 908 };
+27 -23
packages/openapi-ts/src/plugins/zod/v3/api.ts
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 - import type { ValidatorResolverArgs } from '../types'; 6 7 - const validatorResolver = ({ 8 - schema, 9 - }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 - $(schema).attr(identifiers.parseAsync).call('data').await().return(); 11 12 export const createRequestValidatorV3 = ({ 13 operation, ··· 22 }); 23 if (!symbol) return; 24 25 - const z = plugin.referenceSymbol({ 26 - category: 'external', 27 - resource: 'zod.z', 28 - }); 29 - const args: ValidatorResolverArgs = { 30 $, 31 - chain: undefined, 32 operation, 33 plugin, 34 - schema: symbol, 35 - z, 36 }; 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 - const statements = candidate?.(args); 43 if (statements === null) return; 44 if (statements !== undefined) { 45 return $.func() ··· 64 }); 65 if (!symbol) return; 66 67 - const z = plugin.referenceSymbol({ 68 - category: 'external', 69 - resource: 'zod.z', 70 - }); 71 - const args: ValidatorResolverArgs = { 72 $, 73 - chain: undefined, 74 operation, 75 plugin, 76 - schema: symbol, 77 - z, 78 }; 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 - const statements = candidate?.(args); 85 if (statements === null) return; 86 if (statements !== undefined) { 87 return $.func()
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 + import type { ValidatorResolverContext } from '../types'; 6 7 + const validatorResolver = ( 8 + ctx: ValidatorResolverContext, 9 + ): ReturnType<typeof $.return> => { 10 + const { schema } = ctx.symbols; 11 + return $(schema).attr(identifiers.parseAsync).call('data').await().return(); 12 + }; 13 14 export const createRequestValidatorV3 = ({ 15 operation, ··· 24 }); 25 if (!symbol) return; 26 27 + const z = plugin.external('zod.z'); 28 + const ctx: ValidatorResolverContext = { 29 $, 30 + chain: { 31 + current: $(z), 32 + }, 33 operation, 34 plugin, 35 + symbols: { 36 + schema: symbol, 37 + z, 38 + }, 39 }; 40 const validator = plugin.config['~resolvers']?.validator; 41 const resolver = 42 typeof validator === 'function' ? validator : validator?.request; 43 const candidates = [resolver, validatorResolver]; 44 for (const candidate of candidates) { 45 + const statements = candidate?.(ctx); 46 if (statements === null) return; 47 if (statements !== undefined) { 48 return $.func() ··· 67 }); 68 if (!symbol) return; 69 70 + const z = plugin.external('zod.z'); 71 + const ctx: ValidatorResolverContext = { 72 $, 73 + chain: { 74 + current: $(z), 75 + }, 76 operation, 77 plugin, 78 + symbols: { 79 + schema: symbol, 80 + z, 81 + }, 82 }; 83 const validator = plugin.config['~resolvers']?.validator; 84 const resolver = 85 typeof validator === 'function' ? validator : validator?.response; 86 const candidates = [resolver, validatorResolver]; 87 for (const candidate of candidates) { 88 + const statements = candidate?.(ctx); 89 if (statements === null) return; 90 if (statements !== undefined) { 91 return $.func()
+2 -2
packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 - import { stringToAst } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 77 ...args, 78 schema: { ...schema, type: 'number' }, 79 }) 80 - : stringToAst({ 81 ...args, 82 schema: schema as SchemaWithType<'string'>, 83 }),
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 + import { stringToNode } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 77 ...args, 78 schema: { ...schema, type: 'number' }, 79 }) 80 + : stringToNode({ 81 ...args, 82 schema: schema as SchemaWithType<'string'>, 83 }),
+2 -1
packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts
··· 7 import { $ } from '~/ts-dsl'; 8 9 import { identifiers } from '../../constants'; 10 import type { IrSchemaToAstOptions } from '../../shared/types'; 11 12 export const numberToAst = ({ ··· 27 return expression; 28 } 29 30 - let numberExpression: ReturnType<typeof $.call>; 31 32 if (shouldCoerceToBigInt(schema.format)) { 33 numberExpression = $(z)
··· 7 import { $ } from '~/ts-dsl'; 8 9 import { identifiers } from '../../constants'; 10 + import type { Chain } from '../../shared/chain'; 11 import type { IrSchemaToAstOptions } from '../../shared/types'; 12 13 export const numberToAst = ({ ··· 28 return expression; 29 } 30 31 + let numberExpression: Chain; 32 33 if (shouldCoerceToBigInt(schema.format)) { 34 numberExpression = $(z)
+78 -60
packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 - import type { ObjectBaseResolverArgs } from '../../types'; 9 import { irSchemaToAst } from '../plugin'; 10 11 - function defaultBaseResolver({ 12 - additional, 13 - shape, 14 - z, 15 - }: ObjectBaseResolverArgs): ReturnType<typeof $.call> { 16 if (additional) { 17 return $(z).attr(identifiers.record).call(additional); 18 } ··· 20 return $(z).attr(identifiers.object).call(shape); 21 } 22 23 - export const objectToAst = ({ 24 - plugin, 25 - schema, 26 - state, 27 - }: IrSchemaToAstOptions & { 28 - schema: SchemaWithType<'object'>; 29 - }): Omit<Ast, 'typeName'> & { 30 - anyType?: string; 31 - } => { 32 - const z = plugin.referenceSymbol({ 33 - category: 'external', 34 - resource: 'zod.z', 35 - }); 36 - 37 - let hasLazyExpression = false; 38 - 39 // TODO: parser - handle constants 40 41 const shape = $.object().pretty(); 42 - const required = schema.required ?? []; 43 44 for (const name in schema.properties) { 45 const property = schema.properties[name]!; 46 - const isRequired = required.includes(name); 47 48 - const propertyExpression = irSchemaToAst({ 49 - optional: !isRequired, 50 plugin, 51 schema: property, 52 state: { 53 - ...state, 54 - path: ref([...fromRef(state.path), 'properties', name]), 55 }, 56 }); 57 - 58 - if (propertyExpression.hasLazyExpression) hasLazyExpression = true; 59 - 60 - shape.prop(name, propertyExpression.expression); 61 } 62 63 - let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined; 64 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 65 - if ( 66 - schema.additionalProperties && 67 - (!schema.properties || !Object.keys(schema.properties).length) 68 - ) { 69 - const additionalAst = irSchemaToAst({ 70 - plugin, 71 - schema: schema.additionalProperties, 72 - state: { 73 - ...state, 74 - path: ref([...fromRef(state.path), 'additionalProperties']), 75 - }, 76 - }); 77 - hasLazyExpression = additionalAst.hasLazyExpression || hasLazyExpression; 78 - additional = additionalAst.expression; 79 - } 80 81 - const args: ObjectBaseResolverArgs = { 82 $, 83 - additional, 84 - chain: undefined, 85 plugin, 86 schema, 87 - shape, 88 - z, 89 }; 90 - const resolver = plugin.config['~resolvers']?.object?.base; 91 - const chain = resolver?.(args) ?? defaultBaseResolver(args); 92 - result.expression = chain; 93 - 94 return { 95 anyType: 'AnyZodObject', 96 - expression: result.expression!, 97 - hasLazyExpression, 98 }; 99 };
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 + import type { Chain } from '../../shared/chain'; 8 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 9 + import type { ObjectResolverContext } from '../../types'; 10 import { irSchemaToAst } from '../plugin'; 11 12 + function additionalPropertiesNode( 13 + ctx: ObjectResolverContext, 14 + ): Chain | null | undefined { 15 + const { plugin, schema } = ctx; 16 + 17 + if ( 18 + !schema.additionalProperties || 19 + (schema.properties && Object.keys(schema.properties).length > 0) 20 + ) 21 + return; 22 + 23 + const additionalAst = irSchemaToAst({ 24 + plugin, 25 + schema: schema.additionalProperties, 26 + state: { 27 + ...ctx.utils.state, 28 + path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']), 29 + }, 30 + }); 31 + if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; 32 + return additionalAst.expression; 33 + } 34 + 35 + function baseNode(ctx: ObjectResolverContext): Chain { 36 + const { nodes, symbols } = ctx; 37 + const { z } = symbols; 38 + 39 + const additional = nodes.additionalProperties(ctx); 40 + const shape = nodes.shape(ctx); 41 + 42 if (additional) { 43 return $(z).attr(identifiers.record).call(additional); 44 } ··· 46 return $(z).attr(identifiers.object).call(shape); 47 } 48 49 + function objectResolver(ctx: ObjectResolverContext): Chain { 50 // TODO: parser - handle constants 51 + return ctx.nodes.base(ctx); 52 + } 53 54 + function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> { 55 + const { plugin, schema } = ctx; 56 const shape = $.object().pretty(); 57 58 for (const name in schema.properties) { 59 const property = schema.properties[name]!; 60 61 + const propertyAst = irSchemaToAst({ 62 + optional: !schema.required?.includes(name), 63 plugin, 64 schema: property, 65 state: { 66 + ...ctx.utils.state, 67 + path: ref([...fromRef(ctx.utils.state.path), 'properties', name]), 68 }, 69 }); 70 + if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; 71 + shape.prop(name, propertyAst.expression); 72 } 73 74 + return shape; 75 + } 76 77 + export const objectToAst = ({ 78 + plugin, 79 + schema, 80 + state, 81 + }: IrSchemaToAstOptions & { 82 + schema: SchemaWithType<'object'>; 83 + }): Omit<Ast, 'typeName'> & { 84 + anyType?: string; 85 + } => { 86 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 87 + const z = plugin.external('zod.z'); 88 + const ctx: ObjectResolverContext = { 89 $, 90 + chain: { 91 + current: $(z), 92 + }, 93 + nodes: { 94 + additionalProperties: additionalPropertiesNode, 95 + base: baseNode, 96 + shape: shapeNode, 97 + }, 98 plugin, 99 schema, 100 + symbols: { 101 + z, 102 + }, 103 + utils: { 104 + ast, 105 + state, 106 + }, 107 }; 108 + const resolver = plugin.config['~resolvers']?.object; 109 + const node = resolver?.(ctx) ?? objectResolver(ctx); 110 + ast.expression = node; 111 return { 112 + ...ast, 113 anyType: 'AnyZodObject', 114 + } as Omit<Ast, 'typeName'> & { 115 + anyType: string; 116 }; 117 };
+103 -49
packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 import type { IrSchemaToAstOptions } from '../../shared/types'; 6 - import type { FormatResolverArgs } from '../../types'; 7 8 - const defaultFormatResolver = ({ 9 - chain, 10 - plugin, 11 - schema, 12 - }: FormatResolverArgs): ReturnType<typeof $.call> => { 13 switch (schema.format) { 14 case 'date': 15 - return chain.attr(identifiers.date).call(); 16 case 'date-time': { 17 const obj = $.object() 18 .$if(plugin.config.dates.offset, (o) => ··· 21 .$if(plugin.config.dates.local, (o) => 22 o.prop('local', $.literal(true)), 23 ); 24 - return chain 25 .attr(identifiers.datetime) 26 .call(obj.hasProps() ? obj : undefined); 27 } 28 case 'email': 29 - return chain.attr(identifiers.email).call(); 30 case 'ipv4': 31 case 'ipv6': 32 - return chain.attr(identifiers.ip).call(); 33 case 'time': 34 - return chain.attr(identifiers.time).call(); 35 case 'uri': 36 - return chain.attr(identifiers.url).call(); 37 case 'uuid': 38 - return chain.attr(identifiers.uuid).call(); 39 - default: 40 - return chain; 41 } 42 - }; 43 44 - export const stringToAst = ({ 45 - plugin, 46 - schema, 47 - }: IrSchemaToAstOptions & { 48 - schema: SchemaWithType<'string'>; 49 - }): ReturnType<typeof $.call> => { 50 - let chain: ReturnType<typeof $.call>; 51 52 - const z = plugin.referenceSymbol({ 53 - category: 'external', 54 - resource: 'zod.z', 55 - }); 56 57 - if (typeof schema.const === 'string') { 58 - chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); 59 - return chain; 60 - } 61 62 - chain = $(z).attr(identifiers.string).call(); 63 64 - if (schema.format) { 65 - const args: FormatResolverArgs = { $, chain, plugin, schema, z }; 66 - const resolver = 67 - plugin.config['~resolvers']?.string?.formats?.[schema.format]; 68 - chain = resolver?.(args) ?? defaultFormatResolver(args); 69 } 70 71 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 72 - chain = chain.attr(identifiers.length).call($.literal(schema.minLength)); 73 } else { 74 - if (schema.minLength !== undefined) { 75 - chain = chain.attr(identifiers.min).call($.literal(schema.minLength)); 76 - } 77 78 - if (schema.maxLength !== undefined) { 79 - chain = chain.attr(identifiers.max).call($.literal(schema.maxLength)); 80 - } 81 } 82 83 - if (schema.pattern) { 84 - chain = chain.attr(identifiers.regex).call($.regexp(schema.pattern)); 85 - } 86 87 - return chain; 88 };
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 + import type { Chain } from '../../shared/chain'; 6 import type { IrSchemaToAstOptions } from '../../shared/types'; 7 + import type { StringResolverContext } from '../../types'; 8 9 + function baseNode(ctx: StringResolverContext): Chain { 10 + const { z } = ctx.symbols; 11 + return $(z).attr(identifiers.string).call(); 12 + } 13 + 14 + function constNode(ctx: StringResolverContext): Chain | undefined { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (typeof schema.const !== 'string') return; 18 + return $(z).attr(identifiers.literal).call($.literal(schema.const)); 19 + } 20 + 21 + function formatNode(ctx: StringResolverContext): Chain | undefined { 22 + const { chain, plugin, schema } = ctx; 23 + 24 switch (schema.format) { 25 case 'date': 26 + return chain.current.attr(identifiers.date).call(); 27 case 'date-time': { 28 const obj = $.object() 29 .$if(plugin.config.dates.offset, (o) => ··· 32 .$if(plugin.config.dates.local, (o) => 33 o.prop('local', $.literal(true)), 34 ); 35 + return chain.current 36 .attr(identifiers.datetime) 37 .call(obj.hasProps() ? obj : undefined); 38 } 39 case 'email': 40 + return chain.current.attr(identifiers.email).call(); 41 case 'ipv4': 42 case 'ipv6': 43 + return chain.current.attr(identifiers.ip).call(); 44 case 'time': 45 + return chain.current.attr(identifiers.time).call(); 46 case 'uri': 47 + return chain.current.attr(identifiers.url).call(); 48 case 'uuid': 49 + return chain.current.attr(identifiers.uuid).call(); 50 } 51 52 + return; 53 + } 54 55 + function lengthNode(ctx: StringResolverContext): Chain | undefined { 56 + const { chain, schema } = ctx; 57 + if (schema.minLength === undefined || schema.minLength !== schema.maxLength) 58 + return; 59 + return chain.current 60 + .attr(identifiers.length) 61 + .call($.literal(schema.minLength)); 62 + } 63 64 + function maxLengthNode(ctx: StringResolverContext): Chain | undefined { 65 + const { chain, schema } = ctx; 66 + if (schema.maxLength === undefined) return; 67 + return chain.current.attr(identifiers.max).call($.literal(schema.maxLength)); 68 + } 69 70 + function minLengthNode(ctx: StringResolverContext): Chain | undefined { 71 + const { chain, schema } = ctx; 72 + if (schema.minLength === undefined) return; 73 + return chain.current.attr(identifiers.min).call($.literal(schema.minLength)); 74 + } 75 76 + function patternNode(ctx: StringResolverContext): Chain | undefined { 77 + const { chain, schema } = ctx; 78 + if (!schema.pattern) return; 79 + return chain.current.attr(identifiers.regex).call($.regexp(schema.pattern)); 80 + } 81 + 82 + function stringResolver(ctx: StringResolverContext): Chain { 83 + const constNode = ctx.nodes.const(ctx); 84 + if (constNode) { 85 + ctx.chain.current = constNode; 86 + return ctx.chain.current; 87 } 88 89 + const baseNode = ctx.nodes.base(ctx); 90 + if (baseNode) ctx.chain.current = baseNode; 91 + 92 + const formatNode = ctx.nodes.format(ctx); 93 + if (formatNode) ctx.chain.current = formatNode; 94 + 95 + const lengthNode = ctx.nodes.length(ctx); 96 + if (lengthNode) { 97 + ctx.chain.current = lengthNode; 98 } else { 99 + const minLengthNode = ctx.nodes.minLength(ctx); 100 + if (minLengthNode) ctx.chain.current = minLengthNode; 101 102 + const maxLengthNode = ctx.nodes.maxLength(ctx); 103 + if (maxLengthNode) ctx.chain.current = maxLengthNode; 104 } 105 106 + const patternNode = ctx.nodes.pattern(ctx); 107 + if (patternNode) ctx.chain.current = patternNode; 108 109 + return ctx.chain.current; 110 + } 111 + 112 + export const stringToNode = ({ 113 + plugin, 114 + schema, 115 + }: IrSchemaToAstOptions & { 116 + schema: SchemaWithType<'string'>; 117 + }): Chain => { 118 + const z = plugin.external('zod.z'); 119 + const ctx: StringResolverContext = { 120 + $, 121 + chain: { 122 + current: $(z), 123 + }, 124 + nodes: { 125 + base: baseNode, 126 + const: constNode, 127 + format: formatNode, 128 + length: lengthNode, 129 + maxLength: maxLengthNode, 130 + minLength: minLengthNode, 131 + pattern: patternNode, 132 + }, 133 + plugin, 134 + schema, 135 + symbols: { 136 + z, 137 + }, 138 + }; 139 + const resolver = plugin.config['~resolvers']?.string; 140 + const node = resolver?.(ctx) ?? stringResolver(ctx); 141 + return node; 142 };
+27 -23
packages/openapi-ts/src/plugins/zod/v4/api.ts
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 - import type { ValidatorResolverArgs } from '../types'; 6 7 - const validatorResolver = ({ 8 - schema, 9 - }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 - $(schema).attr(identifiers.parseAsync).call('data').await().return(); 11 12 export const createRequestValidatorV4 = ({ 13 operation, ··· 22 }); 23 if (!symbol) return; 24 25 - const z = plugin.referenceSymbol({ 26 - category: 'external', 27 - resource: 'zod.z', 28 - }); 29 - const args: ValidatorResolverArgs = { 30 $, 31 - chain: undefined, 32 operation, 33 plugin, 34 - schema: symbol, 35 - z, 36 }; 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 - const statements = candidate?.(args); 43 if (statements === null) return; 44 if (statements !== undefined) { 45 return $.func() ··· 64 }); 65 if (!symbol) return; 66 67 - const z = plugin.referenceSymbol({ 68 - category: 'external', 69 - resource: 'zod.z', 70 - }); 71 - const args: ValidatorResolverArgs = { 72 $, 73 - chain: undefined, 74 operation, 75 plugin, 76 - schema: symbol, 77 - z, 78 }; 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 - const statements = candidate?.(args); 85 if (statements === null) return; 86 if (statements !== undefined) { 87 return $.func()
··· 2 3 import { identifiers } from '../constants'; 4 import type { ValidatorArgs } from '../shared/types'; 5 + import type { ValidatorResolverContext } from '../types'; 6 7 + const validatorResolver = ( 8 + ctx: ValidatorResolverContext, 9 + ): ReturnType<typeof $.return> => { 10 + const { schema } = ctx.symbols; 11 + return $(schema).attr(identifiers.parseAsync).call('data').await().return(); 12 + }; 13 14 export const createRequestValidatorV4 = ({ 15 operation, ··· 24 }); 25 if (!symbol) return; 26 27 + const z = plugin.external('zod.z'); 28 + const ctx: ValidatorResolverContext = { 29 $, 30 + chain: { 31 + current: $(z), 32 + }, 33 operation, 34 plugin, 35 + symbols: { 36 + schema: symbol, 37 + z, 38 + }, 39 }; 40 const validator = plugin.config['~resolvers']?.validator; 41 const resolver = 42 typeof validator === 'function' ? validator : validator?.request; 43 const candidates = [resolver, validatorResolver]; 44 for (const candidate of candidates) { 45 + const statements = candidate?.(ctx); 46 if (statements === null) return; 47 if (statements !== undefined) { 48 return $.func() ··· 67 }); 68 if (!symbol) return; 69 70 + const z = plugin.external('zod.z'); 71 + const ctx: ValidatorResolverContext = { 72 $, 73 + chain: { 74 + current: $(z), 75 + }, 76 operation, 77 plugin, 78 + symbols: { 79 + schema: symbol, 80 + z, 81 + }, 82 }; 83 const validator = plugin.config['~resolvers']?.validator; 84 const resolver = 85 typeof validator === 'function' ? validator : validator?.response; 86 const candidates = [resolver, validatorResolver]; 87 for (const candidate of candidates) { 88 + const statements = candidate?.(ctx); 89 if (statements === null) return; 90 if (statements !== undefined) { 91 return $.func()
+2 -2
packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 - import { stringToAst } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 64 ...args, 65 schema: { ...schema, type: 'number' }, 66 }) 67 - : stringToAst({ 68 ...args, 69 schema: schema as SchemaWithType<'string'>, 70 });
··· 9 import { nullToAst } from './null'; 10 import { numberToAst } from './number'; 11 import { objectToAst } from './object'; 12 + import { stringToNode } from './string'; 13 import { tupleToAst } from './tuple'; 14 import { undefinedToAst } from './undefined'; 15 import { unknownToAst } from './unknown'; ··· 64 ...args, 65 schema: { ...schema, type: 'number' }, 66 }) 67 + : stringToNode({ 68 ...args, 69 schema: schema as SchemaWithType<'string'>, 70 });
+74 -58
packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 8 - import type { ObjectBaseResolverArgs } from '../../types'; 9 import { irSchemaToAst } from '../plugin'; 10 11 - function defaultBaseResolver({ 12 - additional, 13 - shape, 14 - z, 15 - }: ObjectBaseResolverArgs): ReturnType<typeof $.call> { 16 if (additional) { 17 return $(z) 18 .attr(identifiers.record) ··· 22 return $(z).attr(identifiers.object).call(shape); 23 } 24 25 - export const objectToAst = ({ 26 - plugin, 27 - schema, 28 - state, 29 - }: IrSchemaToAstOptions & { 30 - schema: SchemaWithType<'object'>; 31 - }): Omit<Ast, 'typeName'> => { 32 - const z = plugin.referenceSymbol({ 33 - category: 'external', 34 - resource: 'zod.z', 35 - }); 36 - 37 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 38 - 39 // TODO: parser - handle constants 40 41 const shape = $.object().pretty(); 42 - const required = schema.required ?? []; 43 44 for (const name in schema.properties) { 45 const property = schema.properties[name]!; 46 - const isRequired = required.includes(name); 47 48 const propertyAst = irSchemaToAst({ 49 - optional: !isRequired, 50 plugin, 51 schema: property, 52 state: { 53 - ...state, 54 - path: ref([...fromRef(state.path), 'properties', name]), 55 }, 56 }); 57 if (propertyAst.hasLazyExpression) { 58 - result.hasLazyExpression = true; 59 - } 60 - 61 - if (propertyAst.hasLazyExpression) { 62 shape.getter(name, propertyAst.expression.return()); 63 } else { 64 shape.prop(name, propertyAst.expression); 65 } 66 } 67 68 - let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined; 69 - if ( 70 - schema.additionalProperties && 71 - (!schema.properties || !Object.keys(schema.properties).length) 72 - ) { 73 - const additionalAst = irSchemaToAst({ 74 - plugin, 75 - schema: schema.additionalProperties, 76 - state: { 77 - ...state, 78 - path: ref([...fromRef(state.path), 'additionalProperties']), 79 - }, 80 - }); 81 - if (additionalAst.hasLazyExpression) result.hasLazyExpression = true; 82 - additional = additionalAst.expression; 83 - } 84 85 - const args: ObjectBaseResolverArgs = { 86 $, 87 - additional, 88 - chain: undefined, 89 plugin, 90 schema, 91 - shape, 92 - z, 93 }; 94 - const resolver = plugin.config['~resolvers']?.object?.base; 95 - const chain = resolver?.(args) ?? defaultBaseResolver(args); 96 - result.expression = chain; 97 - 98 // Return with typeName for circular references 99 - if (result.hasLazyExpression) { 100 return { 101 - ...result, 102 typeName: 'ZodType', 103 } as Ast; 104 } 105 - 106 - return result as Omit<Ast, 'typeName'>; 107 };
··· 4 import { $ } from '~/ts-dsl'; 5 6 import { identifiers } from '../../constants'; 7 + import type { Chain } from '../../shared/chain'; 8 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 9 + import type { ObjectResolverContext } from '../../types'; 10 import { irSchemaToAst } from '../plugin'; 11 12 + function additionalPropertiesNode( 13 + ctx: ObjectResolverContext, 14 + ): Chain | null | undefined { 15 + const { plugin, schema } = ctx; 16 + 17 + if ( 18 + !schema.additionalProperties || 19 + (schema.properties && Object.keys(schema.properties).length > 0) 20 + ) 21 + return; 22 + 23 + const additionalAst = irSchemaToAst({ 24 + plugin, 25 + schema: schema.additionalProperties, 26 + state: { 27 + ...ctx.utils.state, 28 + path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']), 29 + }, 30 + }); 31 + if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; 32 + return additionalAst.expression; 33 + } 34 + 35 + function baseNode(ctx: ObjectResolverContext): Chain { 36 + const { nodes, symbols } = ctx; 37 + const { z } = symbols; 38 + 39 + const additional = nodes.additionalProperties(ctx); 40 + const shape = nodes.shape(ctx); 41 + 42 if (additional) { 43 return $(z) 44 .attr(identifiers.record) ··· 48 return $(z).attr(identifiers.object).call(shape); 49 } 50 51 + function objectResolver(ctx: ObjectResolverContext): Chain { 52 // TODO: parser - handle constants 53 + return ctx.nodes.base(ctx); 54 + } 55 56 + function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> { 57 + const { plugin, schema } = ctx; 58 const shape = $.object().pretty(); 59 60 for (const name in schema.properties) { 61 const property = schema.properties[name]!; 62 63 const propertyAst = irSchemaToAst({ 64 + optional: !schema.required?.includes(name), 65 plugin, 66 schema: property, 67 state: { 68 + ...ctx.utils.state, 69 + path: ref([...fromRef(ctx.utils.state.path), 'properties', name]), 70 }, 71 }); 72 if (propertyAst.hasLazyExpression) { 73 + ctx.utils.ast.hasLazyExpression = true; 74 shape.getter(name, propertyAst.expression.return()); 75 } else { 76 shape.prop(name, propertyAst.expression); 77 } 78 } 79 80 + return shape; 81 + } 82 83 + export const objectToAst = ({ 84 + plugin, 85 + schema, 86 + state, 87 + }: IrSchemaToAstOptions & { 88 + schema: SchemaWithType<'object'>; 89 + }): Omit<Ast, 'typeName'> => { 90 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 91 + const z = plugin.external('zod.z'); 92 + const ctx: ObjectResolverContext = { 93 $, 94 + chain: { 95 + current: $(z), 96 + }, 97 + nodes: { 98 + additionalProperties: additionalPropertiesNode, 99 + base: baseNode, 100 + shape: shapeNode, 101 + }, 102 plugin, 103 schema, 104 + symbols: { 105 + z, 106 + }, 107 + utils: { 108 + ast, 109 + state, 110 + }, 111 }; 112 + const resolver = plugin.config['~resolvers']?.object; 113 + const node = resolver?.(ctx) ?? objectResolver(ctx); 114 + ast.expression = node; 115 // Return with typeName for circular references 116 + if (ast.hasLazyExpression) { 117 return { 118 + ...ast, 119 typeName: 'ZodType', 120 } as Ast; 121 } 122 + return ast as Omit<Ast, 'typeName'>; 123 };
+99 -46
packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 6 - import type { FormatResolverArgs } from '../../types'; 7 8 - const defaultFormatResolver = ({ 9 - chain, 10 - plugin, 11 - schema, 12 - z, 13 - }: FormatResolverArgs): ReturnType<typeof $.call> => { 14 switch (schema.format) { 15 case 'date': 16 return $(z).attr(identifiers.iso).attr(identifiers.date).call(); ··· 39 return $(z).attr(identifiers.url).call(); 40 case 'uuid': 41 return $(z).attr(identifiers.uuid).call(); 42 - default: 43 - return chain; 44 } 45 - }; 46 47 - export const stringToAst = ({ 48 - plugin, 49 - schema, 50 - }: IrSchemaToAstOptions & { 51 - schema: SchemaWithType<'string'>; 52 - }): Omit<Ast, 'typeName'> => { 53 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 54 - let chain: ReturnType<typeof $.call>; 55 56 - const z = plugin.referenceSymbol({ 57 - category: 'external', 58 - resource: 'zod.z', 59 - }); 60 61 - if (typeof schema.const === 'string') { 62 - chain = $(z).attr(identifiers.literal).call($.literal(schema.const)); 63 - result.expression = chain; 64 - return result as Omit<Ast, 'typeName'>; 65 } 66 67 - chain = $(z).attr(identifiers.string).call(); 68 69 - if (schema.format) { 70 - const args: FormatResolverArgs = { $, chain, plugin, schema, z }; 71 - const resolver = 72 - plugin.config['~resolvers']?.string?.formats?.[schema.format]; 73 - chain = resolver?.(args) ?? defaultFormatResolver(args); 74 - } 75 76 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 77 - chain = chain.attr(identifiers.length).call($.literal(schema.minLength)); 78 } else { 79 - if (schema.minLength !== undefined) { 80 - chain = chain.attr(identifiers.min).call($.literal(schema.minLength)); 81 - } 82 83 - if (schema.maxLength !== undefined) { 84 - chain = chain.attr(identifiers.max).call($.literal(schema.maxLength)); 85 - } 86 } 87 88 - if (schema.pattern) { 89 - chain = chain.attr(identifiers.regex).call($.regexp(schema.pattern)); 90 - } 91 92 - result.expression = chain; 93 - return result as Omit<Ast, 'typeName'>; 94 };
··· 2 import { $ } from '~/ts-dsl'; 3 4 import { identifiers } from '../../constants'; 5 + import type { Chain } from '../../shared/chain'; 6 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 7 + import type { StringResolverContext } from '../../types'; 8 + 9 + function baseNode(ctx: StringResolverContext): Chain { 10 + const { z } = ctx.symbols; 11 + return $(z).attr(identifiers.string).call(); 12 + } 13 + 14 + function constNode(ctx: StringResolverContext): Chain | undefined { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (typeof schema.const !== 'string') return; 18 + return $(z).attr(identifiers.literal).call($.literal(schema.const)); 19 + } 20 + 21 + function formatNode(ctx: StringResolverContext): Chain | undefined { 22 + const { plugin, schema, symbols } = ctx; 23 + const { z } = symbols; 24 25 switch (schema.format) { 26 case 'date': 27 return $(z).attr(identifiers.iso).attr(identifiers.date).call(); ··· 50 return $(z).attr(identifiers.url).call(); 51 case 'uuid': 52 return $(z).attr(identifiers.uuid).call(); 53 } 54 + 55 + return; 56 + } 57 + 58 + function lengthNode(ctx: StringResolverContext): Chain | undefined { 59 + const { chain, schema } = ctx; 60 + if (schema.minLength === undefined || schema.minLength !== schema.maxLength) 61 + return; 62 + return chain.current 63 + .attr(identifiers.length) 64 + .call($.literal(schema.minLength)); 65 + } 66 67 + function maxLengthNode(ctx: StringResolverContext): Chain | undefined { 68 + const { chain, schema } = ctx; 69 + if (schema.maxLength === undefined) return; 70 + return chain.current.attr(identifiers.max).call($.literal(schema.maxLength)); 71 + } 72 73 + function minLengthNode(ctx: StringResolverContext): Chain | undefined { 74 + const { chain, schema } = ctx; 75 + if (schema.minLength === undefined) return; 76 + return chain.current.attr(identifiers.min).call($.literal(schema.minLength)); 77 + } 78 79 + function patternNode(ctx: StringResolverContext): Chain | undefined { 80 + const { chain, schema } = ctx; 81 + if (!schema.pattern) return; 82 + return chain.current.attr(identifiers.regex).call($.regexp(schema.pattern)); 83 + } 84 + 85 + function stringResolver(ctx: StringResolverContext): Chain { 86 + const constNode = ctx.nodes.const(ctx); 87 + if (constNode) { 88 + ctx.chain.current = constNode; 89 + return ctx.chain.current; 90 } 91 92 + const baseNode = ctx.nodes.base(ctx); 93 + if (baseNode) ctx.chain.current = baseNode; 94 95 + const formatNode = ctx.nodes.format(ctx); 96 + if (formatNode) ctx.chain.current = formatNode; 97 98 + const lengthNode = ctx.nodes.length(ctx); 99 + if (lengthNode) { 100 + ctx.chain.current = lengthNode; 101 } else { 102 + const minLengthNode = ctx.nodes.minLength(ctx); 103 + if (minLengthNode) ctx.chain.current = minLengthNode; 104 105 + const maxLengthNode = ctx.nodes.maxLength(ctx); 106 + if (maxLengthNode) ctx.chain.current = maxLengthNode; 107 } 108 109 + const patternNode = ctx.nodes.pattern(ctx); 110 + if (patternNode) ctx.chain.current = patternNode; 111 112 + return ctx.chain.current; 113 + } 114 + 115 + export const stringToNode = ({ 116 + plugin, 117 + schema, 118 + }: IrSchemaToAstOptions & { 119 + schema: SchemaWithType<'string'>; 120 + }): Omit<Ast, 'typeName'> => { 121 + const z = plugin.external('zod.z'); 122 + const ctx: StringResolverContext = { 123 + $, 124 + chain: { 125 + current: $(z), 126 + }, 127 + nodes: { 128 + base: baseNode, 129 + const: constNode, 130 + format: formatNode, 131 + length: lengthNode, 132 + maxLength: maxLengthNode, 133 + minLength: minLengthNode, 134 + pattern: patternNode, 135 + }, 136 + plugin, 137 + schema, 138 + symbols: { 139 + z, 140 + }, 141 + }; 142 + const resolver = plugin.config['~resolvers']?.string; 143 + const node = resolver?.(ctx) ?? stringResolver(ctx); 144 + return { 145 + expression: node, 146 + }; 147 };