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

Merge pull request #3147 from hey-api/feat/plugin-resolvers-number-2

feat: add number base resolvers

authored by Lubos and committed by GitHub a6de8a79 6d2a873a

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