+5
.changeset/curly-geckos-play.md
+5
.changeset/curly-geckos-play.md
+7
.changeset/fruity-camels-type.md
+7
.changeset/fruity-camels-type.md
···
1
+
---
2
+
'@hey-api/openapi-ts': minor
3
+
---
4
+
5
+
**plugin(valibot)**: **BREAKING:** standardize `~resolvers` API
6
+
7
+
The [Resolvers API](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) page.
+7
.changeset/upset-weeks-dream.md
+7
.changeset/upset-weeks-dream.md
···
1
+
---
2
+
'@hey-api/openapi-ts': minor
3
+
---
4
+
5
+
**plugin(zod)**: **BREAKING:** standardize `~resolvers` API
6
+
7
+
The [Resolvers API](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) page.
+86
-41
dev/openapi-ts.config.ts
+86
-41
dev/openapi-ts.config.ts
···
431
431
},
432
432
},
433
433
'~resolvers': {
434
-
object: {
435
-
// base({ $, additional, pipes, shape }) {
436
-
// if (additional === undefined) {
437
-
// return pipes.push($('v').attr('looseObject').call(shape));
438
-
// }
439
-
// return;
440
-
// },
441
-
},
442
-
string: {
443
-
formats: {
444
-
// date: ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()),
445
-
// 'date-time': ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()),
446
-
},
434
+
// number(ctx) {
435
+
// const { $, plugin, symbols } = ctx;
436
+
// const { v } = symbols;
437
+
// // ctx.nodes.base = () => {
438
+
// // // implement custom base number resolver
439
+
// // }
440
+
// const big = plugin.symbolOnce('Big', {
441
+
// external: 'big.js',
442
+
// importKind: 'default',
443
+
// });
444
+
// return $(v).attr('instance').call(big);
445
+
// },
446
+
object(ctx) {
447
+
const { $, symbols } = ctx;
448
+
const { v } = symbols;
449
+
const additional = ctx.nodes.additionalProperties(ctx);
450
+
if (additional === undefined) {
451
+
const shape = ctx.nodes.shape(ctx);
452
+
ctx.nodes.base = () => $(v).attr('looseObject').call(shape);
453
+
}
447
454
},
448
-
// validator({ $, plugin, schema, v }) {
455
+
// string(ctx) {
456
+
// const { $, schema, symbols } = ctx;
457
+
// const { v } = symbols;
458
+
// if (schema.format === 'date' || schema.format === 'date-time') {
459
+
// ctx.nodes.format = () => $(v).attr('isoDateTime').call();
460
+
// }
461
+
// },
462
+
// validator(ctx) {
463
+
// const { $, plugin, symbols } = ctx;
464
+
// const { schema, v } = symbols;
449
465
// const vShadow = plugin.symbol('v');
450
466
// const test = plugin.symbol('test');
451
467
// const e = plugin.symbol('err');
···
467
483
{
468
484
// case: 'snake_case',
469
485
// comments: false,
470
-
compatibilityVersion: 3,
486
+
compatibilityVersion: 'mini',
471
487
dates: {
472
488
// local: true,
473
489
// offset: true,
···
516
532
},
517
533
},
518
534
'~resolvers': {
519
-
object: {
520
-
// base({ $, additional, shape }) {
521
-
// if (!additional) {
522
-
// // return $('z').attr('object').call(shape).attr('passthrough').call()
523
-
// return $('z').attr('object').call(shape).attr('strict').call();
524
-
// }
525
-
// return;
526
-
// },
527
-
},
528
-
string: {
529
-
formats: {
530
-
// date: ({ $ }) => $('z').attr('date').call(),
531
-
// 'date-time': ({ $ }) => $('z').attr('date').call(),
532
-
// int64: ({ $ }) => $('z').attr('string').call().attr('refine').call(
533
-
// $.func().param('val').do(
534
-
// $.try(
535
-
// $('z').attr('int64').call().attr('parse').call($('BigInt').call('val')),
536
-
// $.return($.literal(true))
537
-
// ).catch(
538
-
// $.return($.literal(false))
539
-
// ),
540
-
// ),
541
-
// $.object().prop('message', $.literal('Must be a valid int64 string'))
542
-
// ),
543
-
},
544
-
},
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
+
// },
545
590
// validator({ $, schema }) {
546
591
// return [
547
592
// $.const('parsed').assign(
+10
docs/.vitepress/config/en.ts
+10
docs/.vitepress/config/en.ts
···
259
259
collapsed: true,
260
260
items: [
261
261
{
262
+
link: '/openapi-ts/plugins/concepts/resolvers',
263
+
text: 'Resolvers',
264
+
},
265
+
],
266
+
text: 'Concepts',
267
+
},
268
+
{
269
+
collapsed: true,
270
+
items: [
271
+
{
262
272
link: '/openapi-ts/plugins/custom',
263
273
text: 'Plugin',
264
274
},
+2
-2
docs/openapi-ts/clients.md
+2
-2
docs/openapi-ts/clients.md
···
1
1
---
2
2
title: Clients
3
-
description: REST clients for Hey API. Compatible with all our features.
3
+
description: HTTP clients for Hey API. Compatible with all our features.
4
4
---
5
5
6
6
<script setup lang="ts">
7
7
import { embedProject } from '../embed'
8
8
</script>
9
9
10
-
# REST Clients
10
+
# HTTP Clients
11
11
12
12
We all send HTTP requests in a slightly different way. Hey API doesn't force you to use any specific technology. What we do, however, is support your choice with great clients. All seamlessly integrated with our other features.
13
13
+6
docs/openapi-ts/migrating.md
+6
docs/openapi-ts/migrating.md
···
7
7
8
8
While we try to avoid breaking changes, sometimes it's unavoidable in order to offer you the latest features. This page lists changes that require updates to your code. If you run into a problem with migration, please [open an issue](https://github.com/hey-api/openapi-ts/issues).
9
9
10
+
## v0.90.0
11
+
12
+
### Resolvers API
13
+
14
+
The [Resolvers API](/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](/openapi-ts/plugins/concepts/resolvers) page.
15
+
10
16
## v0.89.0
11
17
12
18
### Prefer named exports
+181
docs/openapi-ts/plugins/concepts/resolvers.md
+181
docs/openapi-ts/plugins/concepts/resolvers.md
···
1
+
---
2
+
title: Resolvers
3
+
description: Understand the concepts behind plugins.
4
+
---
5
+
6
+
# Resolvers
7
+
8
+
Sometimes the default plugin behavior isn't what you need or expect. Resolvers let you patch plugins in a safe and performant way, without forking or reimplementing core logic.
9
+
10
+
Currently available for [Valibot](/openapi-ts/plugins/valibot) and [Zod](/openapi-ts/plugins/zod).
11
+
12
+
## Examples
13
+
14
+
This page demonstrates resolvers through a few common scenarios.
15
+
16
+
1. [Handle arbitrary schema formats](#example-1)
17
+
2. [Validate high precision numbers](#example-2)
18
+
3. [Replace default base](#example-3)
19
+
20
+
## Terminology
21
+
22
+
Before we look at examples, let's go through the resolvers API to help you understand how they work. Plugins that support resolvers expose them through the `~resolvers` option. Each resolver is a function that receives context and returns an implemented node (or patches existing ones).
23
+
24
+
The resolver context will usually contain:
25
+
26
+
- `$` - The node builder interface. Use it to build your custom logic.
27
+
- `nodes` - Parts of the plugin logic. You can use these to avoid reimplementing the functionality, or replace them with custom implementation.
28
+
- `plugin` - The plugin instance. You'll most likely use it to register new symbols.
29
+
- `symbols` - Frequently used symbols. These are effectively shorthands for commonly used `plugin.referenceSymbol()` calls.
30
+
31
+
Other fields may include the current schema or relevant utilities.
32
+
33
+
## Example 1
34
+
35
+
### Handle arbitrary schema formats
36
+
37
+
By default, the Valibot plugin may produce the following schemas for `date` and `date-time` strings.
38
+
39
+
```js
40
+
export const vDates = v.object({
41
+
created: v.pipe(v.string(), v.isoDate()),
42
+
modified: v.pipe(v.string(), v.isoTimestamp()),
43
+
});
44
+
```
45
+
46
+
We can override this behavior by patching the `nodes.format` function only for strings with `date` or `date-time` formats.
47
+
48
+
```js
49
+
{
50
+
name: 'valibot',
51
+
'~resolvers': {
52
+
string(ctx) {
53
+
const { $, schema, symbols } = ctx;
54
+
const { v } = symbols;
55
+
if (schema.format === 'date' || schema.format === 'date-time') {
56
+
ctx.nodes.format = () => $(v).attr('isoDateTime').call();
57
+
}
58
+
}
59
+
}
60
+
}
61
+
```
62
+
63
+
This applies custom logic with surgical precision, without affecting the rest of the default behavior.
64
+
65
+
::: code-group
66
+
67
+
```js [after]
68
+
export const vDates = v.object({
69
+
created: v.pipe(v.string(), v.isoDateTime()),
70
+
modified: v.pipe(v.string(), v.isoDateTime()),
71
+
});
72
+
```
73
+
74
+
```js [before]
75
+
export const vDates = v.object({
76
+
created: v.pipe(v.string(), v.isoDate()),
77
+
modified: v.pipe(v.string(), v.isoTimestamp()),
78
+
});
79
+
```
80
+
81
+
:::
82
+
83
+
## Example 2
84
+
85
+
### Validate high precision numbers
86
+
87
+
Let's say you're dealing with very large or unsafe numbers.
88
+
89
+
```js
90
+
export const vAmount = v.number();
91
+
```
92
+
93
+
In this case, you'll want to use a third-party library to validate your values. We can use big.js to validate all numbers by replacing the whole resolver.
94
+
95
+
```js
96
+
{
97
+
name: 'valibot',
98
+
'~resolvers': {
99
+
number(ctx) {
100
+
const { $, plugin, symbols } = ctx;
101
+
const { v } = symbols;
102
+
const big = plugin.symbolOnce('Big', {
103
+
external: 'big.js',
104
+
importKind: 'default',
105
+
});
106
+
return $(v).attr('instance').call(big);
107
+
}
108
+
}
109
+
}
110
+
```
111
+
112
+
We're calling `plugin.symbolOnce()` to ensure we always use the same symbol reference.
113
+
114
+
::: code-group
115
+
116
+
```js [after]
117
+
import Big from 'big.js';
118
+
119
+
export const vAmount = v.instance(Big);
120
+
```
121
+
122
+
```js [before]
123
+
export const vAmount = v.number();
124
+
```
125
+
126
+
:::
127
+
128
+
## Example 3
129
+
130
+
### Replace default base
131
+
132
+
You might want to replace the default base schema, e.g. `v.object()`.
133
+
134
+
```js
135
+
export const vUser = v.object({
136
+
age: v.number(),
137
+
});
138
+
```
139
+
140
+
Let's say we want to interpret any schema without explicitly defined additional properties as a loose object.
141
+
142
+
```js
143
+
{
144
+
name: 'valibot',
145
+
'~resolvers': {
146
+
object(ctx) {
147
+
const { $, symbols } = ctx;
148
+
const { v } = symbols;
149
+
const additional = ctx.nodes.additionalProperties(ctx);
150
+
if (additional === undefined) {
151
+
const shape = ctx.nodes.shape(ctx);
152
+
ctx.nodes.base = () => $(v).attr('looseObject').call(shape);
153
+
}
154
+
}
155
+
}
156
+
}
157
+
```
158
+
159
+
Above we demonstrate patching a node based on the result of another node.
160
+
161
+
::: code-group
162
+
163
+
```js [after]
164
+
export const vUser = v.looseObject({
165
+
age: v.number(),
166
+
});
167
+
```
168
+
169
+
```js [before]
170
+
export const vUser = v.object({
171
+
age: v.number(),
172
+
});
173
+
```
174
+
175
+
:::
176
+
177
+
## Feedback
178
+
179
+
We welcome feedback on the Resolvers API. [Open a GitHub issue](https://github.com/hey-api/openapi-ts/issues) to request support for additional plugins.
180
+
181
+
<!--@include: ../../../partials/sponsors.md-->
+4
docs/openapi-ts/plugins/valibot.md
+4
docs/openapi-ts/plugins/valibot.md
···
207
207
208
208
:::
209
209
210
+
## Resolvers
211
+
212
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
213
+
210
214
## API
211
215
212
216
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/valibot/types.d.ts) interface.
+4
docs/openapi-ts/plugins/zod.md
+4
docs/openapi-ts/plugins/zod.md
···
299
299
300
300
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
301
301
302
+
## Resolvers
303
+
304
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
305
+
302
306
## API
303
307
304
308
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface.
+4
docs/openapi-ts/plugins/zod/mini.md
+4
docs/openapi-ts/plugins/zod/mini.md
···
312
312
313
313
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
314
314
315
+
## Resolvers
316
+
317
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
318
+
315
319
## API
316
320
317
321
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface.
+4
docs/openapi-ts/plugins/zod/v3.md
+4
docs/openapi-ts/plugins/zod/v3.md
···
310
310
311
311
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
312
312
313
+
## Resolvers
314
+
315
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
316
+
313
317
## API
314
318
315
319
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface.
+4
packages/codegen-core/src/symbols/types.d.ts
+4
packages/codegen-core/src/symbols/types.d.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts
···
630
630
});
631
631
632
632
export const vPageable = v.object({
633
-
page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2147483648'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'), v.minValue(0)), 0),
634
-
size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2147483648'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'), v.minValue(1))),
633
+
page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647')), 0),
634
+
size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'))),
635
635
sort: v.optional(v.array(v.string()))
636
636
});
637
637
···
962
962
}), v.strictObject({
963
963
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
964
964
})]), v.object({
965
-
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535'), v.minValue(0)), v.null()]),
966
-
qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'), v.minValue(0))
965
+
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535')), v.null()]),
966
+
qux: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'))
967
967
})]);
968
968
969
969
export const vModelWithOneOfAndProperties = v.intersect([v.union([vSimpleParameter, vNonAsciiStringæøåÆøÅöôêÊ字符串]), v.object({
970
-
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535'), v.minValue(0)), v.null()]),
971
-
qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'), v.minValue(0))
970
+
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535')), v.null()]),
971
+
qux: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'))
972
972
})]);
973
973
974
974
/**
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts
···
630
630
});
631
631
632
632
export const vPageable = v.object({
633
-
page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2147483648'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'), v.minValue(0)), 0),
634
-
size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(-2147483648, 'Invalid value: Expected int32 to be >= -2147483648'), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'), v.minValue(1))),
633
+
page: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647')), 0),
634
+
size: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(2147483647, 'Invalid value: Expected int32 to be <= 2147483647'))),
635
635
sort: v.optional(v.array(v.string()))
636
636
});
637
637
···
972
972
}), v.strictObject({
973
973
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
974
974
})]), v.object({
975
-
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535'), v.minValue(0)), v.null()]),
976
-
qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'), v.minValue(0))
975
+
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535')), v.null()]),
976
+
qux: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'))
977
977
})]);
978
978
979
979
export const vModelWithOneOfAndProperties = v.intersect([v.union([vSimpleParameter, vNonAsciiStringæøåÆøÅöôêÊ字符串]), v.object({
980
-
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint16 to be >= 0'), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535'), v.minValue(0)), v.null()]),
981
-
qux: v.pipe(v.number(), v.integer(), v.minValue(0, 'Invalid value: Expected uint8 to be >= 0'), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'), v.minValue(0))
980
+
baz: v.union([v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(65535, 'Invalid value: Expected uint16 to be <= 65535')), v.null()]),
981
+
qux: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(255, 'Invalid value: Expected uint8 to be <= 255'))
982
982
})]);
983
983
984
984
/**
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/validators-bigint-min-max/valibot.gen.ts
···
7
7
v.number(),
8
8
v.string(),
9
9
v.bigint()
10
-
]), v.transform(x => BigInt(x)), v.minValue(BigInt('-9223372036854775808'), 'Invalid value: Expected int64 to be >= -9223372036854775808'), v.maxValue(BigInt('9223372036854775807'), 'Invalid value: Expected int64 to be <= 9223372036854775807'), v.minValue(BigInt(0)), v.maxValue(BigInt(100))))
10
+
]), v.transform(x => BigInt(x)), v.minValue(BigInt(0)), v.maxValue(BigInt(100))))
11
11
});
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/default/zod.gen.ts
···
710
710
});
711
711
712
712
export const zPageable = z.object({
713
-
page: z._default(z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(0))), 0),
714
-
size: z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(1))),
713
+
page: z._default(z.optional(z.int().check(z.gte(0), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))), 0),
714
+
size: z.optional(z.int().check(z.gte(1), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))),
715
715
sort: z.optional(z.array(z.string()))
716
716
});
717
717
···
1127
1127
})
1128
1128
]), z.object({
1129
1129
baz: z.union([
1130
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1130
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1131
1131
z.null()
1132
1132
]),
1133
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1133
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1134
1134
}));
1135
1135
1136
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
1139
]), z.object({
1140
1140
baz: z.union([
1141
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1141
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1142
1142
z.null()
1143
1143
]),
1144
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1144
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1145
1145
}));
1146
1146
1147
1147
/**
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts
···
708
708
});
709
709
710
710
export const zPageable = z.object({
711
-
page: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0).optional().default(0),
712
-
size: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1).optional(),
711
+
page: z.number().int().gte(0).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional().default(0),
712
+
size: z.number().int().gte(1).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
713
713
sort: z.array(z.string()).optional()
714
714
});
715
715
···
1125
1125
})
1126
1126
]), z.object({
1127
1127
baz: z.union([
1128
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1128
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1129
1129
z.null()
1130
1130
]),
1131
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1131
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1132
1132
}));
1133
1133
1134
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
1137
]), z.object({
1138
1138
baz: z.union([
1139
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1139
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1140
1140
z.null()
1141
1141
]),
1142
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1142
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1143
1143
}));
1144
1144
1145
1145
/**
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts
···
710
710
});
711
711
712
712
export const zPageable = z.object({
713
-
page: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0)).default(0),
714
-
size: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1)),
713
+
page: z.optional(z.int().gte(0).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })).default(0),
714
+
size: z.optional(z.int().gte(1).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })),
715
715
sort: z.optional(z.array(z.string()))
716
716
});
717
717
···
1127
1127
})
1128
1128
]), z.object({
1129
1129
baz: z.union([
1130
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1130
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1131
1131
z.null()
1132
1132
]),
1133
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1133
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1134
1134
}));
1135
1135
1136
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
1139
]), z.object({
1140
1140
baz: z.union([
1141
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1141
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1142
1142
z.null()
1143
1143
]),
1144
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1144
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1145
1145
}));
1146
1146
1147
1147
/**
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/default/zod.gen.ts
···
707
707
});
708
708
709
709
export const zPageable = z.object({
710
-
page: z._default(z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(0))), 0),
711
-
size: z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(1))),
710
+
page: z._default(z.optional(z.int().check(z.gte(0), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))), 0),
711
+
size: z.optional(z.int().check(z.gte(1), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))),
712
712
sort: z.optional(z.array(z.string()))
713
713
});
714
714
···
1133
1133
})
1134
1134
]), z.object({
1135
1135
baz: z.union([
1136
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1136
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1137
1137
z.null()
1138
1138
]),
1139
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1139
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1140
1140
}));
1141
1141
1142
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
1145
]), z.object({
1146
1146
baz: z.union([
1147
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1147
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1148
1148
z.null()
1149
1149
]),
1150
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1150
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1151
1151
}));
1152
1152
1153
1153
/**
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/validators-bigint-min-max/zod.gen.ts
···
3
3
import * as z from 'zod/v4-mini';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.optional(z.coerce.bigint().check(z.minimum(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }), z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }), z.gte(BigInt(0)), z.lte(BigInt(100))))
6
+
foo: z.optional(z.coerce.bigint().check(z.gte(BigInt(0)), z.lte(BigInt(100))))
7
7
});
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts
···
705
705
});
706
706
707
707
export const zPageable = z.object({
708
-
page: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0).optional().default(0),
709
-
size: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1).optional(),
708
+
page: z.number().int().gte(0).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional().default(0),
709
+
size: z.number().int().gte(1).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
710
710
sort: z.array(z.string()).optional()
711
711
});
712
712
···
1131
1131
})
1132
1132
]), z.object({
1133
1133
baz: z.union([
1134
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1134
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1135
1135
z.null()
1136
1136
]),
1137
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1137
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1138
1138
}));
1139
1139
1140
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
1143
]), z.object({
1144
1144
baz: z.union([
1145
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1145
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1146
1146
z.null()
1147
1147
]),
1148
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1148
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1149
1149
}));
1150
1150
1151
1151
/**
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/validators-bigint-min-max/zod.gen.ts
···
3
3
import { z } from 'zod';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).gte(BigInt(0)).lte(BigInt(100)).optional()
6
+
foo: z.coerce.bigint().gte(BigInt(0)).lte(BigInt(100)).optional()
7
7
});
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts
···
707
707
});
708
708
709
709
export const zPageable = z.object({
710
-
page: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0)).default(0),
711
-
size: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1)),
710
+
page: z.optional(z.int().gte(0).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })).default(0),
711
+
size: z.optional(z.int().gte(1).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })),
712
712
sort: z.optional(z.array(z.string()))
713
713
});
714
714
···
1133
1133
})
1134
1134
]), z.object({
1135
1135
baz: z.union([
1136
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1136
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1137
1137
z.null()
1138
1138
]),
1139
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1139
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1140
1140
}));
1141
1141
1142
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
1145
]), z.object({
1146
1146
baz: z.union([
1147
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1147
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1148
1148
z.null()
1149
1149
]),
1150
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1150
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1151
1151
}));
1152
1152
1153
1153
/**
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/validators-bigint-min-max/zod.gen.ts
···
3
3
import { z } from 'zod/v4';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.optional(z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).gte(BigInt(0)).lte(BigInt(100)))
6
+
foo: z.optional(z.coerce.bigint().gte(BigInt(0)).lte(BigInt(100)))
7
7
});
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/default/zod.gen.ts
···
710
710
});
711
711
712
712
export const zPageable = z.object({
713
-
page: z._default(z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(0))), 0),
714
-
size: z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(1))),
713
+
page: z._default(z.optional(z.int().check(z.gte(0), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))), 0),
714
+
size: z.optional(z.int().check(z.gte(1), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))),
715
715
sort: z.optional(z.array(z.string()))
716
716
});
717
717
···
1127
1127
})
1128
1128
]), z.object({
1129
1129
baz: z.union([
1130
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1130
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1131
1131
z.null()
1132
1132
]),
1133
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1133
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1134
1134
}));
1135
1135
1136
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
1139
]), z.object({
1140
1140
baz: z.union([
1141
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1141
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1142
1142
z.null()
1143
1143
]),
1144
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1144
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1145
1145
}));
1146
1146
1147
1147
/**
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/default/zod.gen.ts
···
708
708
});
709
709
710
710
export const zPageable = z.object({
711
-
page: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0).optional().default(0),
712
-
size: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1).optional(),
711
+
page: z.number().int().gte(0).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional().default(0),
712
+
size: z.number().int().gte(1).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
713
713
sort: z.array(z.string()).optional()
714
714
});
715
715
···
1125
1125
})
1126
1126
]), z.object({
1127
1127
baz: z.union([
1128
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1128
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1129
1129
z.null()
1130
1130
]),
1131
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1131
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1132
1132
}));
1133
1133
1134
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
1137
]), z.object({
1138
1138
baz: z.union([
1139
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1139
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1140
1140
z.null()
1141
1141
]),
1142
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1142
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1143
1143
}));
1144
1144
1145
1145
/**
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/default/zod.gen.ts
···
710
710
});
711
711
712
712
export const zPageable = z.object({
713
-
page: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0)).default(0),
714
-
size: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1)),
713
+
page: z.optional(z.int().gte(0).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })).default(0),
714
+
size: z.optional(z.int().gte(1).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })),
715
715
sort: z.optional(z.array(z.string()))
716
716
});
717
717
···
1127
1127
})
1128
1128
]), z.object({
1129
1129
baz: z.union([
1130
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1130
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1131
1131
z.null()
1132
1132
]),
1133
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1133
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1134
1134
}));
1135
1135
1136
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
1139
]), z.object({
1140
1140
baz: z.union([
1141
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1141
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1142
1142
z.null()
1143
1143
]),
1144
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1144
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1145
1145
}));
1146
1146
1147
1147
/**
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/default/zod.gen.ts
···
707
707
});
708
708
709
709
export const zPageable = z.object({
710
-
page: z._default(z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(0))), 0),
711
-
size: z.optional(z.int().check(z.minimum(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }), z.gte(1))),
710
+
page: z._default(z.optional(z.int().check(z.gte(0), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))), 0),
711
+
size: z.optional(z.int().check(z.gte(1), z.maximum(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }))),
712
712
sort: z.optional(z.array(z.string()))
713
713
});
714
714
···
1133
1133
})
1134
1134
]), z.object({
1135
1135
baz: z.union([
1136
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1136
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1137
1137
z.null()
1138
1138
]),
1139
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1139
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1140
1140
}));
1141
1141
1142
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
1145
]), z.object({
1146
1146
baz: z.union([
1147
-
z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint16 to be >= 0' }), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }), z.gte(0)),
1147
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1148
1148
z.null()
1149
1149
]),
1150
-
qux: z.int().check(z.minimum(0, { error: 'Invalid value: Expected uint8 to be >= 0' }), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }), z.gte(0))
1150
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1151
1151
}));
1152
1152
1153
1153
/**
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/validators-bigint-min-max/zod.gen.ts
···
3
3
import * as z from 'zod/mini';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.optional(z.coerce.bigint().check(z.minimum(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }), z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }), z.gte(BigInt(0)), z.lte(BigInt(100))))
6
+
foo: z.optional(z.coerce.bigint().check(z.gte(BigInt(0)), z.lte(BigInt(100))))
7
7
});
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/default/zod.gen.ts
···
705
705
});
706
706
707
707
export const zPageable = z.object({
708
-
page: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0).optional().default(0),
709
-
size: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1).optional(),
708
+
page: z.number().int().gte(0).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional().default(0),
709
+
size: z.number().int().gte(1).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
710
710
sort: z.array(z.string()).optional()
711
711
});
712
712
···
1131
1131
})
1132
1132
]), z.object({
1133
1133
baz: z.union([
1134
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1134
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1135
1135
z.null()
1136
1136
]),
1137
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1137
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1138
1138
}));
1139
1139
1140
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
1143
]), z.object({
1144
1144
baz: z.union([
1145
-
z.number().int().min(0, { message: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1145
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1146
1146
z.null()
1147
1147
]),
1148
-
qux: z.number().int().min(0, { message: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1148
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1149
1149
}));
1150
1150
1151
1151
/**
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/validators-bigint-min-max/zod.gen.ts
···
3
3
import { z } from 'zod/v3';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).gte(BigInt(0)).lte(BigInt(100)).optional()
6
+
foo: z.coerce.bigint().gte(BigInt(0)).lte(BigInt(100)).optional()
7
7
});
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts
+6
-6
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/default/zod.gen.ts
···
707
707
});
708
708
709
709
export const zPageable = z.object({
710
-
page: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(0)).default(0),
711
-
size: z.optional(z.int().min(-2147483648, { error: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' }).gte(1)),
710
+
page: z.optional(z.int().gte(0).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })).default(0),
711
+
size: z.optional(z.int().gte(1).max(2147483647, { error: 'Invalid value: Expected int32 to be <= 2147483647' })),
712
712
sort: z.optional(z.array(z.string()))
713
713
});
714
714
···
1133
1133
})
1134
1134
]), z.object({
1135
1135
baz: z.union([
1136
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1136
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1137
1137
z.null()
1138
1138
]),
1139
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1139
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1140
1140
}));
1141
1141
1142
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
1145
]), z.object({
1146
1146
baz: z.union([
1147
-
z.int().min(0, { error: 'Invalid value: Expected uint16 to be >= 0' }).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }).gte(0),
1147
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1148
1148
z.null()
1149
1149
]),
1150
-
qux: z.int().min(0, { error: 'Invalid value: Expected uint8 to be >= 0' }).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' }).gte(0)
1150
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1151
1151
}));
1152
1152
1153
1153
/**
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-bigint-min-max/zod.gen.ts
+1
-1
packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/validators-bigint-min-max/zod.gen.ts
···
3
3
import { z } from 'zod';
4
4
5
5
export const zFoo = z.object({
6
-
foo: z.optional(z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).gte(BigInt(0)).lte(BigInt(100)))
6
+
foo: z.optional(z.coerce.bigint().gte(BigInt(0)).lte(BigInt(100)))
7
7
});
-8
packages/openapi-ts/src/plugins/arktype/v2/toAst/object.ts
-8
packages/openapi-ts/src/plugins/arktype/v2/toAst/object.ts
+120
-93
packages/openapi-ts/src/plugins/valibot/types.d.ts
+120
-93
packages/openapi-ts/src/plugins/valibot/types.d.ts
···
1
-
import type { Symbol } from '@hey-api/codegen-core';
2
-
import type ts from 'typescript';
1
+
import type { Refs, Symbol } from '@hey-api/codegen-core';
3
2
4
3
import type { IR } from '~/ir/types';
5
-
import type { DefinePlugin, Plugin } from '~/plugins';
6
-
import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl';
4
+
import type { DefinePlugin, Plugin, SchemaWithType } from '~/plugins';
5
+
import type {
6
+
MaybeBigInt,
7
+
ShouldCoerceToBigInt,
8
+
} from '~/plugins/shared/utils/coerce';
9
+
import type { GetIntegerLimit } from '~/plugins/shared/utils/formats';
10
+
import type { $, DollarTsDsl } from '~/ts-dsl';
7
11
import type { StringCase, StringName } from '~/types/case';
8
-
import type { MaybeArray } from '~/types/utils';
9
12
10
13
import type { IApi } from './api';
14
+
import type { Pipe, PipeResult, PipesUtils } from './shared/pipes';
15
+
import type { Ast, PluginState } from './shared/types';
11
16
12
17
export type UserConfig = Plugin.Name<'valibot'> &
13
18
Plugin.Hooks &
···
321
326
};
322
327
};
323
328
324
-
type SharedResolverArgs = DollarTsDsl & {
329
+
interface BaseResolverContext extends DollarTsDsl {
325
330
/**
326
-
* The current builder state being processed by this resolver.
327
-
*
328
-
* In Valibot, this represents the current list of call expressions ("pipes")
329
-
* being assembled to form a schema definition.
330
-
*
331
-
* Each pipe can be extended, modified, or replaced to customize how the
332
-
* resulting schema is constructed. Returning `undefined` from a resolver will
333
-
* use the default generation behavior.
331
+
* Functions for working with pipes.
334
332
*/
335
-
pipes: Array<ReturnType<typeof $.call>>;
333
+
pipes: PipesUtils & {
334
+
/**
335
+
* The current pipe.
336
+
*
337
+
* In Valibot, this represents a list of call expressions ("pipes")
338
+
* being assembled to form a schema definition.
339
+
*
340
+
* Each pipe can be extended, modified, or replaced to customize
341
+
* the resulting schema.
342
+
*/
343
+
current: Pipes;
344
+
};
345
+
/**
346
+
* The plugin instance.
347
+
*/
336
348
plugin: ValibotPlugin['Instance'];
337
-
};
349
+
/**
350
+
* Provides access to commonly used symbols within the plugin.
351
+
*/
352
+
symbols: {
353
+
v: Symbol;
354
+
};
355
+
}
338
356
339
-
export type FormatResolverArgs = SharedResolverArgs & {
340
-
schema: IR.SchemaObject;
341
-
};
357
+
export interface NumberResolverContext extends BaseResolverContext {
358
+
/**
359
+
* Nodes used to build different parts of the number schema.
360
+
*/
361
+
nodes: {
362
+
base: (ctx: NumberResolverContext) => PipeResult;
363
+
const: (ctx: NumberResolverContext) => PipeResult | undefined;
364
+
max: (ctx: NumberResolverContext) => PipeResult | undefined;
365
+
min: (ctx: NumberResolverContext) => PipeResult | undefined;
366
+
};
367
+
schema: SchemaWithType<'integer' | 'number'>;
368
+
/**
369
+
* Utility functions for number schema processing.
370
+
*/
371
+
utils: {
372
+
getIntegerLimit: GetIntegerLimit;
373
+
maybeBigInt: MaybeBigInt;
374
+
shouldCoerceToBigInt: ShouldCoerceToBigInt;
375
+
};
376
+
}
342
377
343
-
export type ObjectBaseResolverArgs = SharedResolverArgs & {
344
-
/** Null = never */
345
-
additional?: ReturnType<typeof $.call | typeof $.expr> | null;
346
-
schema: IR.SchemaObject;
347
-
shape: ReturnType<typeof $.object>;
348
-
};
378
+
export interface ObjectResolverContext extends BaseResolverContext {
379
+
/**
380
+
* Nodes used to build different parts of the object schema.
381
+
*/
382
+
nodes: {
383
+
/**
384
+
* If `additionalProperties` is `false` or `{ type: 'never' }`, returns `null`
385
+
* to indicate no additional properties are allowed.
386
+
*/
387
+
additionalProperties: (
388
+
ctx: ObjectResolverContext,
389
+
) => Pipe | null | undefined;
390
+
base: (ctx: ObjectResolverContext) => PipeResult;
391
+
shape: (ctx: ObjectResolverContext) => ReturnType<typeof $.object>;
392
+
};
393
+
schema: SchemaWithType<'object'>;
394
+
/**
395
+
* Utility functions for object schema processing.
396
+
*/
397
+
utils: {
398
+
ast: Partial<Omit<Ast, 'typeName'>>;
399
+
state: Refs<PluginState>;
400
+
};
401
+
}
349
402
350
-
export type ValidatorResolverArgs = SharedResolverArgs & {
403
+
export interface StringResolverContext extends BaseResolverContext {
404
+
/**
405
+
* Nodes used to build different parts of the string schema.
406
+
*/
407
+
nodes: {
408
+
base: (ctx: StringResolverContext) => PipeResult;
409
+
const: (ctx: StringResolverContext) => PipeResult | undefined;
410
+
format: (ctx: StringResolverContext) => PipeResult | undefined;
411
+
length: (ctx: StringResolverContext) => PipeResult | undefined;
412
+
maxLength: (ctx: StringResolverContext) => PipeResult | undefined;
413
+
minLength: (ctx: StringResolverContext) => PipeResult | undefined;
414
+
pattern: (ctx: StringResolverContext) => PipeResult | undefined;
415
+
};
416
+
schema: SchemaWithType<'string'>;
417
+
}
418
+
419
+
export interface ValidatorResolverContext extends BaseResolverContext {
351
420
operation: IR.Operation;
352
-
schema: Symbol;
353
-
v: Symbol;
354
-
};
421
+
/**
422
+
* Provides access to commonly used symbols within the plugin.
423
+
*/
424
+
symbols: BaseResolverContext['symbols'] & {
425
+
schema: Symbol;
426
+
};
427
+
}
355
428
356
429
type ValidatorResolver = (
357
-
args: ValidatorResolverArgs,
358
-
) => MaybeArray<TsDsl<ts.Statement>> | null | undefined;
430
+
ctx: ValidatorResolverContext,
431
+
) => PipeResult | null | undefined;
359
432
360
433
type Resolvers = Plugin.Resolvers<{
361
434
/**
362
-
* Resolvers for number schemas.
435
+
* Resolver for number schemas.
363
436
*
364
-
* Allows customization of how number types are rendered, including
365
-
* per-format handling.
437
+
* Allows customization of how number types are rendered.
438
+
*
439
+
* Returning `undefined` will execute the default resolver logic.
366
440
*/
367
-
number?: {
368
-
/**
369
-
* Resolvers for number formats (e.g., `float`, `double`, `int32`).
370
-
*
371
-
* Each key represents a specific format name with a custom
372
-
* resolver function that controls how that format is rendered.
373
-
*
374
-
* Example path: `~resolvers.number.formats.float`
375
-
*
376
-
* Returning `undefined` from a resolver will apply the default
377
-
* generation behavior for that format.
378
-
*/
379
-
formats?: Record<
380
-
string,
381
-
(args: FormatResolverArgs) => boolean | number | undefined
382
-
>;
383
-
};
441
+
number?: (ctx: NumberResolverContext) => PipeResult | undefined;
384
442
/**
385
-
* Resolvers for object schemas.
443
+
* Resolver for object schemas.
386
444
*
387
445
* Allows customization of how object types are rendered.
388
446
*
389
-
* Example path: `~resolvers.object.base`
390
-
*
391
-
* Returning `undefined` from a resolver will apply the default
392
-
* generation behavior for the object schema.
447
+
* Returning `undefined` will execute the default resolver logic.
393
448
*/
394
-
object?: {
395
-
/**
396
-
* Controls how object schemas are constructed.
397
-
*
398
-
* Called with the fully assembled shape (properties) and any additional
399
-
* property schema, allowing the resolver to choose the correct Valibot
400
-
* base constructor and modify the schema chain if needed.
401
-
*
402
-
* Returning `undefined` will execute the default resolver logic.
403
-
*/
404
-
base?: (
405
-
args: ObjectBaseResolverArgs,
406
-
) => ReturnType<typeof $.call> | undefined;
407
-
};
449
+
object?: (ctx: ObjectResolverContext) => PipeResult | undefined;
408
450
/**
409
-
* Resolvers for string schemas.
451
+
* Resolver for string schemas.
410
452
*
411
-
* Allows customization of how string types are rendered, including
412
-
* per-format handling.
453
+
* Allows customization of how string types are rendered.
454
+
*
455
+
* Returning `undefined` will execute the default resolver logic.
413
456
*/
414
-
string?: {
415
-
/**
416
-
* Resolvers for string formats (e.g., `uuid`, `email`, `date-time`).
417
-
*
418
-
* Each key represents a specific format name with a custom
419
-
* resolver function that controls how that format is rendered.
420
-
*
421
-
* Example path: `~resolvers.string.formats.uuid`
422
-
*
423
-
* Returning `undefined` from a resolver will apply the default
424
-
* generation behavior for that format.
425
-
*/
426
-
formats?: Record<
427
-
string,
428
-
(args: FormatResolverArgs) => boolean | number | undefined
429
-
>;
430
-
};
457
+
string?: (ctx: StringResolverContext) => PipeResult | undefined;
431
458
/**
432
459
* Resolvers for request and response validators.
433
460
*
···
435
462
*
436
463
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
437
464
*
438
-
* Returning `undefined` from a resolver will apply the default generation logic.
465
+
* Returning `undefined` will execute the default resolver logic.
439
466
*/
440
467
validator?:
441
468
| ValidatorResolver
···
443
470
/**
444
471
* Controls how the request validator function body is generated.
445
472
*
446
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
473
+
* Returning `undefined` will execute the default resolver logic.
447
474
*/
448
475
request?: ValidatorResolver;
449
476
/**
450
477
* Controls how the response validator function body is generated.
451
478
*
452
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
479
+
* Returning `undefined` will execute the default resolver logic.
453
480
*/
454
481
response?: ValidatorResolver;
455
482
};
+34
-26
packages/openapi-ts/src/plugins/valibot/v1/api.ts
+34
-26
packages/openapi-ts/src/plugins/valibot/v1/api.ts
···
1
1
import { $ } from '~/ts-dsl';
2
2
3
+
import { pipes } from '../shared/pipes';
3
4
import type { ValidatorArgs } from '../shared/types';
4
-
import type { ValidatorResolverArgs } from '../types';
5
+
import type { ValidatorResolverContext } from '../types';
5
6
import { identifiers } from './constants';
6
7
7
-
const defaultValidatorResolver = ({
8
-
schema,
9
-
v,
10
-
}: ValidatorResolverArgs): ReturnType<typeof $.return> =>
11
-
$(v).attr(identifiers.async.parseAsync).call(schema, 'data').await().return();
8
+
const validatorResolver = (
9
+
ctx: ValidatorResolverContext,
10
+
): ReturnType<typeof $.return> => {
11
+
const { schema, v } = ctx.symbols;
12
+
return $(v)
13
+
.attr(identifiers.async.parseAsync)
14
+
.call(schema, 'data')
15
+
.await()
16
+
.return();
17
+
};
12
18
13
19
export const createRequestValidatorV1 = ({
14
20
operation,
···
23
29
});
24
30
if (!symbol) return;
25
31
26
-
const v = plugin.referenceSymbol({
27
-
category: 'external',
28
-
resource: 'valibot.v',
29
-
});
30
-
const args: ValidatorResolverArgs = {
32
+
const ctx: ValidatorResolverContext = {
31
33
$,
32
34
operation,
33
-
pipes: [],
35
+
pipes: {
36
+
...pipes,
37
+
current: [],
38
+
},
34
39
plugin,
35
-
schema: symbol,
36
-
v,
40
+
symbols: {
41
+
schema: symbol,
42
+
v: plugin.external('valibot.v'),
43
+
},
37
44
};
38
45
const validator = plugin.config['~resolvers']?.validator;
39
46
const resolver =
40
47
typeof validator === 'function' ? validator : validator?.request;
41
-
const candidates = [resolver, defaultValidatorResolver];
48
+
const candidates = [resolver, validatorResolver];
42
49
for (const candidate of candidates) {
43
-
const statements = candidate?.(args);
50
+
const statements = candidate?.(ctx);
44
51
if (statements === null) return;
45
52
if (statements !== undefined) {
46
53
return $.func()
···
65
72
});
66
73
if (!symbol) return;
67
74
68
-
const v = plugin.referenceSymbol({
69
-
category: 'external',
70
-
resource: 'valibot.v',
71
-
});
72
-
const args: ValidatorResolverArgs = {
75
+
const ctx: ValidatorResolverContext = {
73
76
$,
74
77
operation,
75
-
pipes: [],
78
+
pipes: {
79
+
...pipes,
80
+
current: [],
81
+
},
76
82
plugin,
77
-
schema: symbol,
78
-
v,
83
+
symbols: {
84
+
schema: symbol,
85
+
v: plugin.external('valibot.v'),
86
+
},
79
87
};
80
88
const validator = plugin.config['~resolvers']?.validator;
81
89
const resolver =
82
90
typeof validator === 'function' ? validator : validator?.response;
83
-
const candidates = [resolver, defaultValidatorResolver];
91
+
const candidates = [resolver, validatorResolver];
84
92
for (const candidate of candidates) {
85
-
const statements = candidate?.(args);
93
+
const statements = candidate?.(ctx);
86
94
if (statements === null) return;
87
95
if (statements !== undefined) {
88
96
return $.func()
+4
-4
packages/openapi-ts/src/plugins/valibot/v1/plugin.ts
+4
-4
packages/openapi-ts/src/plugins/valibot/v1/plugin.ts
···
11
11
12
12
import { exportAst } from '../shared/export';
13
13
import { irOperationToAst } from '../shared/operation';
14
-
import { pipesToAst } from '../shared/pipesToAst';
14
+
import { pipesToNode } from '../shared/pipes';
15
15
import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types';
16
16
import { irWebhookToAst } from '../shared/webhook';
17
17
import type { ValibotPlugin } from '../types';
···
87
87
path: ref([...fromRef(state.path), 'items', index]),
88
88
},
89
89
});
90
-
return pipesToAst(itemAst.pipes, plugin);
90
+
return pipesToNode(itemAst.pipes, plugin);
91
91
});
92
92
93
93
if (schema.logicalOperator === 'and') {
···
129
129
$(v)
130
130
.attr(identifiers.schemas.optional)
131
131
.call(
132
-
pipesToAst(ast.pipes, plugin),
132
+
pipesToNode(ast.pipes, plugin),
133
133
schema.type === 'integer' || schema.type === 'number'
134
134
? maybeBigInt(schema.default, schema.format)
135
135
: $.fromValue(schema.default),
···
139
139
ast.pipes = [
140
140
$(v)
141
141
.attr(identifiers.schemas.optional)
142
-
.call(pipesToAst(ast.pipes, plugin)),
142
+
.call(pipesToNode(ast.pipes, plugin)),
143
143
];
144
144
}
145
145
}
+2
-2
packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts
+2
-2
packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts
···
4
4
import type { SchemaWithType } from '~/plugins';
5
5
import { $ } from '~/ts-dsl';
6
6
7
-
import { pipesToAst } from '../../shared/pipesToAst';
7
+
import { pipesToNode } from '../../shared/pipes';
8
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
9
import { identifiers } from '../constants';
10
10
import { irSchemaToAst } from '../plugin';
···
54
54
if (itemAst.hasLazyExpression) {
55
55
result.hasLazyExpression = true;
56
56
}
57
-
return pipesToAst(itemAst.pipes, plugin);
57
+
return pipesToNode(itemAst.pipes, plugin);
58
58
});
59
59
60
60
if (itemExpressions.length === 1) {
+3
-3
packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts
+3
-3
packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts
···
1
1
import type { SchemaWithType } from '~/plugins';
2
2
import { $ } from '~/ts-dsl';
3
3
4
-
import { pipesToAst } from '../../shared/pipesToAst';
4
+
import { pipesToNode } from '../../shared/pipes';
5
5
import type { IrSchemaToAstOptions } from '../../shared/types';
6
6
import { identifiers } from '../constants';
7
7
···
22
22
pipes.push(
23
23
$(v).attr(identifiers.schemas.literal).call($.literal(schema.const)),
24
24
);
25
-
return pipesToAst(pipes, plugin);
25
+
return pipesToNode(pipes, plugin);
26
26
}
27
27
28
28
pipes.push($(v).attr(identifiers.schemas.boolean).call());
29
-
return pipesToAst(pipes, plugin);
29
+
return pipesToNode(pipes, plugin);
30
30
};
+9
-9
packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts
+9
-9
packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts
···
2
2
import { shouldCoerceToBigInt } from '~/plugins/shared/utils/coerce';
3
3
import type { $ } from '~/ts-dsl';
4
4
5
-
import { pipesToAst } from '../../shared/pipesToAst';
5
+
import { pipesToNode } from '../../shared/pipes';
6
6
import type { IrSchemaToAstOptions } from '../../shared/types';
7
7
import { arrayToAst } from './array';
8
8
import { booleanToAst } from './boolean';
9
9
import { enumToAst } from './enum';
10
10
import { neverToAst } from './never';
11
11
import { nullToAst } from './null';
12
-
import { numberToAst } from './number';
12
+
import { numberToNode } from './number';
13
13
import { objectToAst } from './object';
14
-
import { stringToAst } from './string';
14
+
import { stringToNode } from './string';
15
15
import { tupleToAst } from './tuple';
16
16
import { undefinedToAst } from './undefined';
17
17
import { unknownToAst } from './unknown';
···
29
29
switch (schema.type) {
30
30
case 'array':
31
31
return {
32
-
expression: pipesToAst(
32
+
expression: pipesToNode(
33
33
arrayToAst({
34
34
...args,
35
35
schema: schema as SchemaWithType<'array'>,
···
54
54
case 'integer':
55
55
case 'number':
56
56
return {
57
-
expression: numberToAst({
57
+
expression: numberToNode({
58
58
...args,
59
59
schema: schema as SchemaWithType<'integer' | 'number'>,
60
60
}),
···
75
75
};
76
76
case 'object':
77
77
return {
78
-
expression: pipesToAst(
78
+
expression: pipesToNode(
79
79
objectToAst({
80
80
...args,
81
81
schema: schema as SchemaWithType<'object'>,
···
86
86
case 'string':
87
87
return {
88
88
expression: shouldCoerceToBigInt(schema.format)
89
-
? numberToAst({
89
+
? numberToNode({
90
90
...args,
91
91
schema: { ...schema, type: 'number' },
92
92
})
93
-
: stringToAst({
93
+
: stringToNode({
94
94
...args,
95
95
schema: schema as SchemaWithType<'string'>,
96
96
}),
97
97
};
98
98
case 'tuple':
99
99
return {
100
-
expression: pipesToAst(
100
+
expression: pipesToNode(
101
101
tupleToAst({
102
102
...args,
103
103
schema: schema as SchemaWithType<'tuple'>,
+117
-69
packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts
+117
-69
packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts
···
6
6
import { getIntegerLimit } from '~/plugins/shared/utils/formats';
7
7
import { $ } from '~/ts-dsl';
8
8
9
-
import { pipesToAst } from '../../shared/pipesToAst';
9
+
import type { Pipe, PipeResult, Pipes } from '../../shared/pipes';
10
+
import { pipes } from '../../shared/pipes';
10
11
import type { IrSchemaToAstOptions } from '../../shared/types';
12
+
import type { NumberResolverContext } from '../../types';
11
13
import { identifiers } from '../constants';
12
14
13
-
export const numberToAst = ({
14
-
plugin,
15
-
schema,
16
-
}: IrSchemaToAstOptions & {
17
-
schema: SchemaWithType<'integer' | 'number'>;
18
-
}) => {
19
-
const v = plugin.referenceSymbol({
20
-
category: 'external',
21
-
resource: 'valibot.v',
22
-
});
23
-
24
-
if (schema.const !== undefined) {
25
-
return $(v)
26
-
.attr(identifiers.schemas.literal)
27
-
.call(maybeBigInt(schema.const, schema.format));
28
-
}
29
-
30
-
const pipes: Array<ReturnType<typeof $.call>> = [];
31
-
32
-
if (shouldCoerceToBigInt(schema.format)) {
33
-
pipes.push(
15
+
function baseNode(ctx: NumberResolverContext): PipeResult {
16
+
const { schema, symbols } = ctx;
17
+
const { v } = symbols;
18
+
if (ctx.utils.shouldCoerceToBigInt(schema.format)) {
19
+
return [
34
20
$(v)
35
21
.attr(identifiers.schemas.union)
36
22
.call(
···
43
29
$(v)
44
30
.attr(identifiers.actions.transform)
45
31
.call($.func().param('x').do($('BigInt').call('x').return())),
46
-
);
47
-
} else {
48
-
pipes.push($(v).attr(identifiers.schemas.number).call());
49
-
if (schema.type === 'integer') {
50
-
pipes.push($(v).attr(identifiers.actions.integer).call());
51
-
}
32
+
];
33
+
}
34
+
const pipes: Pipes = [];
35
+
pipes.push($(v).attr(identifiers.schemas.number).call());
36
+
if (schema.type === 'integer') {
37
+
pipes.push($(v).attr(identifiers.actions.integer).call());
52
38
}
39
+
return pipes;
40
+
}
53
41
54
-
const integerLimit = getIntegerLimit(schema.format);
55
-
if (integerLimit) {
56
-
pipes.push(
57
-
$(v)
58
-
.attr(identifiers.actions.minValue)
59
-
.call(
60
-
maybeBigInt(integerLimit.minValue, schema.format),
61
-
$.literal(integerLimit.minError),
62
-
),
63
-
$(v)
64
-
.attr(identifiers.actions.maxValue)
65
-
.call(
66
-
maybeBigInt(integerLimit.maxValue, schema.format),
67
-
$.literal(integerLimit.maxError),
68
-
),
69
-
);
42
+
function constNode(ctx: NumberResolverContext): PipeResult | undefined {
43
+
const { schema, symbols } = ctx;
44
+
const { v } = symbols;
45
+
if (schema.const === undefined) return;
46
+
return $(v)
47
+
.attr(identifiers.schemas.literal)
48
+
.call(ctx.utils.maybeBigInt(schema.const, schema.format));
49
+
}
50
+
51
+
function maxNode(ctx: NumberResolverContext): PipeResult | undefined {
52
+
const { schema, symbols } = ctx;
53
+
const { v } = symbols;
54
+
if (schema.exclusiveMaximum !== undefined) {
55
+
return $(v)
56
+
.attr(identifiers.actions.ltValue)
57
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format));
70
58
}
59
+
if (schema.maximum !== undefined) {
60
+
return $(v)
61
+
.attr(identifiers.actions.maxValue)
62
+
.call(ctx.utils.maybeBigInt(schema.maximum, schema.format));
63
+
}
64
+
const limit = ctx.utils.getIntegerLimit(schema.format);
65
+
if (limit) {
66
+
return $(v)
67
+
.attr(identifiers.actions.maxValue)
68
+
.call(
69
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
70
+
$.literal(limit.maxError),
71
+
);
72
+
}
73
+
return;
74
+
}
71
75
76
+
function minNode(ctx: NumberResolverContext): PipeResult | undefined {
77
+
const { schema, symbols } = ctx;
78
+
const { v } = symbols;
72
79
if (schema.exclusiveMinimum !== undefined) {
73
-
pipes.push(
74
-
$(v)
75
-
.attr(identifiers.actions.gtValue)
76
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format)),
77
-
);
78
-
} else if (schema.minimum !== undefined) {
79
-
pipes.push(
80
-
$(v)
81
-
.attr(identifiers.actions.minValue)
82
-
.call(maybeBigInt(schema.minimum, schema.format)),
83
-
);
80
+
return $(v)
81
+
.attr(identifiers.actions.gtValue)
82
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
83
+
}
84
+
if (schema.minimum !== undefined) {
85
+
return $(v)
86
+
.attr(identifiers.actions.minValue)
87
+
.call(ctx.utils.maybeBigInt(schema.minimum, schema.format));
88
+
}
89
+
const limit = ctx.utils.getIntegerLimit(schema.format);
90
+
if (limit) {
91
+
return $(v)
92
+
.attr(identifiers.actions.minValue)
93
+
.call(
94
+
ctx.utils.maybeBigInt(limit.minValue, schema.format),
95
+
$.literal(limit.minError),
96
+
);
84
97
}
98
+
return;
99
+
}
100
+
101
+
function numberResolver(ctx: NumberResolverContext): Pipes {
102
+
const constNode = ctx.nodes.const(ctx);
103
+
if (constNode) return ctx.pipes.push(ctx.pipes.current, constNode);
85
104
86
-
if (schema.exclusiveMaximum !== undefined) {
87
-
pipes.push(
88
-
$(v)
89
-
.attr(identifiers.actions.ltValue)
90
-
.call(maybeBigInt(schema.exclusiveMaximum, schema.format)),
91
-
);
92
-
} else if (schema.maximum !== undefined) {
93
-
pipes.push(
94
-
$(v)
95
-
.attr(identifiers.actions.maxValue)
96
-
.call(maybeBigInt(schema.maximum, schema.format)),
97
-
);
98
-
}
105
+
const baseNode = ctx.nodes.base(ctx);
106
+
if (baseNode) ctx.pipes.push(ctx.pipes.current, baseNode);
107
+
108
+
const minNode = ctx.nodes.min(ctx);
109
+
if (minNode) ctx.pipes.push(ctx.pipes.current, minNode);
110
+
111
+
const maxNode = ctx.nodes.max(ctx);
112
+
if (maxNode) ctx.pipes.push(ctx.pipes.current, maxNode);
113
+
114
+
return ctx.pipes.current;
115
+
}
99
116
100
-
return pipesToAst(pipes, plugin);
117
+
export const numberToNode = ({
118
+
plugin,
119
+
schema,
120
+
}: IrSchemaToAstOptions & {
121
+
schema: SchemaWithType<'integer' | 'number'>;
122
+
}): Pipe => {
123
+
const ctx: NumberResolverContext = {
124
+
$,
125
+
nodes: {
126
+
base: baseNode,
127
+
const: constNode,
128
+
max: maxNode,
129
+
min: minNode,
130
+
},
131
+
pipes: {
132
+
...pipes,
133
+
current: [],
134
+
},
135
+
plugin,
136
+
schema,
137
+
symbols: {
138
+
v: plugin.external('valibot.v'),
139
+
},
140
+
utils: {
141
+
getIntegerLimit,
142
+
maybeBigInt,
143
+
shouldCoerceToBigInt,
144
+
},
145
+
};
146
+
const resolver = plugin.config['~resolvers']?.number;
147
+
const node = resolver?.(ctx) ?? numberResolver(ctx);
148
+
return ctx.pipes.toNode(node, plugin);
101
149
};
+76
-69
packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts
+76
-69
packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts
···
3
3
import type { SchemaWithType } from '~/plugins';
4
4
import { $ } from '~/ts-dsl';
5
5
6
-
import { pipesToAst } from '../../shared/pipesToAst';
6
+
import type { Pipe, PipeResult } from '../../shared/pipes';
7
+
import { pipes } from '../../shared/pipes';
7
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
-
import type { ObjectBaseResolverArgs } from '../../types';
9
+
import type { ObjectResolverContext } from '../../types';
9
10
import { identifiers } from '../constants';
10
11
import { irSchemaToAst } from '../plugin';
11
12
12
-
function defaultObjectBaseResolver({
13
-
additional,
14
-
pipes,
15
-
plugin,
16
-
shape,
17
-
}: ObjectBaseResolverArgs): number {
18
-
const v = plugin.referenceSymbol({
19
-
category: 'external',
20
-
resource: 'valibot.v',
13
+
function additionalPropertiesNode(
14
+
ctx: ObjectResolverContext,
15
+
): Pipe | null | undefined {
16
+
const { plugin, schema } = ctx;
17
+
18
+
if (!schema.additionalProperties || !schema.additionalProperties.type) return;
19
+
if (schema.additionalProperties.type === 'never') return null;
20
+
21
+
const additionalAst = irSchemaToAst({
22
+
plugin,
23
+
schema: schema.additionalProperties,
24
+
state: {
25
+
...ctx.utils.state,
26
+
path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']),
27
+
},
21
28
});
29
+
if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
30
+
return pipes.toNode(additionalAst.pipes, plugin);
31
+
}
22
32
23
-
// Handle `additionalProperties: { type: 'never' }` → v.strictObject()
33
+
function baseNode(ctx: ObjectResolverContext): PipeResult {
34
+
const { nodes, symbols } = ctx;
35
+
const { v } = symbols;
36
+
37
+
const additional = nodes.additionalProperties(ctx);
38
+
const shape = nodes.shape(ctx);
39
+
24
40
if (additional === null) {
25
-
return pipes.push($(v).attr(identifiers.schemas.strictObject).call(shape));
41
+
return $(v).attr(identifiers.schemas.strictObject).call(shape);
26
42
}
27
43
28
-
// Handle additionalProperties as schema → v.record() or v.objectWithRest()
29
44
if (additional) {
30
45
if (shape.isEmpty) {
31
-
return pipes.push(
32
-
$(v)
33
-
.attr(identifiers.schemas.record)
34
-
.call($(v).attr(identifiers.schemas.string).call(), additional),
35
-
);
46
+
return $(v)
47
+
.attr(identifiers.schemas.record)
48
+
.call($(v).attr(identifiers.schemas.string).call(), additional);
36
49
}
37
50
38
-
// If there are named properties, use v.objectWithRest() to validate both
39
-
return pipes.push(
40
-
$(v).attr(identifiers.schemas.objectWithRest).call(shape, additional),
41
-
);
51
+
return $(v)
52
+
.attr(identifiers.schemas.objectWithRest)
53
+
.call(shape, additional);
42
54
}
43
55
44
-
// Default case → v.object()
45
-
return pipes.push($(v).attr(identifiers.schemas.object).call(shape));
56
+
return $(v).attr(identifiers.schemas.object).call(shape);
46
57
}
47
58
48
-
export const objectToAst = ({
49
-
plugin,
50
-
schema,
51
-
state,
52
-
}: IrSchemaToAstOptions & {
53
-
schema: SchemaWithType<'object'>;
54
-
}): Omit<Ast, 'typeName'> => {
55
-
const result: Partial<Omit<Ast, 'typeName'>> = {};
56
-
const pipes: Array<ReturnType<typeof $.call>> = [];
57
-
59
+
function objectResolver(ctx: ObjectResolverContext): PipeResult {
58
60
// TODO: parser - handle constants
61
+
return ctx.nodes.base(ctx);
62
+
}
59
63
64
+
function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> {
65
+
const { plugin, schema } = ctx;
60
66
const shape = $.object().pretty();
61
-
const required = schema.required ?? [];
62
67
63
68
for (const name in schema.properties) {
64
69
const property = schema.properties[name]!;
65
-
const isRequired = required.includes(name);
66
70
67
71
const propertyAst = irSchemaToAst({
68
-
optional: !isRequired,
72
+
optional: !schema.required?.includes(name),
69
73
plugin,
70
74
schema: property,
71
75
state: {
72
-
...state,
73
-
path: ref([...fromRef(state.path), 'properties', name]),
76
+
...ctx.utils.state,
77
+
path: ref([...fromRef(ctx.utils.state.path), 'properties', name]),
74
78
},
75
79
});
76
-
if (propertyAst.hasLazyExpression) result.hasLazyExpression = true;
77
-
78
-
shape.prop(name, pipesToAst(propertyAst.pipes, plugin));
80
+
if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
81
+
shape.prop(name, pipes.toNode(propertyAst.pipes, plugin));
79
82
}
80
83
81
-
let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined;
82
-
if (schema.additionalProperties && schema.additionalProperties.type) {
83
-
if (schema.additionalProperties.type === 'never') {
84
-
additional = null;
85
-
} else {
86
-
const additionalAst = irSchemaToAst({
87
-
plugin,
88
-
schema: schema.additionalProperties,
89
-
state: {
90
-
...state,
91
-
path: ref([...fromRef(state.path), 'additionalProperties']),
92
-
},
93
-
});
94
-
if (additionalAst.hasLazyExpression) result.hasLazyExpression = true;
95
-
additional = pipesToAst(additionalAst.pipes, plugin);
96
-
}
97
-
}
84
+
return shape;
85
+
}
98
86
99
-
const args: ObjectBaseResolverArgs = {
87
+
export const objectToAst = ({
88
+
plugin,
89
+
schema,
90
+
state,
91
+
}: IrSchemaToAstOptions & {
92
+
schema: SchemaWithType<'object'>;
93
+
}): Omit<Ast, 'typeName'> => {
94
+
const ctx: ObjectResolverContext = {
100
95
$,
101
-
additional,
102
-
pipes,
96
+
nodes: {
97
+
additionalProperties: additionalPropertiesNode,
98
+
base: baseNode,
99
+
shape: shapeNode,
100
+
},
101
+
pipes: {
102
+
...pipes,
103
+
current: [],
104
+
},
103
105
plugin,
104
106
schema,
105
-
shape,
107
+
symbols: {
108
+
v: plugin.external('valibot.v'),
109
+
},
110
+
utils: {
111
+
ast: {},
112
+
state,
113
+
},
106
114
};
107
-
const resolver = plugin.config['~resolvers']?.object?.base;
108
-
if (!resolver?.(args)) defaultObjectBaseResolver(args);
109
-
110
-
result.pipes = [pipesToAst(pipes, plugin)];
111
-
return result as Omit<Ast, 'typeName'>;
115
+
const resolver = plugin.config['~resolvers']?.object;
116
+
const node = resolver?.(ctx) ?? objectResolver(ctx);
117
+
ctx.utils.ast.pipes = [ctx.pipes.toNode(node, plugin)];
118
+
return ctx.utils.ast as Omit<Ast, 'typeName'>;
112
119
};
+109
-65
packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts
+109
-65
packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts
···
1
1
import type { SchemaWithType } from '~/plugins';
2
2
import { $ } from '~/ts-dsl';
3
3
4
-
import { pipesToAst } from '../../shared/pipesToAst';
4
+
import type { Pipe, PipeResult, Pipes } from '../../shared/pipes';
5
+
import { pipes } from '../../shared/pipes';
5
6
import type { IrSchemaToAstOptions } from '../../shared/types';
6
-
import type { FormatResolverArgs } from '../../types';
7
+
import type { StringResolverContext } from '../../types';
7
8
import { identifiers } from '../constants';
8
9
9
-
const defaultFormatResolver = ({
10
-
pipes,
11
-
plugin,
12
-
schema,
13
-
}: FormatResolverArgs): boolean | number => {
14
-
const v = plugin.referenceSymbol({
15
-
category: 'external',
16
-
resource: 'valibot.v',
17
-
});
10
+
function baseNode(ctx: StringResolverContext): PipeResult {
11
+
const { v } = ctx.symbols;
12
+
return $(v).attr(identifiers.schemas.string).call();
13
+
}
18
14
15
+
function constNode(ctx: StringResolverContext): PipeResult | undefined {
16
+
const { schema, symbols } = ctx;
17
+
const { v } = symbols;
18
+
if (typeof schema.const !== 'string') return;
19
+
return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const));
20
+
}
21
+
22
+
function formatNode(ctx: StringResolverContext): PipeResult | undefined {
23
+
const { schema, symbols } = ctx;
24
+
const { v } = symbols;
19
25
switch (schema.format) {
20
26
case 'date':
21
-
return pipes.push($(v).attr(identifiers.actions.isoDate).call());
27
+
return $(v).attr(identifiers.actions.isoDate).call();
22
28
case 'date-time':
23
-
return pipes.push($(v).attr(identifiers.actions.isoTimestamp).call());
29
+
return $(v).attr(identifiers.actions.isoTimestamp).call();
24
30
case 'email':
25
-
return pipes.push($(v).attr(identifiers.actions.email).call());
31
+
return $(v).attr(identifiers.actions.email).call();
26
32
case 'ipv4':
27
33
case 'ipv6':
28
-
return pipes.push($(v).attr(identifiers.actions.ip).call());
34
+
return $(v).attr(identifiers.actions.ip).call();
29
35
case 'time':
30
-
return pipes.push($(v).attr(identifiers.actions.isoTimeSecond).call());
36
+
return $(v).attr(identifiers.actions.isoTimeSecond).call();
31
37
case 'uri':
32
-
return pipes.push($(v).attr(identifiers.actions.url).call());
38
+
return $(v).attr(identifiers.actions.url).call();
33
39
case 'uuid':
34
-
return pipes.push($(v).attr(identifiers.actions.uuid).call());
40
+
return $(v).attr(identifiers.actions.uuid).call();
35
41
}
36
42
37
-
return true;
38
-
};
43
+
return;
44
+
}
39
45
40
-
export const stringToAst = ({
41
-
plugin,
42
-
schema,
43
-
}: IrSchemaToAstOptions & {
44
-
schema: SchemaWithType<'string'>;
45
-
}): ReturnType<typeof $.call | typeof $.expr> => {
46
-
const v = plugin.referenceSymbol({
47
-
category: 'external',
48
-
resource: 'valibot.v',
49
-
});
46
+
function lengthNode(ctx: StringResolverContext): PipeResult | undefined {
47
+
const { schema, symbols } = ctx;
48
+
const { v } = symbols;
49
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
50
+
return;
51
+
return $(v)
52
+
.attr(identifiers.actions.length)
53
+
.call($.literal(schema.minLength));
54
+
}
50
55
51
-
if (typeof schema.const === 'string') {
52
-
return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const));
53
-
}
56
+
function maxLengthNode(ctx: StringResolverContext): PipeResult | undefined {
57
+
const { schema, symbols } = ctx;
58
+
const { v } = symbols;
59
+
if (schema.maxLength === undefined) return;
60
+
return $(v)
61
+
.attr(identifiers.actions.maxLength)
62
+
.call($.literal(schema.maxLength));
63
+
}
54
64
55
-
const pipes: Array<ReturnType<typeof $.call>> = [];
56
-
pipes.push($(v).attr(identifiers.schemas.string).call());
65
+
function minLengthNode(ctx: StringResolverContext): PipeResult | undefined {
66
+
const { schema, symbols } = ctx;
67
+
const { v } = symbols;
68
+
if (schema.minLength === undefined) return;
69
+
return $(v)
70
+
.attr(identifiers.actions.minLength)
71
+
.call($.literal(schema.minLength));
72
+
}
57
73
58
-
if (schema.format) {
59
-
const args: FormatResolverArgs = { $, pipes, plugin, schema };
60
-
const resolver =
61
-
plugin.config['~resolvers']?.string?.formats?.[schema.format];
62
-
if (!resolver?.(args)) defaultFormatResolver(args);
63
-
}
74
+
function patternNode(ctx: StringResolverContext): PipeResult | undefined {
75
+
const { schema, symbols } = ctx;
76
+
const { v } = symbols;
77
+
if (!schema.pattern) return;
78
+
return $(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern));
79
+
}
64
80
65
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
66
-
pipes.push(
67
-
$(v).attr(identifiers.actions.length).call($.literal(schema.minLength)),
68
-
);
81
+
function stringResolver(ctx: StringResolverContext): Pipes {
82
+
const constNode = ctx.nodes.const(ctx);
83
+
if (constNode) return ctx.pipes.push(ctx.pipes.current, constNode);
84
+
85
+
const baseNode = ctx.nodes.base(ctx);
86
+
if (baseNode) ctx.pipes.push(ctx.pipes.current, baseNode);
87
+
88
+
const formatNode = ctx.nodes.format(ctx);
89
+
if (formatNode) ctx.pipes.push(ctx.pipes.current, formatNode);
90
+
91
+
const lengthNode = ctx.nodes.length(ctx);
92
+
if (lengthNode) {
93
+
ctx.pipes.push(ctx.pipes.current, lengthNode);
69
94
} else {
70
-
if (schema.minLength !== undefined) {
71
-
pipes.push(
72
-
$(v)
73
-
.attr(identifiers.actions.minLength)
74
-
.call($.literal(schema.minLength)),
75
-
);
76
-
}
95
+
const minLengthNode = ctx.nodes.minLength(ctx);
96
+
if (minLengthNode) ctx.pipes.push(ctx.pipes.current, minLengthNode);
77
97
78
-
if (schema.maxLength !== undefined) {
79
-
pipes.push(
80
-
$(v)
81
-
.attr(identifiers.actions.maxLength)
82
-
.call($.literal(schema.maxLength)),
83
-
);
84
-
}
98
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
99
+
if (maxLengthNode) ctx.pipes.push(ctx.pipes.current, maxLengthNode);
85
100
}
86
101
87
-
if (schema.pattern) {
88
-
pipes.push(
89
-
$(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)),
90
-
);
91
-
}
102
+
const patternNode = ctx.nodes.pattern(ctx);
103
+
if (patternNode) ctx.pipes.push(ctx.pipes.current, patternNode);
92
104
93
-
return pipesToAst(pipes, plugin);
105
+
return ctx.pipes.current;
106
+
}
107
+
108
+
export const stringToNode = ({
109
+
plugin,
110
+
schema,
111
+
}: IrSchemaToAstOptions & {
112
+
schema: SchemaWithType<'string'>;
113
+
}): Pipe => {
114
+
const ctx: StringResolverContext = {
115
+
$,
116
+
nodes: {
117
+
base: baseNode,
118
+
const: constNode,
119
+
format: formatNode,
120
+
length: lengthNode,
121
+
maxLength: maxLengthNode,
122
+
minLength: minLengthNode,
123
+
pattern: patternNode,
124
+
},
125
+
pipes: {
126
+
...pipes,
127
+
current: [],
128
+
},
129
+
plugin,
130
+
schema,
131
+
symbols: {
132
+
v: plugin.external('valibot.v'),
133
+
},
134
+
};
135
+
const resolver = plugin.config['~resolvers']?.string;
136
+
const node = resolver?.(ctx) ?? stringResolver(ctx);
137
+
return ctx.pipes.toNode(node, plugin);
94
138
};
+2
-2
packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts
+2
-2
packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts
···
3
3
import type { SchemaWithType } from '~/plugins';
4
4
import { $ } from '~/ts-dsl';
5
5
6
-
import { pipesToAst } from '../../shared/pipesToAst';
6
+
import { pipesToNode } from '../../shared/pipes';
7
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
8
import { identifiers } from '../constants';
9
9
import { irSchemaToAst } from '../plugin';
···
48
48
if (schemaPipes.hasLazyExpression) {
49
49
result.hasLazyExpression = true;
50
50
}
51
-
return pipesToAst(schemaPipes.pipes, plugin);
51
+
return pipesToNode(schemaPipes.pipes, plugin);
52
52
});
53
53
result.pipes = [
54
54
$(v)
+29
-15
packages/openapi-ts/src/plugins/zod/mini/api.ts
+29
-15
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 defaultValidatorResolver = ({
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 args: ValidatorResolverArgs = {
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
26
29
$,
27
-
chain: undefined,
30
+
chain: {
31
+
current: $(z),
32
+
},
28
33
operation,
29
34
plugin,
30
-
schema: symbol,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
31
39
};
32
40
const validator = plugin.config['~resolvers']?.validator;
33
41
const resolver =
34
42
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
43
+
const candidates = [resolver, validatorResolver];
36
44
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
45
+
const statements = candidate?.(ctx);
38
46
if (statements === null) return;
39
47
if (statements !== undefined) {
40
48
return $.func()
···
59
67
});
60
68
if (!symbol) return;
61
69
62
-
const args: ValidatorResolverArgs = {
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
63
72
$,
64
-
chain: undefined,
73
+
chain: {
74
+
current: $(z),
75
+
},
65
76
operation,
66
77
plugin,
67
-
schema: symbol,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
68
82
};
69
83
const validator = plugin.config['~resolvers']?.validator;
70
84
const resolver =
71
85
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
86
+
const candidates = [resolver, validatorResolver];
73
87
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
88
+
const statements = candidate?.(ctx);
75
89
if (statements === null) return;
76
90
if (statements !== undefined) {
77
91
return $.func()
+1
-1
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
+1
-1
packages/openapi-ts/src/plugins/zod/mini/toAst/array.ts
+5
-5
packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts
+5
-5
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
-
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';
···
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
})
67
-
: stringToAst({
67
+
: stringToNode({
68
68
...args,
69
69
schema: schema as SchemaWithType<'string'>,
70
70
});
+123
-72
packages/openapi-ts/src/plugins/zod/mini/toAst/number.ts
+123
-72
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';
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'>> = {};
19
-
20
-
const z = plugin.referenceSymbol({
21
-
category: 'external',
22
-
resource: 'zod.z',
23
-
});
24
-
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'>;
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();
30
19
}
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
-
}
20
+
let chain = $(z).attr(identifiers.number).call();
21
+
if (schema.type === 'integer') {
22
+
chain = $(z).attr(identifiers.int).call();
42
23
}
24
+
return chain;
25
+
}
43
26
44
-
const checks: Array<ReturnType<typeof $.call>> = [];
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
+
}
45
35
46
-
const integerLimit = getIntegerLimit(schema.format);
47
-
if (integerLimit) {
48
-
checks.push(
49
-
$(z)
50
-
.attr(identifiers.minimum)
51
-
.call(
52
-
maybeBigInt(integerLimit.minValue, schema.format),
53
-
$.object().prop('error', $.literal(integerLimit.minError)),
54
-
),
55
-
$(z)
56
-
.attr(identifiers.maximum)
57
-
.call(
58
-
maybeBigInt(integerLimit.maxValue, schema.format),
59
-
$.object().prop('error', $.literal(integerLimit.maxError)),
60
-
),
61
-
);
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
+
);
62
57
}
58
+
return;
59
+
}
63
60
61
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
62
+
const { schema, symbols } = ctx;
63
+
const { z } = symbols;
64
64
if (schema.exclusiveMinimum !== undefined) {
65
-
checks.push(
66
-
$(z)
67
-
.attr(identifiers.gt)
68
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format)),
69
-
);
70
-
} else if (schema.minimum !== undefined) {
71
-
checks.push(
72
-
$(z)
73
-
.attr(identifiers.gte)
74
-
.call(maybeBigInt(schema.minimum, schema.format)),
75
-
);
65
+
return $(z)
66
+
.attr(identifiers.gt)
67
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
68
+
}
69
+
if (schema.minimum !== undefined) {
70
+
return $(z)
71
+
.attr(identifiers.gte)
72
+
.call(ctx.utils.maybeBigInt(schema.minimum, schema.format));
73
+
}
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)),
81
+
);
76
82
}
83
+
return;
84
+
}
77
85
78
-
if (schema.exclusiveMaximum !== undefined) {
79
-
checks.push(
80
-
$(z)
81
-
.attr(identifiers.lt)
82
-
.call(maybeBigInt(schema.exclusiveMaximum, schema.format)),
83
-
);
84
-
} else if (schema.maximum !== undefined) {
85
-
checks.push(
86
-
$(z)
87
-
.attr(identifiers.lte)
88
-
.call(maybeBigInt(schema.maximum, schema.format)),
89
-
);
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;
90
91
}
91
92
92
-
if (checks.length) {
93
-
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
94
106
.attr(identifiers.check)
95
107
.call(...checks);
96
108
}
97
109
98
-
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'>;
99
150
};
+70
-52
packages/openapi-ts/src/plugins/zod/mini/toAst/object.ts
+70
-52
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 defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
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
+
},
19
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);
20
41
21
42
if (additional) {
22
43
return $(z)
···
27
48
return $(z).attr(identifiers.object).call(shape);
28
49
}
29
50
30
-
export const objectToAst = ({
31
-
plugin,
32
-
schema,
33
-
state,
34
-
}: IrSchemaToAstOptions & {
35
-
schema: SchemaWithType<'object'>;
36
-
}): Omit<Ast, 'typeName'> => {
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,
104
+
symbols: {
105
+
z,
106
+
},
107
+
utils: {
108
+
ast,
109
+
state,
110
+
},
92
111
};
93
-
const resolver = plugin.config['~resolvers']?.object?.base;
94
-
const chain = resolver?.(args) ?? defaultObjectBaseResolver(args);
95
-
result.expression = chain;
96
-
97
-
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'>;
98
116
};
+105
-58
packages/openapi-ts/src/plugins/zod/mini/toAst/string.ts
+105
-58
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
-
};
50
54
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>;
55
+
return;
56
+
}
59
57
60
-
const z = plugin.referenceSymbol({
61
-
category: 'external',
62
-
resource: 'zod.z',
63
-
});
58
+
function lengthNode(ctx: StringResolverContext): Chain | undefined {
59
+
const { schema, symbols } = ctx;
60
+
const { z } = symbols;
61
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
62
+
return;
63
+
return $(z).attr(identifiers.length).call($.literal(schema.minLength));
64
+
}
65
+
66
+
function maxLengthNode(ctx: StringResolverContext): Chain | undefined {
67
+
const { schema, symbols } = ctx;
68
+
const { z } = symbols;
69
+
if (schema.maxLength === undefined) return;
70
+
return $(z).attr(identifiers.maxLength).call($.literal(schema.maxLength));
71
+
}
72
+
73
+
function minLengthNode(ctx: StringResolverContext): Chain | undefined {
74
+
const { schema, symbols } = ctx;
75
+
const { z } = symbols;
76
+
if (schema.minLength === undefined) return;
77
+
return $(z).attr(identifiers.minLength).call($.literal(schema.minLength));
78
+
}
64
79
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'>;
80
+
function patternNode(ctx: StringResolverContext): Chain | undefined {
81
+
const { schema, symbols } = ctx;
82
+
const { z } = symbols;
83
+
if (!schema.pattern) return;
84
+
return $(z).attr(identifiers.regex).call($.regexp(schema.pattern));
85
+
}
86
+
87
+
function stringResolver(ctx: StringResolverContext): Chain {
88
+
const constNode = ctx.nodes.const(ctx);
89
+
if (constNode) {
90
+
ctx.chain.current = constNode;
91
+
return ctx.chain.current;
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 };
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)));
113
+
const patternNode = ctx.nodes.pattern(ctx);
114
+
if (patternNode) checks.push(patternNode);
115
+
116
+
if (checks.length > 0) {
117
+
ctx.chain.current = ctx.chain.current
118
+
.attr(identifiers.check)
119
+
.call(...checks);
102
120
}
103
121
104
-
if (checks.length) {
105
-
chain = chain.attr(identifiers.check).call(...checks);
106
-
}
122
+
return ctx.chain.current;
123
+
}
107
124
108
-
result.expression = chain;
109
-
return result as Omit<Ast, 'typeName'>;
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
};
+120
-87
packages/openapi-ts/src/plugins/zod/types.d.ts
+120
-87
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
+
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';
9
14
10
15
import type { IApi } from './api';
16
+
import type { Chain } from './shared/chain';
17
+
import type { Ast, PluginState } from './shared/types';
11
18
12
19
export type UserConfig = Plugin.Name<'zod'> &
13
20
Plugin.Hooks &
···
747
754
};
748
755
};
749
756
750
-
type SharedResolverArgs = DollarTsDsl & {
757
+
interface BaseResolverContext extends DollarTsDsl {
758
+
/**
759
+
* Functions for working with chains.
760
+
*/
761
+
chain: {
762
+
/**
763
+
* The current chain.
764
+
*
765
+
* In Zod, this represents a chain of call expressions ("chains")
766
+
* being assembled to form a schema definition.
767
+
*
768
+
* Each chain can be extended, modified, or replaced to customize
769
+
* the resulting schema.
770
+
*/
771
+
current: Chain;
772
+
};
751
773
/**
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.
774
+
* The plugin instance.
759
775
*/
760
-
chain?: ReturnType<typeof $.call>;
761
776
plugin: ZodPlugin['Instance'];
762
-
};
777
+
/**
778
+
* Provides access to commonly used symbols within the plugin.
779
+
*/
780
+
symbols: {
781
+
z: Symbol;
782
+
};
783
+
}
763
784
764
-
export type FormatResolverArgs = Required<SharedResolverArgs> & {
765
-
schema: IR.SchemaObject;
766
-
};
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
+
}
767
807
768
-
export type ObjectBaseResolverArgs = SharedResolverArgs & {
769
-
/** Null = never */
770
-
additional?: ReturnType<typeof $.call | typeof $.expr> | null;
771
-
schema: IR.SchemaObject;
772
-
shape: ReturnType<typeof $.object>;
773
-
};
808
+
export interface ObjectResolverContext extends BaseResolverContext {
809
+
/**
810
+
* Nodes used to build different parts of the object schema.
811
+
*/
812
+
nodes: {
813
+
/**
814
+
* If `additionalProperties` is `false` or `{ type: 'never' }`, returns `null`
815
+
* to indicate no additional properties are allowed.
816
+
*/
817
+
additionalProperties: (
818
+
ctx: ObjectResolverContext,
819
+
) => Chain | null | undefined;
820
+
base: (ctx: ObjectResolverContext) => Chain;
821
+
shape: (ctx: ObjectResolverContext) => ReturnType<typeof $.object>;
822
+
};
823
+
schema: SchemaWithType<'object'>;
824
+
/**
825
+
* Utility functions for object schema processing.
826
+
*/
827
+
utils: {
828
+
ast: Partial<Omit<Ast, 'typeName'>>;
829
+
state: Refs<PluginState>;
830
+
};
831
+
}
774
832
775
-
export type ValidatorResolverArgs = SharedResolverArgs & {
833
+
export interface StringResolverContext extends BaseResolverContext {
834
+
/**
835
+
* Nodes used to build different parts of the string schema.
836
+
*/
837
+
nodes: {
838
+
base: (ctx: StringResolverContext) => Chain;
839
+
const: (ctx: StringResolverContext) => Chain | undefined;
840
+
format: (ctx: StringResolverContext) => Chain | undefined;
841
+
length: (ctx: StringResolverContext) => Chain | undefined;
842
+
maxLength: (ctx: StringResolverContext) => Chain | undefined;
843
+
minLength: (ctx: StringResolverContext) => Chain | undefined;
844
+
pattern: (ctx: StringResolverContext) => Chain | undefined;
845
+
};
846
+
schema: SchemaWithType<'string'>;
847
+
}
848
+
849
+
export interface ValidatorResolverContext extends BaseResolverContext {
776
850
operation: IR.Operation;
777
-
schema: Symbol;
778
-
};
851
+
/**
852
+
* Provides access to commonly used symbols within the plugin.
853
+
*/
854
+
symbols: BaseResolverContext['symbols'] & {
855
+
schema: Symbol;
856
+
};
857
+
}
779
858
780
859
type ValidatorResolver = (
781
-
args: ValidatorResolverArgs,
860
+
ctx: ValidatorResolverContext,
782
861
) => MaybeArray<TsDsl<ts.Statement>> | null | undefined;
783
862
784
863
type Resolvers = Plugin.Resolvers<{
785
864
/**
786
-
* Resolvers for number schemas.
865
+
* Resolver for number schemas.
787
866
*
788
-
* Allows customization of how number types are rendered, including
789
-
* per-format handling.
867
+
* Allows customization of how number types are rendered.
868
+
*
869
+
* Returning `undefined` will execute the default resolver logic.
790
870
*/
791
-
number?: {
792
-
/**
793
-
* Resolvers for number formats (e.g., `float`, `double`, `int32`).
794
-
*
795
-
* Each key represents a specific format name with a custom
796
-
* resolver function that controls how that format is rendered.
797
-
*
798
-
* Example path: `~resolvers.number.formats.float`
799
-
*
800
-
* Returning `undefined` from a resolver will apply the default
801
-
* generation behavior for that format.
802
-
*/
803
-
formats?: Record<
804
-
string,
805
-
(args: FormatResolverArgs) => boolean | number | undefined
806
-
>;
807
-
};
871
+
number?: (ctx: NumberResolverContext) => Chain | undefined;
808
872
/**
809
-
* Resolvers for object schemas.
873
+
* Resolver for object schemas.
810
874
*
811
875
* Allows customization of how object types are rendered.
812
876
*
813
-
* Example path: `~resolvers.object.base`
814
-
*
815
-
* Returning `undefined` from a resolver will apply the default
816
-
* generation behavior for the object schema.
877
+
* Returning `undefined` will execute the default resolver logic.
817
878
*/
818
-
object?: {
819
-
/**
820
-
* Controls how object schemas are constructed.
821
-
*
822
-
* Called with the fully assembled shape (properties) and any additional
823
-
* property schema, allowing the resolver to choose the correct Zod
824
-
* base constructor and modify the schema chain if needed.
825
-
*
826
-
* Returning `undefined` will execute the default resolver logic.
827
-
*/
828
-
base?: (
829
-
args: ObjectBaseResolverArgs,
830
-
) => ReturnType<typeof $.call> | undefined;
831
-
};
879
+
object?: (ctx: ObjectResolverContext) => Chain | undefined;
832
880
/**
833
-
* Resolvers for string schemas.
881
+
* Resolver for string schemas.
834
882
*
835
-
* Allows customization of how string types are rendered, including
836
-
* per-format handling.
883
+
* Allows customization of how string types are rendered.
884
+
*
885
+
* Returning `undefined` will execute the default resolver logic.
837
886
*/
838
-
string?: {
839
-
/**
840
-
* Resolvers for string formats (e.g., `uuid`, `email`, `date-time`).
841
-
*
842
-
* Each key represents a specific format name with a custom
843
-
* resolver function that controls how that format is rendered.
844
-
*
845
-
* Example path: `~resolvers.string.formats.uuid`
846
-
*
847
-
* Returning `undefined` from a resolver will apply the default
848
-
* generation logic for that format.
849
-
*/
850
-
formats?: Record<
851
-
string,
852
-
(args: FormatResolverArgs) => ReturnType<typeof $.call> | undefined
853
-
>;
854
-
};
887
+
string?: (ctx: StringResolverContext) => Chain | undefined;
855
888
/**
856
889
* Resolvers for request and response validators.
857
890
*
···
859
892
*
860
893
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
861
894
*
862
-
* Returning `undefined` from a resolver will apply the default generation logic.
895
+
* Returning `undefined` will execute the default resolver logic.
863
896
*/
864
897
validator?:
865
898
| ValidatorResolver
···
867
900
/**
868
901
* Controls how the request validator function body is generated.
869
902
*
870
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
903
+
* Returning `undefined` will execute the default resolver logic.
871
904
*/
872
905
request?: ValidatorResolver;
873
906
/**
874
907
* Controls how the response validator function body is generated.
875
908
*
876
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
909
+
* Returning `undefined` will execute the default resolver logic.
877
910
*/
878
911
response?: ValidatorResolver;
879
912
};
+29
-15
packages/openapi-ts/src/plugins/zod/v3/api.ts
+29
-15
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 defaultValidatorResolver = ({
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 args: ValidatorResolverArgs = {
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
26
29
$,
27
-
chain: undefined,
30
+
chain: {
31
+
current: $(z),
32
+
},
28
33
operation,
29
34
plugin,
30
-
schema: symbol,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
31
39
};
32
40
const validator = plugin.config['~resolvers']?.validator;
33
41
const resolver =
34
42
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
43
+
const candidates = [resolver, validatorResolver];
36
44
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
45
+
const statements = candidate?.(ctx);
38
46
if (statements === null) return;
39
47
if (statements !== undefined) {
40
48
return $.func()
···
59
67
});
60
68
if (!symbol) return;
61
69
62
-
const args: ValidatorResolverArgs = {
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
63
72
$,
64
-
chain: undefined,
73
+
chain: {
74
+
current: $(z),
75
+
},
65
76
operation,
66
77
plugin,
67
-
schema: symbol,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
68
82
};
69
83
const validator = plugin.config['~resolvers']?.validator;
70
84
const resolver =
71
85
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
86
+
const candidates = [resolver, validatorResolver];
73
87
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
88
+
const statements = candidate?.(ctx);
75
89
if (statements === null) return;
76
90
if (statements !== undefined) {
77
91
return $.func()
+5
-5
packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts
+5
-5
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
-
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';
···
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
})
80
-
: stringToAst({
80
+
: stringToNode({
81
81
...args,
82
82
schema: schema as SchemaWithType<'string'>,
83
83
}),
+112
-53
packages/openapi-ts/src/plugins/zod/v3/toAst/number.ts
+112
-53
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 { IrSchemaToAstOptions } from '../../shared/types';
10
+
import type { Chain } from '../../shared/chain';
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
-
}) => {
18
-
const z = plugin.referenceSymbol({
19
-
category: 'external',
20
-
resource: 'zod.z',
21
-
});
22
-
23
-
if (schema.const !== undefined) {
24
-
const expression = $(z)
25
-
.attr(identifiers.literal)
26
-
.call(maybeBigInt(schema.const, schema.format));
27
-
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();
28
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
+
}
29
26
30
-
let numberExpression: ReturnType<typeof $.call>;
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
+
}
31
35
32
-
if (shouldCoerceToBigInt(schema.format)) {
33
-
numberExpression = $(z)
34
-
.attr(identifiers.coerce)
35
-
.attr(identifiers.bigint)
36
-
.call();
37
-
} else {
38
-
numberExpression = $(z).attr(identifiers.number).call();
39
-
if (schema.type === 'integer') {
40
-
numberExpression = numberExpression.attr(identifiers.int).call();
41
-
}
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));
42
47
}
43
-
44
-
const integerLimit = getIntegerLimit(schema.format);
45
-
if (integerLimit) {
46
-
numberExpression = numberExpression
47
-
.attr(identifiers.min)
48
-
.call(
49
-
maybeBigInt(integerLimit.minValue, schema.format),
50
-
$.object().prop('message', $.literal(integerLimit.minError)),
51
-
)
48
+
const limit = ctx.utils.getIntegerLimit(schema.format);
49
+
if (limit) {
50
+
return chain.current
52
51
.attr(identifiers.max)
53
52
.call(
54
-
maybeBigInt(integerLimit.maxValue, schema.format),
55
-
$.object().prop('message', $.literal(integerLimit.maxError)),
53
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
54
+
$.object().prop('message', $.literal(limit.maxError)),
56
55
);
57
56
}
57
+
return;
58
+
}
58
59
60
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
61
+
const { chain, schema } = ctx;
59
62
if (schema.exclusiveMinimum !== undefined) {
60
-
numberExpression = numberExpression
63
+
return chain.current
61
64
.attr(identifiers.gt)
62
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format));
63
-
} else if (schema.minimum !== undefined) {
64
-
numberExpression = numberExpression
65
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
66
+
}
67
+
if (schema.minimum !== undefined) {
68
+
return chain.current
65
69
.attr(identifiers.gte)
66
-
.call(maybeBigInt(schema.minimum, schema.format));
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
+
);
67
80
}
81
+
return;
82
+
}
68
83
69
-
if (schema.exclusiveMaximum !== undefined) {
70
-
numberExpression = numberExpression
71
-
.attr(identifiers.lt)
72
-
.call(maybeBigInt(schema.exclusiveMaximum, schema.format));
73
-
} else if (schema.maximum !== undefined) {
74
-
numberExpression = numberExpression
75
-
.attr(identifiers.lte)
76
-
.call(maybeBigInt(schema.maximum, schema.format));
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;
77
89
}
78
90
79
-
return numberExpression;
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;
99
+
100
+
return ctx.chain.current;
101
+
}
102
+
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;
80
139
};
+76
-57
packages/openapi-ts/src/plugins/zod/v3/toAst/object.ts
+76
-57
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 defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
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
+
},
19
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);
20
41
21
42
if (additional) {
22
43
return $(z).attr(identifiers.record).call(additional);
···
25
46
return $(z).attr(identifiers.object).call(shape);
26
47
}
27
48
28
-
export const objectToAst = ({
29
-
plugin,
30
-
schema,
31
-
state,
32
-
}: IrSchemaToAstOptions & {
33
-
schema: SchemaWithType<'object'>;
34
-
}): Omit<Ast, 'typeName'> & {
35
-
anyType?: string;
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,
100
+
symbols: {
101
+
z,
102
+
},
103
+
utils: {
104
+
ast,
105
+
state,
106
+
},
88
107
};
89
-
const resolver = plugin.config['~resolvers']?.object?.base;
90
-
const chain = resolver?.(args) ?? defaultObjectBaseResolver(args);
91
-
result.expression = chain;
92
-
108
+
const resolver = plugin.config['~resolvers']?.object;
109
+
const node = resolver?.(ctx) ?? objectResolver(ctx);
110
+
ast.expression = node;
93
111
return {
112
+
...ast,
94
113
anyType: 'AnyZodObject',
95
-
expression: result.expression!,
96
-
hasLazyExpression,
114
+
} as Omit<Ast, 'typeName'> & {
115
+
anyType: string;
97
116
};
98
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 };
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
};
+29
-15
packages/openapi-ts/src/plugins/zod/v4/api.ts
+29
-15
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 defaultValidatorResolver = ({
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 args: ValidatorResolverArgs = {
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
26
29
$,
27
-
chain: undefined,
30
+
chain: {
31
+
current: $(z),
32
+
},
28
33
operation,
29
34
plugin,
30
-
schema: symbol,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
31
39
};
32
40
const validator = plugin.config['~resolvers']?.validator;
33
41
const resolver =
34
42
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
43
+
const candidates = [resolver, validatorResolver];
36
44
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
45
+
const statements = candidate?.(ctx);
38
46
if (statements === null) return;
39
47
if (statements !== undefined) {
40
48
return $.func()
···
59
67
});
60
68
if (!symbol) return;
61
69
62
-
const args: ValidatorResolverArgs = {
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
63
72
$,
64
-
chain: undefined,
73
+
chain: {
74
+
current: $(z),
75
+
},
65
76
operation,
66
77
plugin,
67
-
schema: symbol,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
68
82
};
69
83
const validator = plugin.config['~resolvers']?.validator;
70
84
const resolver =
71
85
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
86
+
const candidates = [resolver, validatorResolver];
73
87
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
88
+
const statements = candidate?.(ctx);
75
89
if (statements === null) return;
76
90
if (statements !== undefined) {
77
91
return $.func()
+5
-5
packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts
+5
-5
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
-
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';
···
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
})
67
-
: stringToAst({
67
+
: stringToNode({
68
68
...args,
69
69
schema: schema as SchemaWithType<'string'>,
70
70
});
+112
-52
packages/openapi-ts/src/plugins/zod/v4/toAst/number.ts
+112
-52
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
-
const integerLimit = getIntegerLimit(schema.format);
45
-
if (integerLimit) {
46
-
result.expression = result.expression
47
-
.attr(identifiers.min)
48
-
.call(
49
-
maybeBigInt(integerLimit.minValue, schema.format),
50
-
$.object().prop('error', $.literal(integerLimit.minError)),
51
-
)
48
+
const limit = ctx.utils.getIntegerLimit(schema.format);
49
+
if (limit) {
50
+
return chain.current
52
51
.attr(identifiers.max)
53
52
.call(
54
-
maybeBigInt(integerLimit.maxValue, schema.format),
55
-
$.object().prop('error', $.literal(integerLimit.maxError)),
53
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
54
+
$.object().prop('error', $.literal(limit.maxError)),
56
55
);
57
56
}
57
+
return;
58
+
}
58
59
60
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
61
+
const { chain, schema } = ctx;
59
62
if (schema.exclusiveMinimum !== undefined) {
60
-
result.expression = result.expression
63
+
return chain.current
61
64
.attr(identifiers.gt)
62
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format));
63
-
} else if (schema.minimum !== undefined) {
64
-
result.expression = result.expression
65
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
66
+
}
67
+
if (schema.minimum !== undefined) {
68
+
return chain.current
65
69
.attr(identifiers.gte)
66
-
.call(maybeBigInt(schema.minimum, schema.format));
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
+
);
67
80
}
81
+
return;
82
+
}
68
83
69
-
if (schema.exclusiveMaximum !== undefined) {
70
-
result.expression = result.expression
71
-
.attr(identifiers.lt)
72
-
.call(maybeBigInt(schema.exclusiveMaximum, schema.format));
73
-
} else if (schema.maximum !== undefined) {
74
-
result.expression = result.expression
75
-
.attr(identifiers.lte)
76
-
.call(maybeBigInt(schema.maximum, schema.format));
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;
77
89
}
78
90
79
-
return result as Omit<Ast, 'typeName'>;
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;
99
+
100
+
return ctx.chain.current;
101
+
}
102
+
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'>;
80
140
};
+70
-60
packages/openapi-ts/src/plugins/zod/v4/toAst/object.ts
+70
-60
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 defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
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
+
},
19
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);
20
41
21
42
if (additional) {
22
43
return $(z)
···
27
48
return $(z).attr(identifiers.object).call(shape);
28
49
}
29
50
30
-
export const objectToAst = ({
31
-
plugin,
32
-
schema,
33
-
state,
34
-
}: IrSchemaToAstOptions & {
35
-
schema: SchemaWithType<'object'>;
36
-
}): Omit<Ast, 'typeName'> => {
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,
104
+
symbols: {
105
+
z,
106
+
},
107
+
utils: {
108
+
ast,
109
+
state,
110
+
},
92
111
};
93
-
const resolver = plugin.config['~resolvers']?.object?.base;
94
-
const chain = resolver?.(args) ?? defaultObjectBaseResolver(args);
95
-
result.expression = chain;
96
-
97
-
// Return with typeName for circular references
98
-
if (result.hasLazyExpression) {
99
-
return {
100
-
...result,
101
-
typeName: 'ZodType',
102
-
} as Ast;
103
-
}
104
-
105
-
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'>;
106
116
};
+98
-49
packages/openapi-ts/src/plugins/zod/v4/toAst/string.ts
+98
-49
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
+
}
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
-
};
50
54
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>;
55
+
return;
56
+
}
57
+
58
+
function lengthNode(ctx: StringResolverContext): Chain | undefined {
59
+
const { chain, schema } = ctx;
60
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
61
+
return;
62
+
return chain.current
63
+
.attr(identifiers.length)
64
+
.call($.literal(schema.minLength));
65
+
}
66
+
67
+
function maxLengthNode(ctx: StringResolverContext): Chain | undefined {
68
+
const { chain, schema } = ctx;
69
+
if (schema.maxLength === undefined) return;
70
+
return chain.current.attr(identifiers.max).call($.literal(schema.maxLength));
71
+
}
72
+
73
+
function minLengthNode(ctx: StringResolverContext): Chain | undefined {
74
+
const { chain, schema } = ctx;
75
+
if (schema.minLength === undefined) return;
76
+
return chain.current.attr(identifiers.min).call($.literal(schema.minLength));
77
+
}
59
78
60
-
const z = plugin.referenceSymbol({
61
-
category: 'external',
62
-
resource: 'zod.z',
63
-
});
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
+
}
64
84
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'>;
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;
69
90
}
70
91
71
-
chain = $(z).attr(identifiers.string).call();
92
+
const baseNode = ctx.nodes.base(ctx);
93
+
if (baseNode) ctx.chain.current = baseNode;
72
94
73
-
if (schema.format) {
74
-
const args: FormatResolverArgs = { $, chain, plugin, schema };
75
-
const resolver =
76
-
plugin.config['~resolvers']?.string?.formats?.[schema.format];
77
-
chain = resolver?.(args) ?? defaultFormatResolver(args);
78
-
}
95
+
const formatNode = ctx.nodes.format(ctx);
96
+
if (formatNode) ctx.chain.current = formatNode;
79
97
80
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
81
-
chain = chain.attr(identifiers.length).call($.literal(schema.minLength));
98
+
const lengthNode = ctx.nodes.length(ctx);
99
+
if (lengthNode) {
100
+
ctx.chain.current = lengthNode;
82
101
} else {
83
-
if (schema.minLength !== undefined) {
84
-
chain = chain.attr(identifiers.min).call($.literal(schema.minLength));
85
-
}
102
+
const minLengthNode = ctx.nodes.minLength(ctx);
103
+
if (minLengthNode) ctx.chain.current = minLengthNode;
86
104
87
-
if (schema.maxLength !== undefined) {
88
-
chain = chain.attr(identifiers.max).call($.literal(schema.maxLength));
89
-
}
105
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
106
+
if (maxLengthNode) ctx.chain.current = maxLengthNode;
90
107
}
91
108
92
-
if (schema.pattern) {
93
-
chain = chain.attr(identifiers.regex).call($.regexp(schema.pattern));
94
-
}
109
+
const patternNode = ctx.nodes.pattern(ctx);
110
+
if (patternNode) ctx.chain.current = patternNode;
111
+
112
+
return ctx.chain.current;
113
+
}
95
114
96
-
result.expression = chain;
97
-
return result as Omit<Ast, 'typeName'>;
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
+
};
98
147
};