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

feat: add number base resolvers

Lubos cd07d9c1 34381cc4

Changed files
+443 -320
dev
packages
openapi-ts
src
plugins
+55 -43
dev/openapi-ts.config.ts
··· 532 532 }, 533 533 }, 534 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 - }, 535 + // number(ctx) { 536 + // const { $, plugin, symbols } = ctx; 537 + // const { z } = symbols; 538 + // // ctx.nodes.base = () => { 539 + // // // implement custom base number resolver 540 + // // } 541 + // const big = plugin.symbolOnce('Big', { 542 + // external: 'big.js', 543 + // importKind: 'default', 544 + // }); 545 + // return $(z).attr('instanceof').call(big); 546 + // }, 547 + // object(ctx) { 548 + // const { $, symbols } = ctx; 549 + // const { z } = symbols; 550 + // const additional = ctx.nodes.additionalProperties(ctx); 551 + // if (additional === undefined) { 552 + // const shape = ctx.nodes.shape(ctx); 553 + // // return $('z').attr('object').call(shape).attr('passthrough').call() 554 + // ctx.nodes.base = () => 555 + // $(z).attr('object').call(shape).attr('strict').call(); 556 + // } 557 + // }, 558 + // string(ctx) { 559 + // const { $, schema, symbols } = ctx; 560 + // const { z } = symbols; 561 + // if (schema.format === 'date' || schema.format === 'date-time') { 562 + // ctx.nodes.format = () => $(z).attr('date').call(); 563 + // } 564 + // if (schema.format === 'int64') { 565 + // ctx.nodes.format = () => 566 + // $(z) 567 + // .attr('string') 568 + // .call() 569 + // .attr('refine') 570 + // .call( 571 + // $.func() 572 + // .param('val') 573 + // .do( 574 + // $.try( 575 + // $(z) 576 + // .attr('int64') 577 + // .call() 578 + // .attr('parse') 579 + // .call($('BigInt').call('val')), 580 + // $.return($.literal(true)), 581 + // ).catch($.return($.literal(false))), 582 + // ), 583 + // $.object().prop( 584 + // 'message', 585 + // $.literal('Must be a valid int64 string'), 586 + // ), 587 + // ); 588 + // } 589 + // }, 578 590 // validator({ $, schema }) { 579 591 // return [ 580 592 // $.const('parsed').assign(
-8
packages/openapi-ts/src/plugins/arktype/v2/toAst/object.ts
··· 93 93 result.hasLazyExpression = true; 94 94 } 95 95 96 - // Return with typeName for circular references 97 - if (result.hasLazyExpression) { 98 - return { 99 - ...result, 100 - typeName: 'TODO', 101 - } as Ast; 102 - } 103 - 104 96 return result as Omit<Ast, 'typeName'>; 105 97 } 106 98
+1 -1
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
··· 112 112 } 113 113 } 114 114 115 - if (checks.length) { 115 + if (checks.length > 0) { 116 116 result.expression = result.expression 117 117 .attr(identifiers.check) 118 118 .call(...checks);
+3 -3
packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts
··· 7 7 import { enumToAst } from './enum'; 8 8 import { neverToAst } from './never'; 9 9 import { nullToAst } from './null'; 10 - import { numberToAst } from './number'; 10 + import { numberToNode } from './number'; 11 11 import { objectToAst } from './object'; 12 12 import { stringToNode } from './string'; 13 13 import { tupleToAst } from './tuple'; ··· 39 39 }); 40 40 case 'integer': 41 41 case 'number': 42 - return numberToAst({ 42 + return numberToNode({ 43 43 ...args, 44 44 schema: schema as SchemaWithType<'integer' | 'number'>, 45 45 }); ··· 60 60 }); 61 61 case 'string': 62 62 return shouldCoerceToBigInt(schema.format) 63 - ? numberToAst({ 63 + ? numberToNode({ 64 64 ...args, 65 65 schema: { ...schema, type: 'number' }, 66 66 })
+121 -87
packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts
··· 9 9 import { identifiers } from '../../constants'; 10 10 import type { Chain } from '../../shared/chain'; 11 11 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 12 - 13 - export const numberToAst = ({ 14 - plugin, 15 - schema, 16 - }: IrSchemaToAstOptions & { 17 - schema: SchemaWithType<'integer' | 'number'>; 18 - }): Omit<Ast, 'typeName'> => { 19 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 20 - 21 - const z = plugin.referenceSymbol({ 22 - category: 'external', 23 - resource: 'zod.z', 24 - }); 12 + import type { NumberResolverContext } from '../../types'; 25 13 26 - if (schema.const !== undefined) { 27 - result.expression = $(z) 28 - .attr(identifiers.literal) 29 - .call(maybeBigInt(schema.const, schema.format)); 30 - return result as Omit<Ast, 'typeName'>; 14 + function baseNode(ctx: NumberResolverContext): Chain { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (ctx.utils.shouldCoerceToBigInt(schema.format)) { 18 + return $(z).attr(identifiers.coerce).attr(identifiers.bigint).call(); 31 19 } 32 - 33 - if (shouldCoerceToBigInt(schema.format)) { 34 - result.expression = $(z) 35 - .attr(identifiers.coerce) 36 - .attr(identifiers.bigint) 37 - .call(); 38 - } else { 39 - result.expression = $(z).attr(identifiers.number).call(); 40 - if (schema.type === 'integer') { 41 - result.expression = $(z).attr(identifiers.int).call(); 42 - } 20 + let chain = $(z).attr(identifiers.number).call(); 21 + if (schema.type === 'integer') { 22 + chain = $(z).attr(identifiers.int).call(); 43 23 } 24 + return chain; 25 + } 44 26 45 - const checks: Array<Chain> = []; 27 + function constNode(ctx: NumberResolverContext): Chain | undefined { 28 + const { schema, symbols } = ctx; 29 + const { z } = symbols; 30 + if (schema.const === undefined) return; 31 + return $(z) 32 + .attr(identifiers.literal) 33 + .call(ctx.utils.maybeBigInt(schema.const, schema.format)); 34 + } 46 35 47 - let hasLowerBound = false; 48 - let hasUpperBound = false; 36 + function maxNode(ctx: NumberResolverContext): Chain | undefined { 37 + const { schema, symbols } = ctx; 38 + const { z } = symbols; 39 + if (schema.exclusiveMaximum !== undefined) { 40 + return $(z) 41 + .attr(identifiers.lt) 42 + .call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format)); 43 + } 44 + if (schema.maximum !== undefined) { 45 + return $(z) 46 + .attr(identifiers.lte) 47 + .call(ctx.utils.maybeBigInt(schema.maximum, schema.format)); 48 + } 49 + const limit = ctx.utils.getIntegerLimit(schema.format); 50 + if (limit) { 51 + return $(z) 52 + .attr(identifiers.maximum) 53 + .call( 54 + ctx.utils.maybeBigInt(limit.maxValue, schema.format), 55 + $.object().prop('error', $.literal(limit.maxError)), 56 + ); 57 + } 58 + return; 59 + } 49 60 61 + function minNode(ctx: NumberResolverContext): Chain | undefined { 62 + const { schema, symbols } = ctx; 63 + const { z } = symbols; 50 64 if (schema.exclusiveMinimum !== undefined) { 51 - checks.push( 52 - $(z) 53 - .attr(identifiers.gt) 54 - .call(maybeBigInt(schema.exclusiveMinimum, schema.format)), 55 - ); 56 - hasLowerBound = true; 57 - } else if (schema.minimum !== undefined) { 58 - checks.push( 59 - $(z) 60 - .attr(identifiers.gte) 61 - .call(maybeBigInt(schema.minimum, schema.format)), 62 - ); 63 - hasLowerBound = true; 65 + return $(z) 66 + .attr(identifiers.gt) 67 + .call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format)); 64 68 } 65 - 66 - if (schema.exclusiveMaximum !== undefined) { 67 - checks.push( 68 - $(z) 69 - .attr(identifiers.lt) 70 - .call(maybeBigInt(schema.exclusiveMaximum, schema.format)), 71 - ); 72 - hasUpperBound = true; 73 - } else if (schema.maximum !== undefined) { 74 - checks.push( 75 - $(z) 76 - .attr(identifiers.lte) 77 - .call(maybeBigInt(schema.maximum, schema.format)), 78 - ); 79 - hasUpperBound = true; 69 + if (schema.minimum !== undefined) { 70 + return $(z) 71 + .attr(identifiers.gte) 72 + .call(ctx.utils.maybeBigInt(schema.minimum, schema.format)); 80 73 } 81 - 82 - const integerLimit = getIntegerLimit(schema.format); 83 - if (integerLimit) { 84 - if (!hasLowerBound) { 85 - checks.push( 86 - $(z) 87 - .attr(identifiers.minimum) 88 - .call( 89 - maybeBigInt(integerLimit.minValue, schema.format), 90 - $.object().prop('error', $.literal(integerLimit.minError)), 91 - ), 74 + const limit = ctx.utils.getIntegerLimit(schema.format); 75 + if (limit) { 76 + return $(z) 77 + .attr(identifiers.minimum) 78 + .call( 79 + ctx.utils.maybeBigInt(limit.minValue, schema.format), 80 + $.object().prop('error', $.literal(limit.minError)), 92 81 ); 93 - hasLowerBound = true; 94 - } 82 + } 83 + return; 84 + } 95 85 96 - if (!hasUpperBound) { 97 - checks.push( 98 - $(z) 99 - .attr(identifiers.maximum) 100 - .call( 101 - maybeBigInt(integerLimit.maxValue, schema.format), 102 - $.object().prop('error', $.literal(integerLimit.maxError)), 103 - ), 104 - ); 105 - hasUpperBound = true; 106 - } 86 + function numberResolver(ctx: NumberResolverContext): Chain { 87 + const constNode = ctx.nodes.const(ctx); 88 + if (constNode) { 89 + ctx.chain.current = constNode; 90 + return ctx.chain.current; 107 91 } 108 92 109 - if (checks.length) { 110 - result.expression = result.expression 93 + const baseNode = ctx.nodes.base(ctx); 94 + if (baseNode) ctx.chain.current = baseNode; 95 + 96 + const checks: Array<Chain> = []; 97 + 98 + const minNode = ctx.nodes.min(ctx); 99 + if (minNode) checks.push(minNode); 100 + 101 + const maxNode = ctx.nodes.max(ctx); 102 + if (maxNode) checks.push(maxNode); 103 + 104 + if (checks.length > 0) { 105 + ctx.chain.current = ctx.chain.current 111 106 .attr(identifiers.check) 112 107 .call(...checks); 113 108 } 114 109 115 - return result as Omit<Ast, 'typeName'>; 110 + return ctx.chain.current; 111 + } 112 + 113 + export const numberToNode = ({ 114 + plugin, 115 + schema, 116 + state, 117 + }: IrSchemaToAstOptions & { 118 + schema: SchemaWithType<'integer' | 'number'>; 119 + }): Omit<Ast, 'typeName'> => { 120 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 121 + const z = plugin.external('zod.z'); 122 + const ctx: NumberResolverContext = { 123 + $, 124 + chain: { 125 + current: $(z), 126 + }, 127 + nodes: { 128 + base: baseNode, 129 + const: constNode, 130 + max: maxNode, 131 + min: minNode, 132 + }, 133 + plugin, 134 + schema, 135 + symbols: { 136 + z, 137 + }, 138 + utils: { 139 + ast, 140 + getIntegerLimit, 141 + maybeBigInt, 142 + shouldCoerceToBigInt, 143 + state, 144 + }, 145 + }; 146 + const resolver = plugin.config['~resolvers']?.number; 147 + const node = resolver?.(ctx) ?? numberResolver(ctx); 148 + ast.expression = node; 149 + return ast as Omit<Ast, 'typeName'>; 116 150 };
+1 -1
packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts
··· 113 113 const patternNode = ctx.nodes.pattern(ctx); 114 114 if (patternNode) checks.push(patternNode); 115 115 116 - if (checks.length) { 116 + if (checks.length > 0) { 117 117 ctx.chain.current = ctx.chain.current 118 118 .attr(identifiers.check) 119 119 .call(...checks);
+29 -25
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 3 3 4 4 import type { IR } from '~/ir/types'; 5 5 import type { DefinePlugin, Plugin, SchemaWithType } from '~/plugins'; 6 + import type { 7 + MaybeBigInt, 8 + ShouldCoerceToBigInt, 9 + } from '~/plugins/shared/utils/coerce'; 10 + import type { GetIntegerLimit } from '~/plugins/shared/utils/formats'; 6 11 import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl'; 7 12 import type { StringCase, StringName } from '~/types/case'; 8 13 import type { MaybeArray } from '~/types/utils'; ··· 777 782 }; 778 783 } 779 784 785 + export interface NumberResolverContext extends BaseResolverContext { 786 + /** 787 + * Nodes used to build different parts of the number schema. 788 + */ 789 + nodes: { 790 + base: (ctx: NumberResolverContext) => Chain; 791 + const: (ctx: NumberResolverContext) => Chain | undefined; 792 + max: (ctx: NumberResolverContext) => Chain | undefined; 793 + min: (ctx: NumberResolverContext) => Chain | undefined; 794 + }; 795 + schema: SchemaWithType<'integer' | 'number'>; 796 + /** 797 + * Utility functions for number schema processing. 798 + */ 799 + utils: { 800 + ast: Partial<Omit<Ast, 'typeName'>>; 801 + getIntegerLimit: GetIntegerLimit; 802 + maybeBigInt: MaybeBigInt; 803 + shouldCoerceToBigInt: ShouldCoerceToBigInt; 804 + state: Refs<PluginState>; 805 + }; 806 + } 807 + 780 808 export interface ObjectResolverContext extends BaseResolverContext { 781 809 /** 782 810 * Nodes used to build different parts of the object schema. ··· 801 829 state: Refs<PluginState>; 802 830 }; 803 831 } 804 - 805 - type ResolverResult = boolean | number; 806 832 807 833 export interface StringResolverContext extends BaseResolverContext { 808 834 /** ··· 842 868 * 843 869 * Returning `undefined` will execute the default resolver logic. 844 870 */ 845 - number?: { 846 - /** 847 - * Controls the base segment for number schemas. 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 - * 855 - * Each key represents a specific format name with a custom 856 - * resolver function that controls how that format is rendered. 857 - * 858 - * Example path: `~resolvers.number.formats.float` 859 - * 860 - * Returning `undefined` from a resolver will apply the default 861 - * generation behavior for that format. 862 - */ 863 - formats?: Record< 864 - string, 865 - (ctx: StringResolverContext) => ResolverResult | undefined 866 - >; 867 - }; 871 + number?: (ctx: NumberResolverContext) => Chain | undefined; 868 872 /** 869 873 * Resolver for object schemas. 870 874 *
+3 -3
packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts
··· 7 7 import { enumToAst } from './enum'; 8 8 import { neverToAst } from './never'; 9 9 import { nullToAst } from './null'; 10 - import { numberToAst } from './number'; 10 + import { numberToNode } from './number'; 11 11 import { objectToAst } from './object'; 12 12 import { stringToNode } from './string'; 13 13 import { tupleToAst } from './tuple'; ··· 46 46 case 'integer': 47 47 case 'number': 48 48 return { 49 - expression: numberToAst({ 49 + expression: numberToNode({ 50 50 ...args, 51 51 schema: schema as SchemaWithType<'integer' | 'number'>, 52 52 }), ··· 73 73 case 'string': 74 74 return { 75 75 expression: shouldCoerceToBigInt(schema.format) 76 - ? numberToAst({ 76 + ? numberToNode({ 77 77 ...args, 78 78 schema: { ...schema, type: 'number' }, 79 79 })
+113 -70
packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts
··· 8 8 9 9 import { identifiers } from '../../constants'; 10 10 import type { Chain } from '../../shared/chain'; 11 - import type { IrSchemaToAstOptions } from '../../shared/types'; 11 + import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 12 + import type { NumberResolverContext } from '../../types'; 12 13 13 - export const numberToAst = ({ 14 - plugin, 15 - schema, 16 - }: IrSchemaToAstOptions & { 17 - schema: SchemaWithType<'integer' | 'number'>; 18 - }) => { 19 - const z = plugin.referenceSymbol({ 20 - category: 'external', 21 - resource: 'zod.z', 22 - }); 23 - 24 - if (schema.const !== undefined) { 25 - const expression = $(z) 26 - .attr(identifiers.literal) 27 - .call(maybeBigInt(schema.const, schema.format)); 28 - return expression; 14 + function baseNode(ctx: NumberResolverContext): Chain { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (ctx.utils.shouldCoerceToBigInt(schema.format)) { 18 + return $(z).attr(identifiers.coerce).attr(identifiers.bigint).call(); 29 19 } 20 + let chain = $(z).attr(identifiers.number).call(); 21 + if (schema.type === 'integer') { 22 + chain = chain.attr(identifiers.int).call(); 23 + } 24 + return chain; 25 + } 30 26 31 - let numberExpression: Chain; 27 + function constNode(ctx: NumberResolverContext): Chain | undefined { 28 + const { schema, symbols } = ctx; 29 + const { z } = symbols; 30 + if (schema.const === undefined) return; 31 + return $(z) 32 + .attr(identifiers.literal) 33 + .call(ctx.utils.maybeBigInt(schema.const, schema.format)); 34 + } 32 35 33 - if (shouldCoerceToBigInt(schema.format)) { 34 - numberExpression = $(z) 35 - .attr(identifiers.coerce) 36 - .attr(identifiers.bigint) 37 - .call(); 38 - } else { 39 - numberExpression = $(z).attr(identifiers.number).call(); 40 - if (schema.type === 'integer') { 41 - numberExpression = numberExpression.attr(identifiers.int).call(); 42 - } 36 + function maxNode(ctx: NumberResolverContext): Chain | undefined { 37 + const { chain, schema } = ctx; 38 + if (schema.exclusiveMaximum !== undefined) { 39 + return chain.current 40 + .attr(identifiers.lt) 41 + .call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format)); 42 + } 43 + if (schema.maximum !== undefined) { 44 + return chain.current 45 + .attr(identifiers.lte) 46 + .call(ctx.utils.maybeBigInt(schema.maximum, schema.format)); 43 47 } 44 - 45 - let hasLowerBound = false; 46 - let hasUpperBound = false; 48 + const limit = ctx.utils.getIntegerLimit(schema.format); 49 + if (limit) { 50 + return chain.current 51 + .attr(identifiers.max) 52 + .call( 53 + ctx.utils.maybeBigInt(limit.maxValue, schema.format), 54 + $.object().prop('message', $.literal(limit.maxError)), 55 + ); 56 + } 57 + return; 58 + } 47 59 60 + function minNode(ctx: NumberResolverContext): Chain | undefined { 61 + const { chain, schema } = ctx; 48 62 if (schema.exclusiveMinimum !== undefined) { 49 - numberExpression = numberExpression 63 + return chain.current 50 64 .attr(identifiers.gt) 51 - .call(maybeBigInt(schema.exclusiveMinimum, schema.format)); 52 - hasLowerBound = true; 53 - } else if (schema.minimum !== undefined) { 54 - numberExpression = numberExpression 65 + .call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format)); 66 + } 67 + if (schema.minimum !== undefined) { 68 + return chain.current 55 69 .attr(identifiers.gte) 56 - .call(maybeBigInt(schema.minimum, schema.format)); 57 - hasLowerBound = true; 70 + .call(ctx.utils.maybeBigInt(schema.minimum, schema.format)); 71 + } 72 + const limit = ctx.utils.getIntegerLimit(schema.format); 73 + if (limit) { 74 + return chain.current 75 + .attr(identifiers.min) 76 + .call( 77 + ctx.utils.maybeBigInt(limit.minValue, schema.format), 78 + $.object().prop('message', $.literal(limit.minError)), 79 + ); 58 80 } 81 + return; 82 + } 59 83 60 - if (schema.exclusiveMaximum !== undefined) { 61 - numberExpression = numberExpression 62 - .attr(identifiers.lt) 63 - .call(maybeBigInt(schema.exclusiveMaximum, schema.format)); 64 - hasUpperBound = true; 65 - } else if (schema.maximum !== undefined) { 66 - numberExpression = numberExpression 67 - .attr(identifiers.lte) 68 - .call(maybeBigInt(schema.maximum, schema.format)); 69 - hasUpperBound = true; 84 + function numberResolver(ctx: NumberResolverContext): Chain { 85 + const constNode = ctx.nodes.const(ctx); 86 + if (constNode) { 87 + ctx.chain.current = constNode; 88 + return ctx.chain.current; 70 89 } 71 90 72 - const integerLimit = getIntegerLimit(schema.format); 73 - if (integerLimit) { 74 - if (!hasLowerBound) { 75 - numberExpression = numberExpression 76 - .attr(identifiers.min) 77 - .call( 78 - maybeBigInt(integerLimit.minValue, schema.format), 79 - $.object().prop('message', $.literal(integerLimit.minError)), 80 - ); 81 - hasLowerBound = true; 82 - } 91 + const baseNode = ctx.nodes.base(ctx); 92 + if (baseNode) ctx.chain.current = baseNode; 93 + 94 + const minNode = ctx.nodes.min(ctx); 95 + if (minNode) ctx.chain.current = minNode; 96 + 97 + const maxNode = ctx.nodes.max(ctx); 98 + if (maxNode) ctx.chain.current = maxNode; 83 99 84 - if (!hasUpperBound) { 85 - numberExpression = numberExpression 86 - .attr(identifiers.max) 87 - .call( 88 - maybeBigInt(integerLimit.maxValue, schema.format), 89 - $.object().prop('message', $.literal(integerLimit.maxError)), 90 - ); 91 - hasUpperBound = true; 92 - } 93 - } 100 + return ctx.chain.current; 101 + } 94 102 95 - return numberExpression; 103 + export const numberToNode = ({ 104 + plugin, 105 + schema, 106 + state, 107 + }: IrSchemaToAstOptions & { 108 + schema: SchemaWithType<'integer' | 'number'>; 109 + }): Chain => { 110 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 111 + const z = plugin.external('zod.z'); 112 + const ctx: NumberResolverContext = { 113 + $, 114 + chain: { 115 + current: $(z), 116 + }, 117 + nodes: { 118 + base: baseNode, 119 + const: constNode, 120 + max: maxNode, 121 + min: minNode, 122 + }, 123 + plugin, 124 + schema, 125 + symbols: { 126 + z, 127 + }, 128 + utils: { 129 + ast, 130 + getIntegerLimit, 131 + maybeBigInt, 132 + shouldCoerceToBigInt, 133 + state, 134 + }, 135 + }; 136 + const resolver = plugin.config['~resolvers']?.number; 137 + const node = resolver?.(ctx) ?? numberResolver(ctx); 138 + return node; 96 139 };
+3 -3
packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts
··· 7 7 import { enumToAst } from './enum'; 8 8 import { neverToAst } from './never'; 9 9 import { nullToAst } from './null'; 10 - import { numberToAst } from './number'; 10 + import { numberToNode } from './number'; 11 11 import { objectToAst } from './object'; 12 12 import { stringToNode } from './string'; 13 13 import { tupleToAst } from './tuple'; ··· 39 39 }); 40 40 case 'integer': 41 41 case 'number': 42 - return numberToAst({ 42 + return numberToNode({ 43 43 ...args, 44 44 schema: schema as SchemaWithType<'integer' | 'number'>, 45 45 }); ··· 60 60 }); 61 61 case 'string': 62 62 return shouldCoerceToBigInt(schema.format) 63 - ? numberToAst({ 63 + ? numberToNode({ 64 64 ...args, 65 65 schema: { ...schema, type: 'number' }, 66 66 })
+114 -69
packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts
··· 7 7 import { $ } from '~/ts-dsl'; 8 8 9 9 import { identifiers } from '../../constants'; 10 + import type { Chain } from '../../shared/chain'; 10 11 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 12 + import type { NumberResolverContext } from '../../types'; 11 13 12 - export const numberToAst = ({ 13 - plugin, 14 - schema, 15 - }: IrSchemaToAstOptions & { 16 - schema: SchemaWithType<'integer' | 'number'>; 17 - }): Omit<Ast, 'typeName'> => { 18 - const result: Partial<Omit<Ast, 'typeName'>> = {}; 14 + function baseNode(ctx: NumberResolverContext): Chain { 15 + const { schema, symbols } = ctx; 16 + const { z } = symbols; 17 + if (ctx.utils.shouldCoerceToBigInt(schema.format)) { 18 + return $(z).attr(identifiers.coerce).attr(identifiers.bigint).call(); 19 + } 20 + let chain = $(z).attr(identifiers.number).call(); 21 + if (schema.type === 'integer') { 22 + chain = $(z).attr(identifiers.int).call(); 23 + } 24 + return chain; 25 + } 19 26 20 - const z = plugin.referenceSymbol({ 21 - category: 'external', 22 - resource: 'zod.z', 23 - }); 27 + function constNode(ctx: NumberResolverContext): Chain | undefined { 28 + const { schema, symbols } = ctx; 29 + const { z } = symbols; 30 + if (schema.const === undefined) return; 31 + return $(z) 32 + .attr(identifiers.literal) 33 + .call(ctx.utils.maybeBigInt(schema.const, schema.format)); 34 + } 24 35 25 - if (schema.const !== undefined) { 26 - result.expression = $(z) 27 - .attr(identifiers.literal) 28 - .call(maybeBigInt(schema.const, schema.format)); 29 - return result as Omit<Ast, 'typeName'>; 36 + function maxNode(ctx: NumberResolverContext): Chain | undefined { 37 + const { chain, schema } = ctx; 38 + if (schema.exclusiveMaximum !== undefined) { 39 + return chain.current 40 + .attr(identifiers.lt) 41 + .call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format)); 30 42 } 31 - 32 - if (shouldCoerceToBigInt(schema.format)) { 33 - result.expression = $(z) 34 - .attr(identifiers.coerce) 35 - .attr(identifiers.bigint) 36 - .call(); 37 - } else { 38 - result.expression = $(z).attr(identifiers.number).call(); 39 - if (schema.type === 'integer') { 40 - result.expression = $(z).attr(identifiers.int).call(); 41 - } 43 + if (schema.maximum !== undefined) { 44 + return chain.current 45 + .attr(identifiers.lte) 46 + .call(ctx.utils.maybeBigInt(schema.maximum, schema.format)); 42 47 } 43 - 44 - let hasLowerBound = false; 45 - let hasUpperBound = false; 48 + const limit = ctx.utils.getIntegerLimit(schema.format); 49 + if (limit) { 50 + return chain.current 51 + .attr(identifiers.max) 52 + .call( 53 + ctx.utils.maybeBigInt(limit.maxValue, schema.format), 54 + $.object().prop('error', $.literal(limit.maxError)), 55 + ); 56 + } 57 + return; 58 + } 46 59 60 + function minNode(ctx: NumberResolverContext): Chain | undefined { 61 + const { chain, schema } = ctx; 47 62 if (schema.exclusiveMinimum !== undefined) { 48 - result.expression = result.expression 63 + return chain.current 49 64 .attr(identifiers.gt) 50 - .call(maybeBigInt(schema.exclusiveMinimum, schema.format)); 51 - hasLowerBound = true; 52 - } else if (schema.minimum !== undefined) { 53 - result.expression = result.expression 65 + .call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format)); 66 + } 67 + if (schema.minimum !== undefined) { 68 + return chain.current 54 69 .attr(identifiers.gte) 55 - .call(maybeBigInt(schema.minimum, schema.format)); 56 - hasLowerBound = true; 70 + .call(ctx.utils.maybeBigInt(schema.minimum, schema.format)); 71 + } 72 + const limit = ctx.utils.getIntegerLimit(schema.format); 73 + if (limit) { 74 + return chain.current 75 + .attr(identifiers.min) 76 + .call( 77 + ctx.utils.maybeBigInt(limit.minValue, schema.format), 78 + $.object().prop('error', $.literal(limit.minError)), 79 + ); 57 80 } 81 + return; 82 + } 58 83 59 - if (schema.exclusiveMaximum !== undefined) { 60 - result.expression = result.expression 61 - .attr(identifiers.lt) 62 - .call(maybeBigInt(schema.exclusiveMaximum, schema.format)); 63 - hasUpperBound = true; 64 - } else if (schema.maximum !== undefined) { 65 - result.expression = result.expression 66 - .attr(identifiers.lte) 67 - .call(maybeBigInt(schema.maximum, schema.format)); 68 - hasUpperBound = true; 84 + function numberResolver(ctx: NumberResolverContext): Chain { 85 + const constNode = ctx.nodes.const(ctx); 86 + if (constNode) { 87 + ctx.chain.current = constNode; 88 + return ctx.chain.current; 69 89 } 70 90 71 - const integerLimit = getIntegerLimit(schema.format); 72 - if (integerLimit) { 73 - if (!hasLowerBound) { 74 - result.expression = result.expression 75 - .attr(identifiers.min) 76 - .call( 77 - maybeBigInt(integerLimit.minValue, schema.format), 78 - $.object().prop('error', $.literal(integerLimit.minError)), 79 - ); 80 - hasLowerBound = true; 81 - } 91 + const baseNode = ctx.nodes.base(ctx); 92 + if (baseNode) ctx.chain.current = baseNode; 93 + 94 + const minNode = ctx.nodes.min(ctx); 95 + if (minNode) ctx.chain.current = minNode; 96 + 97 + const maxNode = ctx.nodes.max(ctx); 98 + if (maxNode) ctx.chain.current = maxNode; 82 99 83 - if (!hasUpperBound) { 84 - result.expression = result.expression 85 - .attr(identifiers.max) 86 - .call( 87 - maybeBigInt(integerLimit.maxValue, schema.format), 88 - $.object().prop('error', $.literal(integerLimit.maxError)), 89 - ); 90 - hasUpperBound = true; 91 - } 92 - } 100 + return ctx.chain.current; 101 + } 93 102 94 - return result as Omit<Ast, 'typeName'>; 103 + export const numberToNode = ({ 104 + plugin, 105 + schema, 106 + state, 107 + }: IrSchemaToAstOptions & { 108 + schema: SchemaWithType<'integer' | 'number'>; 109 + }): Omit<Ast, 'typeName'> => { 110 + const ast: Partial<Omit<Ast, 'typeName'>> = {}; 111 + const z = plugin.external('zod.z'); 112 + const ctx: NumberResolverContext = { 113 + $, 114 + chain: { 115 + current: $(z), 116 + }, 117 + nodes: { 118 + base: baseNode, 119 + const: constNode, 120 + max: maxNode, 121 + min: minNode, 122 + }, 123 + plugin, 124 + schema, 125 + symbols: { 126 + z, 127 + }, 128 + utils: { 129 + ast, 130 + getIntegerLimit, 131 + maybeBigInt, 132 + shouldCoerceToBigInt, 133 + state, 134 + }, 135 + }; 136 + const resolver = plugin.config['~resolvers']?.number; 137 + const node = resolver?.(ctx) ?? numberResolver(ctx); 138 + ast.expression = node; 139 + return ast as Omit<Ast, 'typeName'>; 95 140 };
-7
packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts
··· 112 112 const resolver = plugin.config['~resolvers']?.object; 113 113 const node = resolver?.(ctx) ?? objectResolver(ctx); 114 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 115 return ast as Omit<Ast, 'typeName'>; 123 116 };