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