+55
-43
dev/openapi-ts.config.ts
+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
-8
packages/openapi-ts/src/plugins/arktype/v2/toAst/object.ts
+1
-1
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
+1
-1
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
+3
-3
packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts
+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
+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
+1
-1
packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts
+29
-25
packages/openapi-ts/src/plugins/zod/types.d.ts
+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
+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
+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
+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
+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
-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
};