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