+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
},
432
},
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
-
},
447
},
448
-
// validator({ $, plugin, schema, v }) {
449
// const vShadow = plugin.symbol('v');
450
// const test = plugin.symbol('test');
451
// const e = plugin.symbol('err');
···
467
{
468
// case: 'snake_case',
469
// comments: false,
470
-
compatibilityVersion: 3,
471
dates: {
472
// local: true,
473
// offset: true,
···
516
},
517
},
518
'~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
-
},
545
// validator({ $, schema }) {
546
// return [
547
// $.const('parsed').assign(
···
431
},
432
},
433
'~resolvers': {
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
+
}
454
},
455
+
// string(ctx) {
456
+
// const { $, schema, symbols } = ctx;
457
+
// const { v } = symbols;
458
+
// if (schema.format === 'date' || schema.format === 'date-time') {
459
+
// ctx.nodes.format = () => $(v).attr('isoDateTime').call();
460
+
// }
461
+
// },
462
+
// validator(ctx) {
463
+
// const { $, plugin, symbols } = ctx;
464
+
// const { schema, v } = symbols;
465
// const vShadow = plugin.symbol('v');
466
// const test = plugin.symbol('test');
467
// const e = plugin.symbol('err');
···
483
{
484
// case: 'snake_case',
485
// comments: false,
486
+
compatibilityVersion: 'mini',
487
dates: {
488
// local: true,
489
// offset: true,
···
532
},
533
},
534
'~resolvers': {
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
+
// },
590
// validator({ $, schema }) {
591
// return [
592
// $.const('parsed').assign(
+10
docs/.vitepress/config/en.ts
+10
docs/.vitepress/config/en.ts
+2
-2
docs/openapi-ts/clients.md
+2
-2
docs/openapi-ts/clients.md
···
1
---
2
title: Clients
3
-
description: REST clients for Hey API. Compatible with all our features.
4
---
5
6
<script setup lang="ts">
7
import { embedProject } from '../embed'
8
</script>
9
10
-
# REST Clients
11
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
···
1
---
2
title: Clients
3
+
description: HTTP clients for Hey API. Compatible with all our features.
4
---
5
6
<script setup lang="ts">
7
import { embedProject } from '../embed'
8
</script>
9
10
+
# HTTP Clients
11
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
+6
docs/openapi-ts/migrating.md
+6
docs/openapi-ts/migrating.md
···
7
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
10
## v0.89.0
11
12
### Prefer named exports
···
7
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
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
+
16
## v0.89.0
17
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
208
:::
209
210
+
## Resolvers
211
+
212
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
213
+
214
## API
215
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
300
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
301
302
## API
303
304
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.
···
299
300
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
301
302
+
## Resolvers
303
+
304
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
305
+
306
## API
307
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
313
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
314
315
## API
316
317
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.
···
312
313
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
314
315
+
## Resolvers
316
+
317
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
318
+
319
## API
320
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
311
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
312
313
## API
314
315
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.
···
310
311
You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
312
313
+
## Resolvers
314
+
315
+
You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers).
316
+
317
## API
318
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
});
631
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))),
635
sort: v.optional(v.array(v.string()))
636
});
637
···
962
}), v.strictObject({
963
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
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))
967
})]);
968
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))
972
})]);
973
974
/**
···
630
});
631
632
export const vPageable = v.object({
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
sort: v.optional(v.array(v.string()))
636
});
637
···
962
}), v.strictObject({
963
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
964
})]), v.object({
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
})]);
968
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), 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
})]);
973
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
});
631
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))),
635
sort: v.optional(v.array(v.string()))
636
});
637
···
972
}), v.strictObject({
973
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
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))
977
})]);
978
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))
982
})]);
983
984
/**
···
630
});
631
632
export const vPageable = v.object({
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
sort: v.optional(v.array(v.string()))
636
});
637
···
972
}), v.strictObject({
973
bar: vNonAsciiStringæøåÆøÅöôêÊ字符串
974
})]), v.object({
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
})]);
978
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), 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
})]);
983
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
v.number(),
8
v.string(),
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))))
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
});
711
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))),
715
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
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)),
1131
z.null()
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))
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
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)),
1142
z.null()
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))
1145
}));
1146
1147
/**
···
710
});
711
712
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
1129
baz: z.union([
1130
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1131
z.null()
1132
]),
1133
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
1140
baz: z.union([
1141
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1142
z.null()
1143
]),
1144
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1145
}));
1146
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
});
709
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(),
713
sort: z.array(z.string()).optional()
714
});
715
···
1125
})
1126
]), z.object({
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),
1129
z.null()
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)
1132
}));
1133
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
]), z.object({
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),
1140
z.null()
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)
1143
}));
1144
1145
/**
···
708
});
709
710
export const zPageable = z.object({
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
sort: z.array(z.string()).optional()
714
});
715
···
1125
})
1126
]), z.object({
1127
baz: z.union([
1128
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1129
z.null()
1130
]),
1131
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1132
}));
1133
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
]), z.object({
1138
baz: z.union([
1139
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1140
z.null()
1141
]),
1142
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1143
}));
1144
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
});
711
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)),
715
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
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),
1131
z.null()
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)
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
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),
1142
z.null()
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)
1145
}));
1146
1147
/**
···
710
});
711
712
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
1129
baz: z.union([
1130
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1131
z.null()
1132
]),
1133
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
1140
baz: z.union([
1141
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1142
z.null()
1143
]),
1144
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1145
}));
1146
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
});
708
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))),
712
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
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)),
1137
z.null()
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))
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
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)),
1148
z.null()
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))
1151
}));
1152
1153
/**
···
707
});
708
709
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
1135
baz: z.union([
1136
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1137
z.null()
1138
]),
1139
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
1146
baz: z.union([
1147
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1148
z.null()
1149
]),
1150
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1151
}));
1152
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
import * as z from 'zod/v4-mini';
4
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))))
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
});
706
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(),
710
sort: z.array(z.string()).optional()
711
});
712
···
1131
})
1132
]), z.object({
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),
1135
z.null()
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)
1138
}));
1139
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
]), z.object({
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),
1146
z.null()
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)
1149
}));
1150
1151
/**
···
705
});
706
707
export const zPageable = z.object({
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
sort: z.array(z.string()).optional()
711
});
712
···
1131
})
1132
]), z.object({
1133
baz: z.union([
1134
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1135
z.null()
1136
]),
1137
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1138
}));
1139
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
]), z.object({
1144
baz: z.union([
1145
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1146
z.null()
1147
]),
1148
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1149
}));
1150
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
import { z } from 'zod';
4
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()
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
});
708
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)),
712
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
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),
1137
z.null()
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)
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
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),
1148
z.null()
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)
1151
}));
1152
1153
/**
···
707
});
708
709
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
1135
baz: z.union([
1136
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1137
z.null()
1138
]),
1139
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
1146
baz: z.union([
1147
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1148
z.null()
1149
]),
1150
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1151
}));
1152
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
import { z } from 'zod/v4';
4
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)))
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
});
711
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))),
715
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
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)),
1131
z.null()
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))
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
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)),
1142
z.null()
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))
1145
}));
1146
1147
/**
···
710
});
711
712
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
1129
baz: z.union([
1130
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1131
z.null()
1132
]),
1133
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
1140
baz: z.union([
1141
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1142
z.null()
1143
]),
1144
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1145
}));
1146
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
});
709
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(),
713
sort: z.array(z.string()).optional()
714
});
715
···
1125
})
1126
]), z.object({
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),
1129
z.null()
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)
1132
}));
1133
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
]), z.object({
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),
1140
z.null()
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)
1143
}));
1144
1145
/**
···
708
});
709
710
export const zPageable = z.object({
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
sort: z.array(z.string()).optional()
714
});
715
···
1125
})
1126
]), z.object({
1127
baz: z.union([
1128
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1129
z.null()
1130
]),
1131
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1132
}));
1133
1134
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1136
zNonAsciiStringæøåÆøÅöôêÊ字符串
1137
]), z.object({
1138
baz: z.union([
1139
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1140
z.null()
1141
]),
1142
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1143
}));
1144
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
});
711
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)),
715
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
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),
1131
z.null()
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)
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
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),
1142
z.null()
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)
1145
}));
1146
1147
/**
···
710
});
711
712
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
716
});
717
···
1127
})
1128
]), z.object({
1129
baz: z.union([
1130
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1131
z.null()
1132
]),
1133
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1134
}));
1135
1136
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1138
zNonAsciiStringæøåÆøÅöôêÊ字符串
1139
]), z.object({
1140
baz: z.union([
1141
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1142
z.null()
1143
]),
1144
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1145
}));
1146
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
});
708
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))),
712
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
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)),
1137
z.null()
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))
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
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)),
1148
z.null()
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))
1151
}));
1152
1153
/**
···
707
});
708
709
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
1135
baz: z.union([
1136
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1137
z.null()
1138
]),
1139
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
1146
baz: z.union([
1147
+
z.int().check(z.gte(0), z.maximum(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' })),
1148
z.null()
1149
]),
1150
+
qux: z.int().check(z.gte(0), z.maximum(255, { error: 'Invalid value: Expected uint8 to be <= 255' }))
1151
}));
1152
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
import * as z from 'zod/mini';
4
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))))
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
});
706
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(),
710
sort: z.array(z.string()).optional()
711
});
712
···
1131
})
1132
]), z.object({
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),
1135
z.null()
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)
1138
}));
1139
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
]), z.object({
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),
1146
z.null()
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)
1149
}));
1150
1151
/**
···
705
});
706
707
export const zPageable = z.object({
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
sort: z.array(z.string()).optional()
711
});
712
···
1131
})
1132
]), z.object({
1133
baz: z.union([
1134
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1135
z.null()
1136
]),
1137
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1138
}));
1139
1140
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1142
zNonAsciiStringæøåÆøÅöôêÊ字符串
1143
]), z.object({
1144
baz: z.union([
1145
+
z.number().int().gte(0).max(65535, { message: 'Invalid value: Expected uint16 to be <= 65535' }),
1146
z.null()
1147
]),
1148
+
qux: z.number().int().gte(0).max(255, { message: 'Invalid value: Expected uint8 to be <= 255' })
1149
}));
1150
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
import { z } from 'zod/v3';
4
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()
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
});
708
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)),
712
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
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),
1137
z.null()
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)
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
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),
1148
z.null()
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)
1151
}));
1152
1153
/**
···
707
});
708
709
export const zPageable = z.object({
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
sort: z.optional(z.array(z.string()))
713
});
714
···
1133
})
1134
]), z.object({
1135
baz: z.union([
1136
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1137
z.null()
1138
]),
1139
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1140
}));
1141
1142
export const zModelWithOneOfAndProperties = z.intersection(z.union([
···
1144
zNonAsciiStringæøåÆøÅöôêÊ字符串
1145
]), z.object({
1146
baz: z.union([
1147
+
z.int().gte(0).max(65535, { error: 'Invalid value: Expected uint16 to be <= 65535' }),
1148
z.null()
1149
]),
1150
+
qux: z.int().gte(0).max(255, { error: 'Invalid value: Expected uint8 to be <= 255' })
1151
}));
1152
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
import { z } from 'zod';
4
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)))
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';
3
4
import type { IR } from '~/ir/types';
5
-
import type { DefinePlugin, Plugin } from '~/plugins';
6
-
import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl';
7
import type { StringCase, StringName } from '~/types/case';
8
-
import type { MaybeArray } from '~/types/utils';
9
10
import type { IApi } from './api';
11
12
export type UserConfig = Plugin.Name<'valibot'> &
13
Plugin.Hooks &
···
321
};
322
};
323
324
-
type SharedResolverArgs = DollarTsDsl & {
325
/**
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.
334
*/
335
-
pipes: Array<ReturnType<typeof $.call>>;
336
plugin: ValibotPlugin['Instance'];
337
-
};
338
339
-
export type FormatResolverArgs = SharedResolverArgs & {
340
-
schema: IR.SchemaObject;
341
-
};
342
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
-
};
349
350
-
export type ValidatorResolverArgs = SharedResolverArgs & {
351
operation: IR.Operation;
352
-
schema: Symbol;
353
-
v: Symbol;
354
-
};
355
356
type ValidatorResolver = (
357
-
args: ValidatorResolverArgs,
358
-
) => MaybeArray<TsDsl<ts.Statement>> | null | undefined;
359
360
type Resolvers = Plugin.Resolvers<{
361
/**
362
-
* Resolvers for number schemas.
363
*
364
-
* Allows customization of how number types are rendered, including
365
-
* per-format handling.
366
*/
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
-
};
384
/**
385
-
* Resolvers for object schemas.
386
*
387
* Allows customization of how object types are rendered.
388
*
389
-
* Example path: `~resolvers.object.base`
390
-
*
391
-
* Returning `undefined` from a resolver will apply the default
392
-
* generation behavior for the object schema.
393
*/
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
-
};
408
/**
409
-
* Resolvers for string schemas.
410
*
411
-
* Allows customization of how string types are rendered, including
412
-
* per-format handling.
413
*/
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
-
};
431
/**
432
* Resolvers for request and response validators.
433
*
···
435
*
436
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
437
*
438
-
* Returning `undefined` from a resolver will apply the default generation logic.
439
*/
440
validator?:
441
| ValidatorResolver
···
443
/**
444
* Controls how the request validator function body is generated.
445
*
446
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
447
*/
448
request?: ValidatorResolver;
449
/**
450
* Controls how the response validator function body is generated.
451
*
452
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
453
*/
454
response?: ValidatorResolver;
455
};
···
1
+
import type { Refs, Symbol } from '@hey-api/codegen-core';
2
3
import type { IR } from '~/ir/types';
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';
11
import type { StringCase, StringName } from '~/types/case';
12
13
import type { IApi } from './api';
14
+
import type { Pipe, PipeResult, PipesUtils } from './shared/pipes';
15
+
import type { Ast, PluginState } from './shared/types';
16
17
export type UserConfig = Plugin.Name<'valibot'> &
18
Plugin.Hooks &
···
326
};
327
};
328
329
+
interface BaseResolverContext extends DollarTsDsl {
330
/**
331
+
* Functions for working with pipes.
332
*/
333
+
pipes: PipesUtils & {
334
+
/**
335
+
* The current pipe.
336
+
*
337
+
* In Valibot, this represents a list of call expressions ("pipes")
338
+
* being assembled to form a schema definition.
339
+
*
340
+
* Each pipe can be extended, modified, or replaced to customize
341
+
* the resulting schema.
342
+
*/
343
+
current: Pipes;
344
+
};
345
+
/**
346
+
* The plugin instance.
347
+
*/
348
plugin: ValibotPlugin['Instance'];
349
+
/**
350
+
* Provides access to commonly used symbols within the plugin.
351
+
*/
352
+
symbols: {
353
+
v: Symbol;
354
+
};
355
+
}
356
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
+
}
377
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
+
}
402
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 {
420
operation: IR.Operation;
421
+
/**
422
+
* Provides access to commonly used symbols within the plugin.
423
+
*/
424
+
symbols: BaseResolverContext['symbols'] & {
425
+
schema: Symbol;
426
+
};
427
+
}
428
429
type ValidatorResolver = (
430
+
ctx: ValidatorResolverContext,
431
+
) => PipeResult | null | undefined;
432
433
type Resolvers = Plugin.Resolvers<{
434
/**
435
+
* Resolver for number schemas.
436
*
437
+
* Allows customization of how number types are rendered.
438
+
*
439
+
* Returning `undefined` will execute the default resolver logic.
440
*/
441
+
number?: (ctx: NumberResolverContext) => PipeResult | undefined;
442
/**
443
+
* Resolver for object schemas.
444
*
445
* Allows customization of how object types are rendered.
446
*
447
+
* Returning `undefined` will execute the default resolver logic.
448
*/
449
+
object?: (ctx: ObjectResolverContext) => PipeResult | undefined;
450
/**
451
+
* Resolver for string schemas.
452
*
453
+
* Allows customization of how string types are rendered.
454
+
*
455
+
* Returning `undefined` will execute the default resolver logic.
456
*/
457
+
string?: (ctx: StringResolverContext) => PipeResult | undefined;
458
/**
459
* Resolvers for request and response validators.
460
*
···
462
*
463
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
464
*
465
+
* Returning `undefined` will execute the default resolver logic.
466
*/
467
validator?:
468
| ValidatorResolver
···
470
/**
471
* Controls how the request validator function body is generated.
472
*
473
+
* Returning `undefined` will execute the default resolver logic.
474
*/
475
request?: ValidatorResolver;
476
/**
477
* Controls how the response validator function body is generated.
478
*
479
+
* Returning `undefined` will execute the default resolver logic.
480
*/
481
response?: ValidatorResolver;
482
};
+34
-26
packages/openapi-ts/src/plugins/valibot/v1/api.ts
+34
-26
packages/openapi-ts/src/plugins/valibot/v1/api.ts
···
1
import { $ } from '~/ts-dsl';
2
3
import type { ValidatorArgs } from '../shared/types';
4
-
import type { ValidatorResolverArgs } from '../types';
5
import { identifiers } from './constants';
6
7
-
const defaultValidatorResolver = ({
8
-
schema,
9
-
v,
10
-
}: ValidatorResolverArgs): ReturnType<typeof $.return> =>
11
-
$(v).attr(identifiers.async.parseAsync).call(schema, 'data').await().return();
12
13
export const createRequestValidatorV1 = ({
14
operation,
···
23
});
24
if (!symbol) return;
25
26
-
const v = plugin.referenceSymbol({
27
-
category: 'external',
28
-
resource: 'valibot.v',
29
-
});
30
-
const args: ValidatorResolverArgs = {
31
$,
32
operation,
33
-
pipes: [],
34
plugin,
35
-
schema: symbol,
36
-
v,
37
};
38
const validator = plugin.config['~resolvers']?.validator;
39
const resolver =
40
typeof validator === 'function' ? validator : validator?.request;
41
-
const candidates = [resolver, defaultValidatorResolver];
42
for (const candidate of candidates) {
43
-
const statements = candidate?.(args);
44
if (statements === null) return;
45
if (statements !== undefined) {
46
return $.func()
···
65
});
66
if (!symbol) return;
67
68
-
const v = plugin.referenceSymbol({
69
-
category: 'external',
70
-
resource: 'valibot.v',
71
-
});
72
-
const args: ValidatorResolverArgs = {
73
$,
74
operation,
75
-
pipes: [],
76
plugin,
77
-
schema: symbol,
78
-
v,
79
};
80
const validator = plugin.config['~resolvers']?.validator;
81
const resolver =
82
typeof validator === 'function' ? validator : validator?.response;
83
-
const candidates = [resolver, defaultValidatorResolver];
84
for (const candidate of candidates) {
85
-
const statements = candidate?.(args);
86
if (statements === null) return;
87
if (statements !== undefined) {
88
return $.func()
···
1
import { $ } from '~/ts-dsl';
2
3
+
import { pipes } from '../shared/pipes';
4
import type { ValidatorArgs } from '../shared/types';
5
+
import type { ValidatorResolverContext } from '../types';
6
import { identifiers } from './constants';
7
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
+
};
18
19
export const createRequestValidatorV1 = ({
20
operation,
···
29
});
30
if (!symbol) return;
31
32
+
const ctx: ValidatorResolverContext = {
33
$,
34
operation,
35
+
pipes: {
36
+
...pipes,
37
+
current: [],
38
+
},
39
plugin,
40
+
symbols: {
41
+
schema: symbol,
42
+
v: plugin.external('valibot.v'),
43
+
},
44
};
45
const validator = plugin.config['~resolvers']?.validator;
46
const resolver =
47
typeof validator === 'function' ? validator : validator?.request;
48
+
const candidates = [resolver, validatorResolver];
49
for (const candidate of candidates) {
50
+
const statements = candidate?.(ctx);
51
if (statements === null) return;
52
if (statements !== undefined) {
53
return $.func()
···
72
});
73
if (!symbol) return;
74
75
+
const ctx: ValidatorResolverContext = {
76
$,
77
operation,
78
+
pipes: {
79
+
...pipes,
80
+
current: [],
81
+
},
82
plugin,
83
+
symbols: {
84
+
schema: symbol,
85
+
v: plugin.external('valibot.v'),
86
+
},
87
};
88
const validator = plugin.config['~resolvers']?.validator;
89
const resolver =
90
typeof validator === 'function' ? validator : validator?.response;
91
+
const candidates = [resolver, validatorResolver];
92
for (const candidate of candidates) {
93
+
const statements = candidate?.(ctx);
94
if (statements === null) return;
95
if (statements !== undefined) {
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
12
import { exportAst } from '../shared/export';
13
import { irOperationToAst } from '../shared/operation';
14
-
import { pipesToAst } from '../shared/pipesToAst';
15
import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types';
16
import { irWebhookToAst } from '../shared/webhook';
17
import type { ValibotPlugin } from '../types';
···
87
path: ref([...fromRef(state.path), 'items', index]),
88
},
89
});
90
-
return pipesToAst(itemAst.pipes, plugin);
91
});
92
93
if (schema.logicalOperator === 'and') {
···
129
$(v)
130
.attr(identifiers.schemas.optional)
131
.call(
132
-
pipesToAst(ast.pipes, plugin),
133
schema.type === 'integer' || schema.type === 'number'
134
? maybeBigInt(schema.default, schema.format)
135
: $.fromValue(schema.default),
···
139
ast.pipes = [
140
$(v)
141
.attr(identifiers.schemas.optional)
142
-
.call(pipesToAst(ast.pipes, plugin)),
143
];
144
}
145
}
···
11
12
import { exportAst } from '../shared/export';
13
import { irOperationToAst } from '../shared/operation';
14
+
import { pipesToNode } from '../shared/pipes';
15
import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types';
16
import { irWebhookToAst } from '../shared/webhook';
17
import type { ValibotPlugin } from '../types';
···
87
path: ref([...fromRef(state.path), 'items', index]),
88
},
89
});
90
+
return pipesToNode(itemAst.pipes, plugin);
91
});
92
93
if (schema.logicalOperator === 'and') {
···
129
$(v)
130
.attr(identifiers.schemas.optional)
131
.call(
132
+
pipesToNode(ast.pipes, plugin),
133
schema.type === 'integer' || schema.type === 'number'
134
? maybeBigInt(schema.default, schema.format)
135
: $.fromValue(schema.default),
···
139
ast.pipes = [
140
$(v)
141
.attr(identifiers.schemas.optional)
142
+
.call(pipesToNode(ast.pipes, plugin)),
143
];
144
}
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
import type { SchemaWithType } from '~/plugins';
5
import { $ } from '~/ts-dsl';
6
7
-
import { pipesToAst } from '../../shared/pipesToAst';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
import { identifiers } from '../constants';
10
import { irSchemaToAst } from '../plugin';
···
54
if (itemAst.hasLazyExpression) {
55
result.hasLazyExpression = true;
56
}
57
-
return pipesToAst(itemAst.pipes, plugin);
58
});
59
60
if (itemExpressions.length === 1) {
···
4
import type { SchemaWithType } from '~/plugins';
5
import { $ } from '~/ts-dsl';
6
7
+
import { pipesToNode } from '../../shared/pipes';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
import { identifiers } from '../constants';
10
import { irSchemaToAst } from '../plugin';
···
54
if (itemAst.hasLazyExpression) {
55
result.hasLazyExpression = true;
56
}
57
+
return pipesToNode(itemAst.pipes, plugin);
58
});
59
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
import type { SchemaWithType } from '~/plugins';
2
import { $ } from '~/ts-dsl';
3
4
-
import { pipesToAst } from '../../shared/pipesToAst';
5
import type { IrSchemaToAstOptions } from '../../shared/types';
6
import { identifiers } from '../constants';
7
···
22
pipes.push(
23
$(v).attr(identifiers.schemas.literal).call($.literal(schema.const)),
24
);
25
-
return pipesToAst(pipes, plugin);
26
}
27
28
pipes.push($(v).attr(identifiers.schemas.boolean).call());
29
-
return pipesToAst(pipes, plugin);
30
};
···
1
import type { SchemaWithType } from '~/plugins';
2
import { $ } from '~/ts-dsl';
3
4
+
import { pipesToNode } from '../../shared/pipes';
5
import type { IrSchemaToAstOptions } from '../../shared/types';
6
import { identifiers } from '../constants';
7
···
22
pipes.push(
23
$(v).attr(identifiers.schemas.literal).call($.literal(schema.const)),
24
);
25
+
return pipesToNode(pipes, plugin);
26
}
27
28
pipes.push($(v).attr(identifiers.schemas.boolean).call());
29
+
return pipesToNode(pipes, plugin);
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
import { shouldCoerceToBigInt } from '~/plugins/shared/utils/coerce';
3
import type { $ } from '~/ts-dsl';
4
5
-
import { pipesToAst } from '../../shared/pipesToAst';
6
import type { IrSchemaToAstOptions } from '../../shared/types';
7
import { arrayToAst } from './array';
8
import { booleanToAst } from './boolean';
9
import { enumToAst } from './enum';
10
import { neverToAst } from './never';
11
import { nullToAst } from './null';
12
-
import { numberToAst } from './number';
13
import { objectToAst } from './object';
14
-
import { stringToAst } from './string';
15
import { tupleToAst } from './tuple';
16
import { undefinedToAst } from './undefined';
17
import { unknownToAst } from './unknown';
···
29
switch (schema.type) {
30
case 'array':
31
return {
32
-
expression: pipesToAst(
33
arrayToAst({
34
...args,
35
schema: schema as SchemaWithType<'array'>,
···
54
case 'integer':
55
case 'number':
56
return {
57
-
expression: numberToAst({
58
...args,
59
schema: schema as SchemaWithType<'integer' | 'number'>,
60
}),
···
75
};
76
case 'object':
77
return {
78
-
expression: pipesToAst(
79
objectToAst({
80
...args,
81
schema: schema as SchemaWithType<'object'>,
···
86
case 'string':
87
return {
88
expression: shouldCoerceToBigInt(schema.format)
89
-
? numberToAst({
90
...args,
91
schema: { ...schema, type: 'number' },
92
})
93
-
: stringToAst({
94
...args,
95
schema: schema as SchemaWithType<'string'>,
96
}),
97
};
98
case 'tuple':
99
return {
100
-
expression: pipesToAst(
101
tupleToAst({
102
...args,
103
schema: schema as SchemaWithType<'tuple'>,
···
2
import { shouldCoerceToBigInt } from '~/plugins/shared/utils/coerce';
3
import type { $ } from '~/ts-dsl';
4
5
+
import { pipesToNode } from '../../shared/pipes';
6
import type { IrSchemaToAstOptions } from '../../shared/types';
7
import { arrayToAst } from './array';
8
import { booleanToAst } from './boolean';
9
import { enumToAst } from './enum';
10
import { neverToAst } from './never';
11
import { nullToAst } from './null';
12
+
import { numberToNode } from './number';
13
import { objectToAst } from './object';
14
+
import { stringToNode } from './string';
15
import { tupleToAst } from './tuple';
16
import { undefinedToAst } from './undefined';
17
import { unknownToAst } from './unknown';
···
29
switch (schema.type) {
30
case 'array':
31
return {
32
+
expression: pipesToNode(
33
arrayToAst({
34
...args,
35
schema: schema as SchemaWithType<'array'>,
···
54
case 'integer':
55
case 'number':
56
return {
57
+
expression: numberToNode({
58
...args,
59
schema: schema as SchemaWithType<'integer' | 'number'>,
60
}),
···
75
};
76
case 'object':
77
return {
78
+
expression: pipesToNode(
79
objectToAst({
80
...args,
81
schema: schema as SchemaWithType<'object'>,
···
86
case 'string':
87
return {
88
expression: shouldCoerceToBigInt(schema.format)
89
+
? numberToNode({
90
...args,
91
schema: { ...schema, type: 'number' },
92
})
93
+
: stringToNode({
94
...args,
95
schema: schema as SchemaWithType<'string'>,
96
}),
97
};
98
case 'tuple':
99
return {
100
+
expression: pipesToNode(
101
tupleToAst({
102
...args,
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
import { getIntegerLimit } from '~/plugins/shared/utils/formats';
7
import { $ } from '~/ts-dsl';
8
9
-
import { pipesToAst } from '../../shared/pipesToAst';
10
import type { IrSchemaToAstOptions } from '../../shared/types';
11
import { identifiers } from '../constants';
12
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(
34
$(v)
35
.attr(identifiers.schemas.union)
36
.call(
···
43
$(v)
44
.attr(identifiers.actions.transform)
45
.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
-
}
52
}
53
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
-
);
70
}
71
72
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
-
);
84
}
85
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
-
}
99
100
-
return pipesToAst(pipes, plugin);
101
};
···
6
import { getIntegerLimit } from '~/plugins/shared/utils/formats';
7
import { $ } from '~/ts-dsl';
8
9
+
import type { Pipe, PipeResult, Pipes } from '../../shared/pipes';
10
+
import { pipes } from '../../shared/pipes';
11
import type { IrSchemaToAstOptions } from '../../shared/types';
12
+
import type { NumberResolverContext } from '../../types';
13
import { identifiers } from '../constants';
14
15
+
function baseNode(ctx: NumberResolverContext): PipeResult {
16
+
const { schema, symbols } = ctx;
17
+
const { v } = symbols;
18
+
if (ctx.utils.shouldCoerceToBigInt(schema.format)) {
19
+
return [
20
$(v)
21
.attr(identifiers.schemas.union)
22
.call(
···
29
$(v)
30
.attr(identifiers.actions.transform)
31
.call($.func().param('x').do($('BigInt').call('x').return())),
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());
38
}
39
+
return pipes;
40
+
}
41
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));
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
+
}
75
76
+
function minNode(ctx: NumberResolverContext): PipeResult | undefined {
77
+
const { schema, symbols } = ctx;
78
+
const { v } = symbols;
79
if (schema.exclusiveMinimum !== undefined) {
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
+
);
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);
104
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
+
}
116
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);
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
import type { SchemaWithType } from '~/plugins';
4
import { $ } from '~/ts-dsl';
5
6
-
import { pipesToAst } from '../../shared/pipesToAst';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
-
import type { ObjectBaseResolverArgs } from '../../types';
9
import { identifiers } from '../constants';
10
import { irSchemaToAst } from '../plugin';
11
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',
21
});
22
23
-
// Handle `additionalProperties: { type: 'never' }` → v.strictObject()
24
if (additional === null) {
25
-
return pipes.push($(v).attr(identifiers.schemas.strictObject).call(shape));
26
}
27
28
-
// Handle additionalProperties as schema → v.record() or v.objectWithRest()
29
if (additional) {
30
if (shape.isEmpty) {
31
-
return pipes.push(
32
-
$(v)
33
-
.attr(identifiers.schemas.record)
34
-
.call($(v).attr(identifiers.schemas.string).call(), additional),
35
-
);
36
}
37
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
-
);
42
}
43
44
-
// Default case → v.object()
45
-
return pipes.push($(v).attr(identifiers.schemas.object).call(shape));
46
}
47
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
-
58
// TODO: parser - handle constants
59
60
const shape = $.object().pretty();
61
-
const required = schema.required ?? [];
62
63
for (const name in schema.properties) {
64
const property = schema.properties[name]!;
65
-
const isRequired = required.includes(name);
66
67
const propertyAst = irSchemaToAst({
68
-
optional: !isRequired,
69
plugin,
70
schema: property,
71
state: {
72
-
...state,
73
-
path: ref([...fromRef(state.path), 'properties', name]),
74
},
75
});
76
-
if (propertyAst.hasLazyExpression) result.hasLazyExpression = true;
77
-
78
-
shape.prop(name, pipesToAst(propertyAst.pipes, plugin));
79
}
80
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
-
}
98
99
-
const args: ObjectBaseResolverArgs = {
100
$,
101
-
additional,
102
-
pipes,
103
plugin,
104
schema,
105
-
shape,
106
};
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'>;
112
};
···
3
import type { SchemaWithType } from '~/plugins';
4
import { $ } from '~/ts-dsl';
5
6
+
import type { Pipe, PipeResult } from '../../shared/pipes';
7
+
import { pipes } from '../../shared/pipes';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
+
import type { ObjectResolverContext } from '../../types';
10
import { identifiers } from '../constants';
11
import { irSchemaToAst } from '../plugin';
12
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
+
},
28
});
29
+
if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
30
+
return pipes.toNode(additionalAst.pipes, plugin);
31
+
}
32
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
+
40
if (additional === null) {
41
+
return $(v).attr(identifiers.schemas.strictObject).call(shape);
42
}
43
44
if (additional) {
45
if (shape.isEmpty) {
46
+
return $(v)
47
+
.attr(identifiers.schemas.record)
48
+
.call($(v).attr(identifiers.schemas.string).call(), additional);
49
}
50
51
+
return $(v)
52
+
.attr(identifiers.schemas.objectWithRest)
53
+
.call(shape, additional);
54
}
55
56
+
return $(v).attr(identifiers.schemas.object).call(shape);
57
}
58
59
+
function objectResolver(ctx: ObjectResolverContext): PipeResult {
60
// TODO: parser - handle constants
61
+
return ctx.nodes.base(ctx);
62
+
}
63
64
+
function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> {
65
+
const { plugin, schema } = ctx;
66
const shape = $.object().pretty();
67
68
for (const name in schema.properties) {
69
const property = schema.properties[name]!;
70
71
const propertyAst = irSchemaToAst({
72
+
optional: !schema.required?.includes(name),
73
plugin,
74
schema: property,
75
state: {
76
+
...ctx.utils.state,
77
+
path: ref([...fromRef(ctx.utils.state.path), 'properties', name]),
78
},
79
});
80
+
if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
81
+
shape.prop(name, pipes.toNode(propertyAst.pipes, plugin));
82
}
83
84
+
return shape;
85
+
}
86
87
+
export const objectToAst = ({
88
+
plugin,
89
+
schema,
90
+
state,
91
+
}: IrSchemaToAstOptions & {
92
+
schema: SchemaWithType<'object'>;
93
+
}): Omit<Ast, 'typeName'> => {
94
+
const ctx: ObjectResolverContext = {
95
$,
96
+
nodes: {
97
+
additionalProperties: additionalPropertiesNode,
98
+
base: baseNode,
99
+
shape: shapeNode,
100
+
},
101
+
pipes: {
102
+
...pipes,
103
+
current: [],
104
+
},
105
plugin,
106
schema,
107
+
symbols: {
108
+
v: plugin.external('valibot.v'),
109
+
},
110
+
utils: {
111
+
ast: {},
112
+
state,
113
+
},
114
};
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'>;
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
import type { SchemaWithType } from '~/plugins';
2
import { $ } from '~/ts-dsl';
3
4
-
import { pipesToAst } from '../../shared/pipesToAst';
5
import type { IrSchemaToAstOptions } from '../../shared/types';
6
-
import type { FormatResolverArgs } from '../../types';
7
import { identifiers } from '../constants';
8
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
-
});
18
19
switch (schema.format) {
20
case 'date':
21
-
return pipes.push($(v).attr(identifiers.actions.isoDate).call());
22
case 'date-time':
23
-
return pipes.push($(v).attr(identifiers.actions.isoTimestamp).call());
24
case 'email':
25
-
return pipes.push($(v).attr(identifiers.actions.email).call());
26
case 'ipv4':
27
case 'ipv6':
28
-
return pipes.push($(v).attr(identifiers.actions.ip).call());
29
case 'time':
30
-
return pipes.push($(v).attr(identifiers.actions.isoTimeSecond).call());
31
case 'uri':
32
-
return pipes.push($(v).attr(identifiers.actions.url).call());
33
case 'uuid':
34
-
return pipes.push($(v).attr(identifiers.actions.uuid).call());
35
}
36
37
-
return true;
38
-
};
39
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
-
});
50
51
-
if (typeof schema.const === 'string') {
52
-
return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const));
53
-
}
54
55
-
const pipes: Array<ReturnType<typeof $.call>> = [];
56
-
pipes.push($(v).attr(identifiers.schemas.string).call());
57
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
-
}
64
65
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
66
-
pipes.push(
67
-
$(v).attr(identifiers.actions.length).call($.literal(schema.minLength)),
68
-
);
69
} else {
70
-
if (schema.minLength !== undefined) {
71
-
pipes.push(
72
-
$(v)
73
-
.attr(identifiers.actions.minLength)
74
-
.call($.literal(schema.minLength)),
75
-
);
76
-
}
77
78
-
if (schema.maxLength !== undefined) {
79
-
pipes.push(
80
-
$(v)
81
-
.attr(identifiers.actions.maxLength)
82
-
.call($.literal(schema.maxLength)),
83
-
);
84
-
}
85
}
86
87
-
if (schema.pattern) {
88
-
pipes.push(
89
-
$(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)),
90
-
);
91
-
}
92
93
-
return pipesToAst(pipes, plugin);
94
};
···
1
import type { SchemaWithType } from '~/plugins';
2
import { $ } from '~/ts-dsl';
3
4
+
import type { Pipe, PipeResult, Pipes } from '../../shared/pipes';
5
+
import { pipes } from '../../shared/pipes';
6
import type { IrSchemaToAstOptions } from '../../shared/types';
7
+
import type { StringResolverContext } from '../../types';
8
import { identifiers } from '../constants';
9
10
+
function baseNode(ctx: StringResolverContext): PipeResult {
11
+
const { v } = ctx.symbols;
12
+
return $(v).attr(identifiers.schemas.string).call();
13
+
}
14
15
+
function constNode(ctx: StringResolverContext): PipeResult | undefined {
16
+
const { schema, symbols } = ctx;
17
+
const { v } = symbols;
18
+
if (typeof schema.const !== 'string') return;
19
+
return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const));
20
+
}
21
+
22
+
function formatNode(ctx: StringResolverContext): PipeResult | undefined {
23
+
const { schema, symbols } = ctx;
24
+
const { v } = symbols;
25
switch (schema.format) {
26
case 'date':
27
+
return $(v).attr(identifiers.actions.isoDate).call();
28
case 'date-time':
29
+
return $(v).attr(identifiers.actions.isoTimestamp).call();
30
case 'email':
31
+
return $(v).attr(identifiers.actions.email).call();
32
case 'ipv4':
33
case 'ipv6':
34
+
return $(v).attr(identifiers.actions.ip).call();
35
case 'time':
36
+
return $(v).attr(identifiers.actions.isoTimeSecond).call();
37
case 'uri':
38
+
return $(v).attr(identifiers.actions.url).call();
39
case 'uuid':
40
+
return $(v).attr(identifiers.actions.uuid).call();
41
}
42
43
+
return;
44
+
}
45
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
+
}
55
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
+
}
64
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
+
}
73
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
+
}
80
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);
94
} else {
95
+
const minLengthNode = ctx.nodes.minLength(ctx);
96
+
if (minLengthNode) ctx.pipes.push(ctx.pipes.current, minLengthNode);
97
98
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
99
+
if (maxLengthNode) ctx.pipes.push(ctx.pipes.current, maxLengthNode);
100
}
101
102
+
const patternNode = ctx.nodes.pattern(ctx);
103
+
if (patternNode) ctx.pipes.push(ctx.pipes.current, patternNode);
104
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);
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
import type { SchemaWithType } from '~/plugins';
4
import { $ } from '~/ts-dsl';
5
6
-
import { pipesToAst } from '../../shared/pipesToAst';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
import { identifiers } from '../constants';
9
import { irSchemaToAst } from '../plugin';
···
48
if (schemaPipes.hasLazyExpression) {
49
result.hasLazyExpression = true;
50
}
51
-
return pipesToAst(schemaPipes.pipes, plugin);
52
});
53
result.pipes = [
54
$(v)
···
3
import type { SchemaWithType } from '~/plugins';
4
import { $ } from '~/ts-dsl';
5
6
+
import { pipesToNode } from '../../shared/pipes';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
import { identifiers } from '../constants';
9
import { irSchemaToAst } from '../plugin';
···
48
if (schemaPipes.hasLazyExpression) {
49
result.hasLazyExpression = true;
50
}
51
+
return pipesToNode(schemaPipes.pipes, plugin);
52
});
53
result.pipes = [
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
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
-
import type { ValidatorResolverArgs } from '../types';
6
7
-
const defaultValidatorResolver = ({
8
-
schema,
9
-
}: ValidatorResolverArgs): ReturnType<typeof $.return> =>
10
-
$(schema).attr(identifiers.parseAsync).call('data').await().return();
11
12
export const createRequestValidatorMini = ({
13
operation,
···
22
});
23
if (!symbol) return;
24
25
-
const args: ValidatorResolverArgs = {
26
$,
27
-
chain: undefined,
28
operation,
29
plugin,
30
-
schema: symbol,
31
};
32
const validator = plugin.config['~resolvers']?.validator;
33
const resolver =
34
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
36
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
38
if (statements === null) return;
39
if (statements !== undefined) {
40
return $.func()
···
59
});
60
if (!symbol) return;
61
62
-
const args: ValidatorResolverArgs = {
63
$,
64
-
chain: undefined,
65
operation,
66
plugin,
67
-
schema: symbol,
68
};
69
const validator = plugin.config['~resolvers']?.validator;
70
const resolver =
71
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
73
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
75
if (statements === null) return;
76
if (statements !== undefined) {
77
return $.func()
···
2
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
+
import type { ValidatorResolverContext } from '../types';
6
7
+
const validatorResolver = (
8
+
ctx: ValidatorResolverContext,
9
+
): ReturnType<typeof $.return> => {
10
+
const { schema } = ctx.symbols;
11
+
return $(schema).attr(identifiers.parseAsync).call('data').await().return();
12
+
};
13
14
export const createRequestValidatorMini = ({
15
operation,
···
24
});
25
if (!symbol) return;
26
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
29
$,
30
+
chain: {
31
+
current: $(z),
32
+
},
33
operation,
34
plugin,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
39
};
40
const validator = plugin.config['~resolvers']?.validator;
41
const resolver =
42
typeof validator === 'function' ? validator : validator?.request;
43
+
const candidates = [resolver, validatorResolver];
44
for (const candidate of candidates) {
45
+
const statements = candidate?.(ctx);
46
if (statements === null) return;
47
if (statements !== undefined) {
48
return $.func()
···
67
});
68
if (!symbol) return;
69
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
72
$,
73
+
chain: {
74
+
current: $(z),
75
+
},
76
operation,
77
plugin,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
82
};
83
const validator = plugin.config['~resolvers']?.validator;
84
const resolver =
85
typeof validator === 'function' ? validator : validator?.response;
86
+
const candidates = [resolver, validatorResolver];
87
for (const candidate of candidates) {
88
+
const statements = candidate?.(ctx);
89
if (statements === null) return;
90
if (statements !== undefined) {
91
return $.func()
+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
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
-
import { numberToAst } from './number';
11
import { objectToAst } from './object';
12
-
import { stringToAst } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
39
});
40
case 'integer':
41
case 'number':
42
-
return numberToAst({
43
...args,
44
schema: schema as SchemaWithType<'integer' | 'number'>,
45
});
···
60
});
61
case 'string':
62
return shouldCoerceToBigInt(schema.format)
63
-
? numberToAst({
64
...args,
65
schema: { ...schema, type: 'number' },
66
})
67
-
: stringToAst({
68
...args,
69
schema: schema as SchemaWithType<'string'>,
70
});
···
7
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
+
import { numberToNode } from './number';
11
import { objectToAst } from './object';
12
+
import { stringToNode } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
39
});
40
case 'integer':
41
case 'number':
42
+
return numberToNode({
43
...args,
44
schema: schema as SchemaWithType<'integer' | 'number'>,
45
});
···
60
});
61
case 'string':
62
return shouldCoerceToBigInt(schema.format)
63
+
? numberToNode({
64
...args,
65
schema: { ...schema, type: 'number' },
66
})
67
+
: stringToNode({
68
...args,
69
schema: schema as SchemaWithType<'string'>,
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
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
11
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'>;
30
}
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
-
}
42
}
43
44
-
const checks: Array<ReturnType<typeof $.call>> = [];
45
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
-
);
62
}
63
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
-
);
76
}
77
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
-
);
90
}
91
92
-
if (checks.length) {
93
-
result.expression = result.expression
94
.attr(identifiers.check)
95
.call(...checks);
96
}
97
98
-
return result as Omit<Ast, 'typeName'>;
99
};
···
7
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
+
import type { Chain } from '../../shared/chain';
11
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
12
+
import type { NumberResolverContext } from '../../types';
13
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
+
}
26
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
+
}
35
36
+
function maxNode(ctx: NumberResolverContext): Chain | undefined {
37
+
const { schema, symbols } = ctx;
38
+
const { z } = symbols;
39
+
if (schema.exclusiveMaximum !== undefined) {
40
+
return $(z)
41
+
.attr(identifiers.lt)
42
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format));
43
+
}
44
+
if (schema.maximum !== undefined) {
45
+
return $(z)
46
+
.attr(identifiers.lte)
47
+
.call(ctx.utils.maybeBigInt(schema.maximum, schema.format));
48
+
}
49
+
const limit = ctx.utils.getIntegerLimit(schema.format);
50
+
if (limit) {
51
+
return $(z)
52
+
.attr(identifiers.maximum)
53
+
.call(
54
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
55
+
$.object().prop('error', $.literal(limit.maxError)),
56
+
);
57
}
58
+
return;
59
+
}
60
61
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
62
+
const { schema, symbols } = ctx;
63
+
const { z } = symbols;
64
if (schema.exclusiveMinimum !== undefined) {
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
+
);
82
}
83
+
return;
84
+
}
85
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;
91
}
92
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
106
.attr(identifiers.check)
107
.call(...checks);
108
}
109
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'>;
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
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
-
import type { ObjectBaseResolverArgs } from '../../types';
9
import { irSchemaToAst } from '../plugin';
10
11
-
function defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
19
});
20
21
if (additional) {
22
return $(z)
···
27
return $(z).attr(identifiers.object).call(shape);
28
}
29
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
-
39
// TODO: parser - handle constants
40
41
const shape = $.object().pretty();
42
-
const required = schema.required ?? [];
43
44
for (const name in schema.properties) {
45
const property = schema.properties[name]!;
46
-
const isRequired = required.includes(name);
47
48
const propertyAst = irSchemaToAst({
49
-
optional: !isRequired,
50
plugin,
51
schema: property,
52
state: {
53
-
...state,
54
-
path: ref([...fromRef(state.path), 'properties', name]),
55
},
56
});
57
if (propertyAst.hasLazyExpression) {
58
-
result.hasLazyExpression = true;
59
-
}
60
-
61
-
if (propertyAst.hasLazyExpression) {
62
shape.getter(name, propertyAst.expression.return());
63
} else {
64
shape.prop(name, propertyAst.expression);
65
}
66
}
67
68
-
let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined;
69
-
if (
70
-
schema.additionalProperties &&
71
-
(!schema.properties || !Object.keys(schema.properties).length)
72
-
) {
73
-
const additionalAst = irSchemaToAst({
74
-
plugin,
75
-
schema: schema.additionalProperties,
76
-
state: {
77
-
...state,
78
-
path: ref([...fromRef(state.path), 'additionalProperties']),
79
-
},
80
-
});
81
-
if (additionalAst.hasLazyExpression) result.hasLazyExpression = true;
82
-
additional = additionalAst.expression;
83
-
}
84
85
-
const args: ObjectBaseResolverArgs = {
86
$,
87
-
additional,
88
-
chain: undefined,
89
plugin,
90
schema,
91
-
shape,
92
};
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'>;
98
};
···
4
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
+
import type { Chain } from '../../shared/chain';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
+
import type { ObjectResolverContext } from '../../types';
10
import { irSchemaToAst } from '../plugin';
11
12
+
function additionalPropertiesNode(
13
+
ctx: ObjectResolverContext,
14
+
): Chain | null | undefined {
15
+
const { plugin, schema } = ctx;
16
+
17
+
if (
18
+
!schema.additionalProperties ||
19
+
(schema.properties && Object.keys(schema.properties).length > 0)
20
+
)
21
+
return;
22
+
23
+
const additionalAst = irSchemaToAst({
24
+
plugin,
25
+
schema: schema.additionalProperties,
26
+
state: {
27
+
...ctx.utils.state,
28
+
path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']),
29
+
},
30
});
31
+
if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
32
+
return additionalAst.expression;
33
+
}
34
+
35
+
function baseNode(ctx: ObjectResolverContext): Chain {
36
+
const { nodes, symbols } = ctx;
37
+
const { z } = symbols;
38
+
39
+
const additional = nodes.additionalProperties(ctx);
40
+
const shape = nodes.shape(ctx);
41
42
if (additional) {
43
return $(z)
···
48
return $(z).attr(identifiers.object).call(shape);
49
}
50
51
+
function objectResolver(ctx: ObjectResolverContext): Chain {
52
// TODO: parser - handle constants
53
+
return ctx.nodes.base(ctx);
54
+
}
55
56
+
function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> {
57
+
const { plugin, schema } = ctx;
58
const shape = $.object().pretty();
59
60
for (const name in schema.properties) {
61
const property = schema.properties[name]!;
62
63
const propertyAst = irSchemaToAst({
64
+
optional: !schema.required?.includes(name),
65
plugin,
66
schema: property,
67
state: {
68
+
...ctx.utils.state,
69
+
path: ref([...fromRef(ctx.utils.state.path), 'properties', name]),
70
},
71
});
72
if (propertyAst.hasLazyExpression) {
73
+
ctx.utils.ast.hasLazyExpression = true;
74
shape.getter(name, propertyAst.expression.return());
75
} else {
76
shape.prop(name, propertyAst.expression);
77
}
78
}
79
80
+
return shape;
81
+
}
82
83
+
export const objectToAst = ({
84
+
plugin,
85
+
schema,
86
+
state,
87
+
}: IrSchemaToAstOptions & {
88
+
schema: SchemaWithType<'object'>;
89
+
}): Omit<Ast, 'typeName'> => {
90
+
const ast: Partial<Omit<Ast, 'typeName'>> = {};
91
+
const z = plugin.external('zod.z');
92
+
const ctx: ObjectResolverContext = {
93
$,
94
+
chain: {
95
+
current: $(z),
96
+
},
97
+
nodes: {
98
+
additionalProperties: additionalPropertiesNode,
99
+
base: baseNode,
100
+
shape: shapeNode,
101
+
},
102
plugin,
103
schema,
104
+
symbols: {
105
+
z,
106
+
},
107
+
utils: {
108
+
ast,
109
+
state,
110
+
},
111
};
112
+
const resolver = plugin.config['~resolvers']?.object;
113
+
const node = resolver?.(ctx) ?? objectResolver(ctx);
114
+
ast.expression = node;
115
+
return ast as Omit<Ast, 'typeName'>;
116
};
+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
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
6
-
import type { FormatResolverArgs } from '../../types';
7
8
-
const defaultFormatResolver = ({
9
-
chain,
10
-
plugin,
11
-
schema,
12
-
}: FormatResolverArgs): ReturnType<typeof $.call> => {
13
-
const z = plugin.referenceSymbol({
14
-
category: 'external',
15
-
resource: 'zod.z',
16
-
});
17
18
switch (schema.format) {
19
case 'date':
···
43
return $(z).attr(identifiers.url).call();
44
case 'uuid':
45
return $(z).attr(identifiers.uuid).call();
46
-
default:
47
-
return chain;
48
}
49
-
};
50
51
-
export const stringToAst = ({
52
-
plugin,
53
-
schema,
54
-
}: IrSchemaToAstOptions & {
55
-
schema: SchemaWithType<'string'>;
56
-
}): Omit<Ast, 'typeName'> => {
57
-
const result: Partial<Omit<Ast, 'typeName'>> = {};
58
-
let chain: ReturnType<typeof $.call>;
59
60
-
const z = plugin.referenceSymbol({
61
-
category: 'external',
62
-
resource: 'zod.z',
63
-
});
64
65
-
if (typeof schema.const === 'string') {
66
-
chain = $(z).attr(identifiers.literal).call($.literal(schema.const));
67
-
result.expression = chain;
68
-
return result as Omit<Ast, 'typeName'>;
69
}
70
71
-
chain = $(z).attr(identifiers.string).call();
72
73
-
if (schema.format) {
74
-
const args: FormatResolverArgs = { $, chain, plugin, schema };
75
-
const resolver =
76
-
plugin.config['~resolvers']?.string?.formats?.[schema.format];
77
-
chain = resolver?.(args) ?? defaultFormatResolver(args);
78
-
}
79
80
-
const checks: Array<ReturnType<typeof $.call>> = [];
81
82
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
83
-
checks.push(
84
-
$(z).attr(identifiers.length).call($.literal(schema.minLength)),
85
-
);
86
} else {
87
-
if (schema.minLength !== undefined) {
88
-
checks.push(
89
-
$(z).attr(identifiers.minLength).call($.literal(schema.minLength)),
90
-
);
91
-
}
92
93
-
if (schema.maxLength !== undefined) {
94
-
checks.push(
95
-
$(z).attr(identifiers.maxLength).call($.literal(schema.maxLength)),
96
-
);
97
-
}
98
}
99
100
-
if (schema.pattern) {
101
-
checks.push($(z).attr(identifiers.regex).call($.regexp(schema.pattern)));
102
}
103
104
-
if (checks.length) {
105
-
chain = chain.attr(identifiers.check).call(...checks);
106
-
}
107
108
-
result.expression = chain;
109
-
return result as Omit<Ast, 'typeName'>;
110
};
···
2
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
+
import type { Chain } from '../../shared/chain';
6
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
7
+
import type { StringResolverContext } from '../../types';
8
+
9
+
function baseNode(ctx: StringResolverContext): Chain {
10
+
const { z } = ctx.symbols;
11
+
return $(z).attr(identifiers.string).call();
12
+
}
13
+
14
+
function constNode(ctx: StringResolverContext): Chain | undefined {
15
+
const { schema, symbols } = ctx;
16
+
const { z } = symbols;
17
+
if (typeof schema.const !== 'string') return;
18
+
return $(z).attr(identifiers.literal).call($.literal(schema.const));
19
+
}
20
21
+
function formatNode(ctx: StringResolverContext): Chain | undefined {
22
+
const { plugin, schema, symbols } = ctx;
23
+
const { z } = symbols;
24
25
switch (schema.format) {
26
case 'date':
···
50
return $(z).attr(identifiers.url).call();
51
case 'uuid':
52
return $(z).attr(identifiers.uuid).call();
53
}
54
55
+
return;
56
+
}
57
58
+
function lengthNode(ctx: StringResolverContext): Chain | undefined {
59
+
const { schema, symbols } = ctx;
60
+
const { z } = symbols;
61
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
62
+
return;
63
+
return $(z).attr(identifiers.length).call($.literal(schema.minLength));
64
+
}
65
+
66
+
function maxLengthNode(ctx: StringResolverContext): Chain | undefined {
67
+
const { schema, symbols } = ctx;
68
+
const { z } = symbols;
69
+
if (schema.maxLength === undefined) return;
70
+
return $(z).attr(identifiers.maxLength).call($.literal(schema.maxLength));
71
+
}
72
+
73
+
function minLengthNode(ctx: StringResolverContext): Chain | undefined {
74
+
const { schema, symbols } = ctx;
75
+
const { z } = symbols;
76
+
if (schema.minLength === undefined) return;
77
+
return $(z).attr(identifiers.minLength).call($.literal(schema.minLength));
78
+
}
79
80
+
function patternNode(ctx: StringResolverContext): Chain | undefined {
81
+
const { schema, symbols } = ctx;
82
+
const { z } = symbols;
83
+
if (!schema.pattern) return;
84
+
return $(z).attr(identifiers.regex).call($.regexp(schema.pattern));
85
+
}
86
+
87
+
function stringResolver(ctx: StringResolverContext): Chain {
88
+
const constNode = ctx.nodes.const(ctx);
89
+
if (constNode) {
90
+
ctx.chain.current = constNode;
91
+
return ctx.chain.current;
92
}
93
94
+
const baseNode = ctx.nodes.base(ctx);
95
+
if (baseNode) ctx.chain.current = baseNode;
96
97
+
const formatNode = ctx.nodes.format(ctx);
98
+
if (formatNode) ctx.chain.current = formatNode;
99
100
+
const checks: Array<Chain> = [];
101
102
+
const lengthNode = ctx.nodes.length(ctx);
103
+
if (lengthNode) {
104
+
checks.push(lengthNode);
105
} else {
106
+
const minLengthNode = ctx.nodes.minLength(ctx);
107
+
if (minLengthNode) checks.push(minLengthNode);
108
109
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
110
+
if (maxLengthNode) checks.push(maxLengthNode);
111
}
112
113
+
const patternNode = ctx.nodes.pattern(ctx);
114
+
if (patternNode) checks.push(patternNode);
115
+
116
+
if (checks.length > 0) {
117
+
ctx.chain.current = ctx.chain.current
118
+
.attr(identifiers.check)
119
+
.call(...checks);
120
}
121
122
+
return ctx.chain.current;
123
+
}
124
125
+
export const stringToNode = ({
126
+
plugin,
127
+
schema,
128
+
}: IrSchemaToAstOptions & {
129
+
schema: SchemaWithType<'string'>;
130
+
}): Omit<Ast, 'typeName'> => {
131
+
const z = plugin.external('zod.z');
132
+
const ctx: StringResolverContext = {
133
+
$,
134
+
chain: {
135
+
current: $(z),
136
+
},
137
+
nodes: {
138
+
base: baseNode,
139
+
const: constNode,
140
+
format: formatNode,
141
+
length: lengthNode,
142
+
maxLength: maxLengthNode,
143
+
minLength: minLengthNode,
144
+
pattern: patternNode,
145
+
},
146
+
plugin,
147
+
schema,
148
+
symbols: {
149
+
z,
150
+
},
151
+
};
152
+
const resolver = plugin.config['~resolvers']?.string;
153
+
const node = resolver?.(ctx) ?? stringResolver(ctx);
154
+
return {
155
+
expression: node,
156
+
};
157
};
+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';
2
import type ts from 'typescript';
3
4
import type { IR } from '~/ir/types';
5
-
import type { DefinePlugin, Plugin } from '~/plugins';
6
import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl';
7
import type { StringCase, StringName } from '~/types/case';
8
import type { MaybeArray } from '~/types/utils';
9
10
import type { IApi } from './api';
11
12
export type UserConfig = Plugin.Name<'zod'> &
13
Plugin.Hooks &
···
747
};
748
};
749
750
-
type SharedResolverArgs = DollarTsDsl & {
751
/**
752
-
* The current fluent builder chain under construction for this resolver.
753
-
*
754
-
* Represents the in-progress call sequence (e.g., a Zod or DSL chain)
755
-
* that defines the current schema or expression being generated.
756
-
*
757
-
* This chain can be extended, transformed, or replaced entirely to customize
758
-
* the resulting output of the resolver.
759
*/
760
-
chain?: ReturnType<typeof $.call>;
761
plugin: ZodPlugin['Instance'];
762
-
};
763
764
-
export type FormatResolverArgs = Required<SharedResolverArgs> & {
765
-
schema: IR.SchemaObject;
766
-
};
767
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
-
};
774
775
-
export type ValidatorResolverArgs = SharedResolverArgs & {
776
operation: IR.Operation;
777
-
schema: Symbol;
778
-
};
779
780
type ValidatorResolver = (
781
-
args: ValidatorResolverArgs,
782
) => MaybeArray<TsDsl<ts.Statement>> | null | undefined;
783
784
type Resolvers = Plugin.Resolvers<{
785
/**
786
-
* Resolvers for number schemas.
787
*
788
-
* Allows customization of how number types are rendered, including
789
-
* per-format handling.
790
*/
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
-
};
808
/**
809
-
* Resolvers for object schemas.
810
*
811
* Allows customization of how object types are rendered.
812
*
813
-
* Example path: `~resolvers.object.base`
814
-
*
815
-
* Returning `undefined` from a resolver will apply the default
816
-
* generation behavior for the object schema.
817
*/
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
-
};
832
/**
833
-
* Resolvers for string schemas.
834
*
835
-
* Allows customization of how string types are rendered, including
836
-
* per-format handling.
837
*/
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
-
};
855
/**
856
* Resolvers for request and response validators.
857
*
···
859
*
860
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
861
*
862
-
* Returning `undefined` from a resolver will apply the default generation logic.
863
*/
864
validator?:
865
| ValidatorResolver
···
867
/**
868
* Controls how the request validator function body is generated.
869
*
870
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
871
*/
872
request?: ValidatorResolver;
873
/**
874
* Controls how the response validator function body is generated.
875
*
876
-
* Returning `undefined` will fall back to the default `.await().return()` logic.
877
*/
878
response?: ValidatorResolver;
879
};
···
1
+
import type { Refs, Symbol } from '@hey-api/codegen-core';
2
import type ts from 'typescript';
3
4
import type { IR } from '~/ir/types';
5
+
import type { DefinePlugin, Plugin, SchemaWithType } from '~/plugins';
6
+
import type {
7
+
MaybeBigInt,
8
+
ShouldCoerceToBigInt,
9
+
} from '~/plugins/shared/utils/coerce';
10
+
import type { GetIntegerLimit } from '~/plugins/shared/utils/formats';
11
import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl';
12
import type { StringCase, StringName } from '~/types/case';
13
import type { MaybeArray } from '~/types/utils';
14
15
import type { IApi } from './api';
16
+
import type { Chain } from './shared/chain';
17
+
import type { Ast, PluginState } from './shared/types';
18
19
export type UserConfig = Plugin.Name<'zod'> &
20
Plugin.Hooks &
···
754
};
755
};
756
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
+
};
773
/**
774
+
* The plugin instance.
775
*/
776
plugin: ZodPlugin['Instance'];
777
+
/**
778
+
* Provides access to commonly used symbols within the plugin.
779
+
*/
780
+
symbols: {
781
+
z: Symbol;
782
+
};
783
+
}
784
785
+
export interface NumberResolverContext extends BaseResolverContext {
786
+
/**
787
+
* Nodes used to build different parts of the number schema.
788
+
*/
789
+
nodes: {
790
+
base: (ctx: NumberResolverContext) => Chain;
791
+
const: (ctx: NumberResolverContext) => Chain | undefined;
792
+
max: (ctx: NumberResolverContext) => Chain | undefined;
793
+
min: (ctx: NumberResolverContext) => Chain | undefined;
794
+
};
795
+
schema: SchemaWithType<'integer' | 'number'>;
796
+
/**
797
+
* Utility functions for number schema processing.
798
+
*/
799
+
utils: {
800
+
ast: Partial<Omit<Ast, 'typeName'>>;
801
+
getIntegerLimit: GetIntegerLimit;
802
+
maybeBigInt: MaybeBigInt;
803
+
shouldCoerceToBigInt: ShouldCoerceToBigInt;
804
+
state: Refs<PluginState>;
805
+
};
806
+
}
807
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
+
}
832
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 {
850
operation: IR.Operation;
851
+
/**
852
+
* Provides access to commonly used symbols within the plugin.
853
+
*/
854
+
symbols: BaseResolverContext['symbols'] & {
855
+
schema: Symbol;
856
+
};
857
+
}
858
859
type ValidatorResolver = (
860
+
ctx: ValidatorResolverContext,
861
) => MaybeArray<TsDsl<ts.Statement>> | null | undefined;
862
863
type Resolvers = Plugin.Resolvers<{
864
/**
865
+
* Resolver for number schemas.
866
*
867
+
* Allows customization of how number types are rendered.
868
+
*
869
+
* Returning `undefined` will execute the default resolver logic.
870
*/
871
+
number?: (ctx: NumberResolverContext) => Chain | undefined;
872
/**
873
+
* Resolver for object schemas.
874
*
875
* Allows customization of how object types are rendered.
876
*
877
+
* Returning `undefined` will execute the default resolver logic.
878
*/
879
+
object?: (ctx: ObjectResolverContext) => Chain | undefined;
880
/**
881
+
* Resolver for string schemas.
882
*
883
+
* Allows customization of how string types are rendered.
884
+
*
885
+
* Returning `undefined` will execute the default resolver logic.
886
*/
887
+
string?: (ctx: StringResolverContext) => Chain | undefined;
888
/**
889
* Resolvers for request and response validators.
890
*
···
892
*
893
* Example path: `~resolvers.validator.request` or `~resolvers.validator.response`
894
*
895
+
* Returning `undefined` will execute the default resolver logic.
896
*/
897
validator?:
898
| ValidatorResolver
···
900
/**
901
* Controls how the request validator function body is generated.
902
*
903
+
* Returning `undefined` will execute the default resolver logic.
904
*/
905
request?: ValidatorResolver;
906
/**
907
* Controls how the response validator function body is generated.
908
*
909
+
* Returning `undefined` will execute the default resolver logic.
910
*/
911
response?: ValidatorResolver;
912
};
+29
-15
packages/openapi-ts/src/plugins/zod/v3/api.ts
+29
-15
packages/openapi-ts/src/plugins/zod/v3/api.ts
···
2
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
-
import type { ValidatorResolverArgs } from '../types';
6
7
-
const defaultValidatorResolver = ({
8
-
schema,
9
-
}: ValidatorResolverArgs): ReturnType<typeof $.return> =>
10
-
$(schema).attr(identifiers.parseAsync).call('data').await().return();
11
12
export const createRequestValidatorV3 = ({
13
operation,
···
22
});
23
if (!symbol) return;
24
25
-
const args: ValidatorResolverArgs = {
26
$,
27
-
chain: undefined,
28
operation,
29
plugin,
30
-
schema: symbol,
31
};
32
const validator = plugin.config['~resolvers']?.validator;
33
const resolver =
34
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
36
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
38
if (statements === null) return;
39
if (statements !== undefined) {
40
return $.func()
···
59
});
60
if (!symbol) return;
61
62
-
const args: ValidatorResolverArgs = {
63
$,
64
-
chain: undefined,
65
operation,
66
plugin,
67
-
schema: symbol,
68
};
69
const validator = plugin.config['~resolvers']?.validator;
70
const resolver =
71
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
73
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
75
if (statements === null) return;
76
if (statements !== undefined) {
77
return $.func()
···
2
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
+
import type { ValidatorResolverContext } from '../types';
6
7
+
const validatorResolver = (
8
+
ctx: ValidatorResolverContext,
9
+
): ReturnType<typeof $.return> => {
10
+
const { schema } = ctx.symbols;
11
+
return $(schema).attr(identifiers.parseAsync).call('data').await().return();
12
+
};
13
14
export const createRequestValidatorV3 = ({
15
operation,
···
24
});
25
if (!symbol) return;
26
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
29
$,
30
+
chain: {
31
+
current: $(z),
32
+
},
33
operation,
34
plugin,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
39
};
40
const validator = plugin.config['~resolvers']?.validator;
41
const resolver =
42
typeof validator === 'function' ? validator : validator?.request;
43
+
const candidates = [resolver, validatorResolver];
44
for (const candidate of candidates) {
45
+
const statements = candidate?.(ctx);
46
if (statements === null) return;
47
if (statements !== undefined) {
48
return $.func()
···
67
});
68
if (!symbol) return;
69
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
72
$,
73
+
chain: {
74
+
current: $(z),
75
+
},
76
operation,
77
plugin,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
82
};
83
const validator = plugin.config['~resolvers']?.validator;
84
const resolver =
85
typeof validator === 'function' ? validator : validator?.response;
86
+
const candidates = [resolver, validatorResolver];
87
for (const candidate of candidates) {
88
+
const statements = candidate?.(ctx);
89
if (statements === null) return;
90
if (statements !== undefined) {
91
return $.func()
+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
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
-
import { numberToAst } from './number';
11
import { objectToAst } from './object';
12
-
import { stringToAst } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
46
case 'integer':
47
case 'number':
48
return {
49
-
expression: numberToAst({
50
...args,
51
schema: schema as SchemaWithType<'integer' | 'number'>,
52
}),
···
73
case 'string':
74
return {
75
expression: shouldCoerceToBigInt(schema.format)
76
-
? numberToAst({
77
...args,
78
schema: { ...schema, type: 'number' },
79
})
80
-
: stringToAst({
81
...args,
82
schema: schema as SchemaWithType<'string'>,
83
}),
···
7
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
+
import { numberToNode } from './number';
11
import { objectToAst } from './object';
12
+
import { stringToNode } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
46
case 'integer':
47
case 'number':
48
return {
49
+
expression: numberToNode({
50
...args,
51
schema: schema as SchemaWithType<'integer' | 'number'>,
52
}),
···
73
case 'string':
74
return {
75
expression: shouldCoerceToBigInt(schema.format)
76
+
? numberToNode({
77
...args,
78
schema: { ...schema, type: 'number' },
79
})
80
+
: stringToNode({
81
...args,
82
schema: schema as SchemaWithType<'string'>,
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
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
-
import type { IrSchemaToAstOptions } from '../../shared/types';
11
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;
28
}
29
30
-
let numberExpression: ReturnType<typeof $.call>;
31
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
-
}
42
}
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
-
)
52
.attr(identifiers.max)
53
.call(
54
-
maybeBigInt(integerLimit.maxValue, schema.format),
55
-
$.object().prop('message', $.literal(integerLimit.maxError)),
56
);
57
}
58
59
if (schema.exclusiveMinimum !== undefined) {
60
-
numberExpression = numberExpression
61
.attr(identifiers.gt)
62
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format));
63
-
} else if (schema.minimum !== undefined) {
64
-
numberExpression = numberExpression
65
.attr(identifiers.gte)
66
-
.call(maybeBigInt(schema.minimum, schema.format));
67
}
68
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));
77
}
78
79
-
return numberExpression;
80
};
···
7
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
+
import type { Chain } from '../../shared/chain';
11
+
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
12
+
import type { NumberResolverContext } from '../../types';
13
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 = chain.attr(identifiers.int).call();
23
+
}
24
+
return chain;
25
+
}
26
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
+
}
35
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));
47
}
48
+
const limit = ctx.utils.getIntegerLimit(schema.format);
49
+
if (limit) {
50
+
return chain.current
51
.attr(identifiers.max)
52
.call(
53
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
54
+
$.object().prop('message', $.literal(limit.maxError)),
55
);
56
}
57
+
return;
58
+
}
59
60
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
61
+
const { chain, schema } = ctx;
62
if (schema.exclusiveMinimum !== undefined) {
63
+
return chain.current
64
.attr(identifiers.gt)
65
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
66
+
}
67
+
if (schema.minimum !== undefined) {
68
+
return chain.current
69
.attr(identifiers.gte)
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
+
);
80
}
81
+
return;
82
+
}
83
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;
89
}
90
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;
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
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
-
import type { ObjectBaseResolverArgs } from '../../types';
9
import { irSchemaToAst } from '../plugin';
10
11
-
function defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
19
});
20
21
if (additional) {
22
return $(z).attr(identifiers.record).call(additional);
···
25
return $(z).attr(identifiers.object).call(shape);
26
}
27
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
-
39
// TODO: parser - handle constants
40
41
const shape = $.object().pretty();
42
-
const required = schema.required ?? [];
43
44
for (const name in schema.properties) {
45
const property = schema.properties[name]!;
46
-
const isRequired = required.includes(name);
47
48
-
const propertyExpression = irSchemaToAst({
49
-
optional: !isRequired,
50
plugin,
51
schema: property,
52
state: {
53
-
...state,
54
-
path: ref([...fromRef(state.path), 'properties', name]),
55
},
56
});
57
-
58
-
if (propertyExpression.hasLazyExpression) hasLazyExpression = true;
59
-
60
-
shape.prop(name, propertyExpression.expression);
61
}
62
63
-
let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined;
64
-
const result: Partial<Omit<Ast, 'typeName'>> = {};
65
-
if (
66
-
schema.additionalProperties &&
67
-
(!schema.properties || !Object.keys(schema.properties).length)
68
-
) {
69
-
const additionalAst = irSchemaToAst({
70
-
plugin,
71
-
schema: schema.additionalProperties,
72
-
state: {
73
-
...state,
74
-
path: ref([...fromRef(state.path), 'additionalProperties']),
75
-
},
76
-
});
77
-
hasLazyExpression = additionalAst.hasLazyExpression || hasLazyExpression;
78
-
additional = additionalAst.expression;
79
-
}
80
81
-
const args: ObjectBaseResolverArgs = {
82
$,
83
-
additional,
84
-
chain: undefined,
85
plugin,
86
schema,
87
-
shape,
88
};
89
-
const resolver = plugin.config['~resolvers']?.object?.base;
90
-
const chain = resolver?.(args) ?? defaultObjectBaseResolver(args);
91
-
result.expression = chain;
92
-
93
return {
94
anyType: 'AnyZodObject',
95
-
expression: result.expression!,
96
-
hasLazyExpression,
97
};
98
};
···
4
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
+
import type { Chain } from '../../shared/chain';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
+
import type { ObjectResolverContext } from '../../types';
10
import { irSchemaToAst } from '../plugin';
11
12
+
function additionalPropertiesNode(
13
+
ctx: ObjectResolverContext,
14
+
): Chain | null | undefined {
15
+
const { plugin, schema } = ctx;
16
+
17
+
if (
18
+
!schema.additionalProperties ||
19
+
(schema.properties && Object.keys(schema.properties).length > 0)
20
+
)
21
+
return;
22
+
23
+
const additionalAst = irSchemaToAst({
24
+
plugin,
25
+
schema: schema.additionalProperties,
26
+
state: {
27
+
...ctx.utils.state,
28
+
path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']),
29
+
},
30
});
31
+
if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
32
+
return additionalAst.expression;
33
+
}
34
+
35
+
function baseNode(ctx: ObjectResolverContext): Chain {
36
+
const { nodes, symbols } = ctx;
37
+
const { z } = symbols;
38
+
39
+
const additional = nodes.additionalProperties(ctx);
40
+
const shape = nodes.shape(ctx);
41
42
if (additional) {
43
return $(z).attr(identifiers.record).call(additional);
···
46
return $(z).attr(identifiers.object).call(shape);
47
}
48
49
+
function objectResolver(ctx: ObjectResolverContext): Chain {
50
// TODO: parser - handle constants
51
+
return ctx.nodes.base(ctx);
52
+
}
53
54
+
function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> {
55
+
const { plugin, schema } = ctx;
56
const shape = $.object().pretty();
57
58
for (const name in schema.properties) {
59
const property = schema.properties[name]!;
60
61
+
const propertyAst = irSchemaToAst({
62
+
optional: !schema.required?.includes(name),
63
plugin,
64
schema: property,
65
state: {
66
+
...ctx.utils.state,
67
+
path: ref([...fromRef(ctx.utils.state.path), 'properties', name]),
68
},
69
});
70
+
if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
71
+
shape.prop(name, propertyAst.expression);
72
}
73
74
+
return shape;
75
+
}
76
77
+
export const objectToAst = ({
78
+
plugin,
79
+
schema,
80
+
state,
81
+
}: IrSchemaToAstOptions & {
82
+
schema: SchemaWithType<'object'>;
83
+
}): Omit<Ast, 'typeName'> & {
84
+
anyType?: string;
85
+
} => {
86
+
const ast: Partial<Omit<Ast, 'typeName'>> = {};
87
+
const z = plugin.external('zod.z');
88
+
const ctx: ObjectResolverContext = {
89
$,
90
+
chain: {
91
+
current: $(z),
92
+
},
93
+
nodes: {
94
+
additionalProperties: additionalPropertiesNode,
95
+
base: baseNode,
96
+
shape: shapeNode,
97
+
},
98
plugin,
99
schema,
100
+
symbols: {
101
+
z,
102
+
},
103
+
utils: {
104
+
ast,
105
+
state,
106
+
},
107
};
108
+
const resolver = plugin.config['~resolvers']?.object;
109
+
const node = resolver?.(ctx) ?? objectResolver(ctx);
110
+
ast.expression = node;
111
return {
112
+
...ast,
113
anyType: 'AnyZodObject',
114
+
} as Omit<Ast, 'typeName'> & {
115
+
anyType: string;
116
};
117
};
+103
-49
packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts
+103
-49
packages/openapi-ts/src/plugins/zod/v3/toAst/string.ts
···
2
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
import type { IrSchemaToAstOptions } from '../../shared/types';
6
-
import type { FormatResolverArgs } from '../../types';
7
8
-
const defaultFormatResolver = ({
9
-
chain,
10
-
plugin,
11
-
schema,
12
-
}: FormatResolverArgs): ReturnType<typeof $.call> => {
13
switch (schema.format) {
14
case 'date':
15
-
return chain.attr(identifiers.date).call();
16
case 'date-time': {
17
const obj = $.object()
18
.$if(plugin.config.dates.offset, (o) =>
···
21
.$if(plugin.config.dates.local, (o) =>
22
o.prop('local', $.literal(true)),
23
);
24
-
return chain
25
.attr(identifiers.datetime)
26
.call(obj.hasProps() ? obj : undefined);
27
}
28
case 'email':
29
-
return chain.attr(identifiers.email).call();
30
case 'ipv4':
31
case 'ipv6':
32
-
return chain.attr(identifiers.ip).call();
33
case 'time':
34
-
return chain.attr(identifiers.time).call();
35
case 'uri':
36
-
return chain.attr(identifiers.url).call();
37
case 'uuid':
38
-
return chain.attr(identifiers.uuid).call();
39
-
default:
40
-
return chain;
41
}
42
-
};
43
44
-
export const stringToAst = ({
45
-
plugin,
46
-
schema,
47
-
}: IrSchemaToAstOptions & {
48
-
schema: SchemaWithType<'string'>;
49
-
}): ReturnType<typeof $.call> => {
50
-
let chain: ReturnType<typeof $.call>;
51
52
-
const z = plugin.referenceSymbol({
53
-
category: 'external',
54
-
resource: 'zod.z',
55
-
});
56
57
-
if (typeof schema.const === 'string') {
58
-
chain = $(z).attr(identifiers.literal).call($.literal(schema.const));
59
-
return chain;
60
-
}
61
62
-
chain = $(z).attr(identifiers.string).call();
63
64
-
if (schema.format) {
65
-
const args: FormatResolverArgs = { $, chain, plugin, schema };
66
-
const resolver =
67
-
plugin.config['~resolvers']?.string?.formats?.[schema.format];
68
-
chain = resolver?.(args) ?? defaultFormatResolver(args);
69
}
70
71
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
72
-
chain = chain.attr(identifiers.length).call($.literal(schema.minLength));
73
} else {
74
-
if (schema.minLength !== undefined) {
75
-
chain = chain.attr(identifiers.min).call($.literal(schema.minLength));
76
-
}
77
78
-
if (schema.maxLength !== undefined) {
79
-
chain = chain.attr(identifiers.max).call($.literal(schema.maxLength));
80
-
}
81
}
82
83
-
if (schema.pattern) {
84
-
chain = chain.attr(identifiers.regex).call($.regexp(schema.pattern));
85
-
}
86
87
-
return chain;
88
};
···
2
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
+
import type { Chain } from '../../shared/chain';
6
import type { IrSchemaToAstOptions } from '../../shared/types';
7
+
import type { StringResolverContext } from '../../types';
8
9
+
function baseNode(ctx: StringResolverContext): Chain {
10
+
const { z } = ctx.symbols;
11
+
return $(z).attr(identifiers.string).call();
12
+
}
13
+
14
+
function constNode(ctx: StringResolverContext): Chain | undefined {
15
+
const { schema, symbols } = ctx;
16
+
const { z } = symbols;
17
+
if (typeof schema.const !== 'string') return;
18
+
return $(z).attr(identifiers.literal).call($.literal(schema.const));
19
+
}
20
+
21
+
function formatNode(ctx: StringResolverContext): Chain | undefined {
22
+
const { chain, plugin, schema } = ctx;
23
+
24
switch (schema.format) {
25
case 'date':
26
+
return chain.current.attr(identifiers.date).call();
27
case 'date-time': {
28
const obj = $.object()
29
.$if(plugin.config.dates.offset, (o) =>
···
32
.$if(plugin.config.dates.local, (o) =>
33
o.prop('local', $.literal(true)),
34
);
35
+
return chain.current
36
.attr(identifiers.datetime)
37
.call(obj.hasProps() ? obj : undefined);
38
}
39
case 'email':
40
+
return chain.current.attr(identifiers.email).call();
41
case 'ipv4':
42
case 'ipv6':
43
+
return chain.current.attr(identifiers.ip).call();
44
case 'time':
45
+
return chain.current.attr(identifiers.time).call();
46
case 'uri':
47
+
return chain.current.attr(identifiers.url).call();
48
case 'uuid':
49
+
return chain.current.attr(identifiers.uuid).call();
50
}
51
52
+
return;
53
+
}
54
55
+
function lengthNode(ctx: StringResolverContext): Chain | undefined {
56
+
const { chain, schema } = ctx;
57
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
58
+
return;
59
+
return chain.current
60
+
.attr(identifiers.length)
61
+
.call($.literal(schema.minLength));
62
+
}
63
64
+
function maxLengthNode(ctx: StringResolverContext): Chain | undefined {
65
+
const { chain, schema } = ctx;
66
+
if (schema.maxLength === undefined) return;
67
+
return chain.current.attr(identifiers.max).call($.literal(schema.maxLength));
68
+
}
69
70
+
function minLengthNode(ctx: StringResolverContext): Chain | undefined {
71
+
const { chain, schema } = ctx;
72
+
if (schema.minLength === undefined) return;
73
+
return chain.current.attr(identifiers.min).call($.literal(schema.minLength));
74
+
}
75
76
+
function patternNode(ctx: StringResolverContext): Chain | undefined {
77
+
const { chain, schema } = ctx;
78
+
if (!schema.pattern) return;
79
+
return chain.current.attr(identifiers.regex).call($.regexp(schema.pattern));
80
+
}
81
+
82
+
function stringResolver(ctx: StringResolverContext): Chain {
83
+
const constNode = ctx.nodes.const(ctx);
84
+
if (constNode) {
85
+
ctx.chain.current = constNode;
86
+
return ctx.chain.current;
87
}
88
89
+
const baseNode = ctx.nodes.base(ctx);
90
+
if (baseNode) ctx.chain.current = baseNode;
91
+
92
+
const formatNode = ctx.nodes.format(ctx);
93
+
if (formatNode) ctx.chain.current = formatNode;
94
+
95
+
const lengthNode = ctx.nodes.length(ctx);
96
+
if (lengthNode) {
97
+
ctx.chain.current = lengthNode;
98
} else {
99
+
const minLengthNode = ctx.nodes.minLength(ctx);
100
+
if (minLengthNode) ctx.chain.current = minLengthNode;
101
102
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
103
+
if (maxLengthNode) ctx.chain.current = maxLengthNode;
104
}
105
106
+
const patternNode = ctx.nodes.pattern(ctx);
107
+
if (patternNode) ctx.chain.current = patternNode;
108
109
+
return ctx.chain.current;
110
+
}
111
+
112
+
export const stringToNode = ({
113
+
plugin,
114
+
schema,
115
+
}: IrSchemaToAstOptions & {
116
+
schema: SchemaWithType<'string'>;
117
+
}): Chain => {
118
+
const z = plugin.external('zod.z');
119
+
const ctx: StringResolverContext = {
120
+
$,
121
+
chain: {
122
+
current: $(z),
123
+
},
124
+
nodes: {
125
+
base: baseNode,
126
+
const: constNode,
127
+
format: formatNode,
128
+
length: lengthNode,
129
+
maxLength: maxLengthNode,
130
+
minLength: minLengthNode,
131
+
pattern: patternNode,
132
+
},
133
+
plugin,
134
+
schema,
135
+
symbols: {
136
+
z,
137
+
},
138
+
};
139
+
const resolver = plugin.config['~resolvers']?.string;
140
+
const node = resolver?.(ctx) ?? stringResolver(ctx);
141
+
return node;
142
};
+29
-15
packages/openapi-ts/src/plugins/zod/v4/api.ts
+29
-15
packages/openapi-ts/src/plugins/zod/v4/api.ts
···
2
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
-
import type { ValidatorResolverArgs } from '../types';
6
7
-
const defaultValidatorResolver = ({
8
-
schema,
9
-
}: ValidatorResolverArgs): ReturnType<typeof $.return> =>
10
-
$(schema).attr(identifiers.parseAsync).call('data').await().return();
11
12
export const createRequestValidatorV4 = ({
13
operation,
···
22
});
23
if (!symbol) return;
24
25
-
const args: ValidatorResolverArgs = {
26
$,
27
-
chain: undefined,
28
operation,
29
plugin,
30
-
schema: symbol,
31
};
32
const validator = plugin.config['~resolvers']?.validator;
33
const resolver =
34
typeof validator === 'function' ? validator : validator?.request;
35
-
const candidates = [resolver, defaultValidatorResolver];
36
for (const candidate of candidates) {
37
-
const statements = candidate?.(args);
38
if (statements === null) return;
39
if (statements !== undefined) {
40
return $.func()
···
59
});
60
if (!symbol) return;
61
62
-
const args: ValidatorResolverArgs = {
63
$,
64
-
chain: undefined,
65
operation,
66
plugin,
67
-
schema: symbol,
68
};
69
const validator = plugin.config['~resolvers']?.validator;
70
const resolver =
71
typeof validator === 'function' ? validator : validator?.response;
72
-
const candidates = [resolver, defaultValidatorResolver];
73
for (const candidate of candidates) {
74
-
const statements = candidate?.(args);
75
if (statements === null) return;
76
if (statements !== undefined) {
77
return $.func()
···
2
3
import { identifiers } from '../constants';
4
import type { ValidatorArgs } from '../shared/types';
5
+
import type { ValidatorResolverContext } from '../types';
6
7
+
const validatorResolver = (
8
+
ctx: ValidatorResolverContext,
9
+
): ReturnType<typeof $.return> => {
10
+
const { schema } = ctx.symbols;
11
+
return $(schema).attr(identifiers.parseAsync).call('data').await().return();
12
+
};
13
14
export const createRequestValidatorV4 = ({
15
operation,
···
24
});
25
if (!symbol) return;
26
27
+
const z = plugin.external('zod.z');
28
+
const ctx: ValidatorResolverContext = {
29
$,
30
+
chain: {
31
+
current: $(z),
32
+
},
33
operation,
34
plugin,
35
+
symbols: {
36
+
schema: symbol,
37
+
z,
38
+
},
39
};
40
const validator = plugin.config['~resolvers']?.validator;
41
const resolver =
42
typeof validator === 'function' ? validator : validator?.request;
43
+
const candidates = [resolver, validatorResolver];
44
for (const candidate of candidates) {
45
+
const statements = candidate?.(ctx);
46
if (statements === null) return;
47
if (statements !== undefined) {
48
return $.func()
···
67
});
68
if (!symbol) return;
69
70
+
const z = plugin.external('zod.z');
71
+
const ctx: ValidatorResolverContext = {
72
$,
73
+
chain: {
74
+
current: $(z),
75
+
},
76
operation,
77
plugin,
78
+
symbols: {
79
+
schema: symbol,
80
+
z,
81
+
},
82
};
83
const validator = plugin.config['~resolvers']?.validator;
84
const resolver =
85
typeof validator === 'function' ? validator : validator?.response;
86
+
const candidates = [resolver, validatorResolver];
87
for (const candidate of candidates) {
88
+
const statements = candidate?.(ctx);
89
if (statements === null) return;
90
if (statements !== undefined) {
91
return $.func()
+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
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
-
import { numberToAst } from './number';
11
import { objectToAst } from './object';
12
-
import { stringToAst } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
39
});
40
case 'integer':
41
case 'number':
42
-
return numberToAst({
43
...args,
44
schema: schema as SchemaWithType<'integer' | 'number'>,
45
});
···
60
});
61
case 'string':
62
return shouldCoerceToBigInt(schema.format)
63
-
? numberToAst({
64
...args,
65
schema: { ...schema, type: 'number' },
66
})
67
-
: stringToAst({
68
...args,
69
schema: schema as SchemaWithType<'string'>,
70
});
···
7
import { enumToAst } from './enum';
8
import { neverToAst } from './never';
9
import { nullToAst } from './null';
10
+
import { numberToNode } from './number';
11
import { objectToAst } from './object';
12
+
import { stringToNode } from './string';
13
import { tupleToAst } from './tuple';
14
import { undefinedToAst } from './undefined';
15
import { unknownToAst } from './unknown';
···
39
});
40
case 'integer':
41
case 'number':
42
+
return numberToNode({
43
...args,
44
schema: schema as SchemaWithType<'integer' | 'number'>,
45
});
···
60
});
61
case 'string':
62
return shouldCoerceToBigInt(schema.format)
63
+
? numberToNode({
64
...args,
65
schema: { ...schema, type: 'number' },
66
})
67
+
: stringToNode({
68
...args,
69
schema: schema as SchemaWithType<'string'>,
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
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
11
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'>;
30
}
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
-
}
42
}
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
-
)
52
.attr(identifiers.max)
53
.call(
54
-
maybeBigInt(integerLimit.maxValue, schema.format),
55
-
$.object().prop('error', $.literal(integerLimit.maxError)),
56
);
57
}
58
59
if (schema.exclusiveMinimum !== undefined) {
60
-
result.expression = result.expression
61
.attr(identifiers.gt)
62
-
.call(maybeBigInt(schema.exclusiveMinimum, schema.format));
63
-
} else if (schema.minimum !== undefined) {
64
-
result.expression = result.expression
65
.attr(identifiers.gte)
66
-
.call(maybeBigInt(schema.minimum, schema.format));
67
}
68
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));
77
}
78
79
-
return result as Omit<Ast, 'typeName'>;
80
};
···
7
import { $ } from '~/ts-dsl';
8
9
import { identifiers } from '../../constants';
10
+
import type { Chain } from '../../shared/chain';
11
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
12
+
import type { NumberResolverContext } from '../../types';
13
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
+
}
26
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
+
}
35
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));
47
}
48
+
const limit = ctx.utils.getIntegerLimit(schema.format);
49
+
if (limit) {
50
+
return chain.current
51
.attr(identifiers.max)
52
.call(
53
+
ctx.utils.maybeBigInt(limit.maxValue, schema.format),
54
+
$.object().prop('error', $.literal(limit.maxError)),
55
);
56
}
57
+
return;
58
+
}
59
60
+
function minNode(ctx: NumberResolverContext): Chain | undefined {
61
+
const { chain, schema } = ctx;
62
if (schema.exclusiveMinimum !== undefined) {
63
+
return chain.current
64
.attr(identifiers.gt)
65
+
.call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format));
66
+
}
67
+
if (schema.minimum !== undefined) {
68
+
return chain.current
69
.attr(identifiers.gte)
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
+
);
80
}
81
+
return;
82
+
}
83
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;
89
}
90
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'>;
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
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
8
-
import type { ObjectBaseResolverArgs } from '../../types';
9
import { irSchemaToAst } from '../plugin';
10
11
-
function defaultObjectBaseResolver({
12
-
additional,
13
-
plugin,
14
-
shape,
15
-
}: ObjectBaseResolverArgs): ReturnType<typeof $.call> {
16
-
const z = plugin.referenceSymbol({
17
-
category: 'external',
18
-
resource: 'zod.z',
19
});
20
21
if (additional) {
22
return $(z)
···
27
return $(z).attr(identifiers.object).call(shape);
28
}
29
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
-
39
// TODO: parser - handle constants
40
41
const shape = $.object().pretty();
42
-
const required = schema.required ?? [];
43
44
for (const name in schema.properties) {
45
const property = schema.properties[name]!;
46
-
const isRequired = required.includes(name);
47
48
const propertyAst = irSchemaToAst({
49
-
optional: !isRequired,
50
plugin,
51
schema: property,
52
state: {
53
-
...state,
54
-
path: ref([...fromRef(state.path), 'properties', name]),
55
},
56
});
57
if (propertyAst.hasLazyExpression) {
58
-
result.hasLazyExpression = true;
59
-
}
60
-
61
-
if (propertyAst.hasLazyExpression) {
62
shape.getter(name, propertyAst.expression.return());
63
} else {
64
shape.prop(name, propertyAst.expression);
65
}
66
}
67
68
-
let additional: ReturnType<typeof $.call | typeof $.expr> | null | undefined;
69
-
if (
70
-
schema.additionalProperties &&
71
-
(!schema.properties || !Object.keys(schema.properties).length)
72
-
) {
73
-
const additionalAst = irSchemaToAst({
74
-
plugin,
75
-
schema: schema.additionalProperties,
76
-
state: {
77
-
...state,
78
-
path: ref([...fromRef(state.path), 'additionalProperties']),
79
-
},
80
-
});
81
-
if (additionalAst.hasLazyExpression) result.hasLazyExpression = true;
82
-
additional = additionalAst.expression;
83
-
}
84
85
-
const args: ObjectBaseResolverArgs = {
86
$,
87
-
additional,
88
-
chain: undefined,
89
plugin,
90
schema,
91
-
shape,
92
};
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'>;
106
};
···
4
import { $ } from '~/ts-dsl';
5
6
import { identifiers } from '../../constants';
7
+
import type { Chain } from '../../shared/chain';
8
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
9
+
import type { ObjectResolverContext } from '../../types';
10
import { irSchemaToAst } from '../plugin';
11
12
+
function additionalPropertiesNode(
13
+
ctx: ObjectResolverContext,
14
+
): Chain | null | undefined {
15
+
const { plugin, schema } = ctx;
16
+
17
+
if (
18
+
!schema.additionalProperties ||
19
+
(schema.properties && Object.keys(schema.properties).length > 0)
20
+
)
21
+
return;
22
+
23
+
const additionalAst = irSchemaToAst({
24
+
plugin,
25
+
schema: schema.additionalProperties,
26
+
state: {
27
+
...ctx.utils.state,
28
+
path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']),
29
+
},
30
});
31
+
if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true;
32
+
return additionalAst.expression;
33
+
}
34
+
35
+
function baseNode(ctx: ObjectResolverContext): Chain {
36
+
const { nodes, symbols } = ctx;
37
+
const { z } = symbols;
38
+
39
+
const additional = nodes.additionalProperties(ctx);
40
+
const shape = nodes.shape(ctx);
41
42
if (additional) {
43
return $(z)
···
48
return $(z).attr(identifiers.object).call(shape);
49
}
50
51
+
function objectResolver(ctx: ObjectResolverContext): Chain {
52
// TODO: parser - handle constants
53
+
return ctx.nodes.base(ctx);
54
+
}
55
56
+
function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> {
57
+
const { plugin, schema } = ctx;
58
const shape = $.object().pretty();
59
60
for (const name in schema.properties) {
61
const property = schema.properties[name]!;
62
63
const propertyAst = irSchemaToAst({
64
+
optional: !schema.required?.includes(name),
65
plugin,
66
schema: property,
67
state: {
68
+
...ctx.utils.state,
69
+
path: ref([...fromRef(ctx.utils.state.path), 'properties', name]),
70
},
71
});
72
if (propertyAst.hasLazyExpression) {
73
+
ctx.utils.ast.hasLazyExpression = true;
74
shape.getter(name, propertyAst.expression.return());
75
} else {
76
shape.prop(name, propertyAst.expression);
77
}
78
}
79
80
+
return shape;
81
+
}
82
83
+
export const objectToAst = ({
84
+
plugin,
85
+
schema,
86
+
state,
87
+
}: IrSchemaToAstOptions & {
88
+
schema: SchemaWithType<'object'>;
89
+
}): Omit<Ast, 'typeName'> => {
90
+
const ast: Partial<Omit<Ast, 'typeName'>> = {};
91
+
const z = plugin.external('zod.z');
92
+
const ctx: ObjectResolverContext = {
93
$,
94
+
chain: {
95
+
current: $(z),
96
+
},
97
+
nodes: {
98
+
additionalProperties: additionalPropertiesNode,
99
+
base: baseNode,
100
+
shape: shapeNode,
101
+
},
102
plugin,
103
schema,
104
+
symbols: {
105
+
z,
106
+
},
107
+
utils: {
108
+
ast,
109
+
state,
110
+
},
111
};
112
+
const resolver = plugin.config['~resolvers']?.object;
113
+
const node = resolver?.(ctx) ?? objectResolver(ctx);
114
+
ast.expression = node;
115
+
return ast as Omit<Ast, 'typeName'>;
116
};
+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
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
6
-
import type { FormatResolverArgs } from '../../types';
7
8
-
const defaultFormatResolver = ({
9
-
chain,
10
-
plugin,
11
-
schema,
12
-
}: FormatResolverArgs): ReturnType<typeof $.call> => {
13
-
const z = plugin.referenceSymbol({
14
-
category: 'external',
15
-
resource: 'zod.z',
16
-
});
17
18
switch (schema.format) {
19
case 'date':
···
43
return $(z).attr(identifiers.url).call();
44
case 'uuid':
45
return $(z).attr(identifiers.uuid).call();
46
-
default:
47
-
return chain;
48
}
49
-
};
50
51
-
export const stringToAst = ({
52
-
plugin,
53
-
schema,
54
-
}: IrSchemaToAstOptions & {
55
-
schema: SchemaWithType<'string'>;
56
-
}): Omit<Ast, 'typeName'> => {
57
-
const result: Partial<Omit<Ast, 'typeName'>> = {};
58
-
let chain: ReturnType<typeof $.call>;
59
60
-
const z = plugin.referenceSymbol({
61
-
category: 'external',
62
-
resource: 'zod.z',
63
-
});
64
65
-
if (typeof schema.const === 'string') {
66
-
chain = $(z).attr(identifiers.literal).call($.literal(schema.const));
67
-
result.expression = chain;
68
-
return result as Omit<Ast, 'typeName'>;
69
}
70
71
-
chain = $(z).attr(identifiers.string).call();
72
73
-
if (schema.format) {
74
-
const args: FormatResolverArgs = { $, chain, plugin, schema };
75
-
const resolver =
76
-
plugin.config['~resolvers']?.string?.formats?.[schema.format];
77
-
chain = resolver?.(args) ?? defaultFormatResolver(args);
78
-
}
79
80
-
if (schema.minLength === schema.maxLength && schema.minLength !== undefined) {
81
-
chain = chain.attr(identifiers.length).call($.literal(schema.minLength));
82
} else {
83
-
if (schema.minLength !== undefined) {
84
-
chain = chain.attr(identifiers.min).call($.literal(schema.minLength));
85
-
}
86
87
-
if (schema.maxLength !== undefined) {
88
-
chain = chain.attr(identifiers.max).call($.literal(schema.maxLength));
89
-
}
90
}
91
92
-
if (schema.pattern) {
93
-
chain = chain.attr(identifiers.regex).call($.regexp(schema.pattern));
94
-
}
95
96
-
result.expression = chain;
97
-
return result as Omit<Ast, 'typeName'>;
98
};
···
2
import { $ } from '~/ts-dsl';
3
4
import { identifiers } from '../../constants';
5
+
import type { Chain } from '../../shared/chain';
6
import type { Ast, IrSchemaToAstOptions } from '../../shared/types';
7
+
import type { StringResolverContext } from '../../types';
8
+
9
+
function baseNode(ctx: StringResolverContext): Chain {
10
+
const { z } = ctx.symbols;
11
+
return $(z).attr(identifiers.string).call();
12
+
}
13
+
14
+
function constNode(ctx: StringResolverContext): Chain | undefined {
15
+
const { schema, symbols } = ctx;
16
+
const { z } = symbols;
17
+
if (typeof schema.const !== 'string') return;
18
+
return $(z).attr(identifiers.literal).call($.literal(schema.const));
19
+
}
20
21
+
function formatNode(ctx: StringResolverContext): Chain | undefined {
22
+
const { plugin, schema, symbols } = ctx;
23
+
const { z } = symbols;
24
25
switch (schema.format) {
26
case 'date':
···
50
return $(z).attr(identifiers.url).call();
51
case 'uuid':
52
return $(z).attr(identifiers.uuid).call();
53
}
54
55
+
return;
56
+
}
57
+
58
+
function lengthNode(ctx: StringResolverContext): Chain | undefined {
59
+
const { chain, schema } = ctx;
60
+
if (schema.minLength === undefined || schema.minLength !== schema.maxLength)
61
+
return;
62
+
return chain.current
63
+
.attr(identifiers.length)
64
+
.call($.literal(schema.minLength));
65
+
}
66
+
67
+
function maxLengthNode(ctx: StringResolverContext): Chain | undefined {
68
+
const { chain, schema } = ctx;
69
+
if (schema.maxLength === undefined) return;
70
+
return chain.current.attr(identifiers.max).call($.literal(schema.maxLength));
71
+
}
72
+
73
+
function minLengthNode(ctx: StringResolverContext): Chain | undefined {
74
+
const { chain, schema } = ctx;
75
+
if (schema.minLength === undefined) return;
76
+
return chain.current.attr(identifiers.min).call($.literal(schema.minLength));
77
+
}
78
79
+
function patternNode(ctx: StringResolverContext): Chain | undefined {
80
+
const { chain, schema } = ctx;
81
+
if (!schema.pattern) return;
82
+
return chain.current.attr(identifiers.regex).call($.regexp(schema.pattern));
83
+
}
84
85
+
function stringResolver(ctx: StringResolverContext): Chain {
86
+
const constNode = ctx.nodes.const(ctx);
87
+
if (constNode) {
88
+
ctx.chain.current = constNode;
89
+
return ctx.chain.current;
90
}
91
92
+
const baseNode = ctx.nodes.base(ctx);
93
+
if (baseNode) ctx.chain.current = baseNode;
94
95
+
const formatNode = ctx.nodes.format(ctx);
96
+
if (formatNode) ctx.chain.current = formatNode;
97
98
+
const lengthNode = ctx.nodes.length(ctx);
99
+
if (lengthNode) {
100
+
ctx.chain.current = lengthNode;
101
} else {
102
+
const minLengthNode = ctx.nodes.minLength(ctx);
103
+
if (minLengthNode) ctx.chain.current = minLengthNode;
104
105
+
const maxLengthNode = ctx.nodes.maxLength(ctx);
106
+
if (maxLengthNode) ctx.chain.current = maxLengthNode;
107
}
108
109
+
const patternNode = ctx.nodes.pattern(ctx);
110
+
if (patternNode) ctx.chain.current = patternNode;
111
+
112
+
return ctx.chain.current;
113
+
}
114
115
+
export const stringToNode = ({
116
+
plugin,
117
+
schema,
118
+
}: IrSchemaToAstOptions & {
119
+
schema: SchemaWithType<'string'>;
120
+
}): Omit<Ast, 'typeName'> => {
121
+
const z = plugin.external('zod.z');
122
+
const ctx: StringResolverContext = {
123
+
$,
124
+
chain: {
125
+
current: $(z),
126
+
},
127
+
nodes: {
128
+
base: baseNode,
129
+
const: constNode,
130
+
format: formatNode,
131
+
length: lengthNode,
132
+
maxLength: maxLengthNode,
133
+
minLength: minLengthNode,
134
+
pattern: patternNode,
135
+
},
136
+
plugin,
137
+
schema,
138
+
symbols: {
139
+
z,
140
+
},
141
+
};
142
+
const resolver = plugin.config['~resolvers']?.string;
143
+
const node = resolver?.(ctx) ?? stringResolver(ctx);
144
+
return {
145
+
expression: node,
146
+
};
147
};