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

refactor: clean up valibot resolvers

Lubos 131085cb cfab9a09

Changed files
+557 -407
dev
packages
+28 -22
dev/openapi-ts.config.ts
··· 431 }, 432 }, 433 '~resolvers': { 434 - number: { 435 - // base({ $, pipes, v }) { 436 - // return pipes.push($(v).attr('test').call()); 437 - // }, 438 - formats: { 439 - // date: ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), 440 - // 'date-time': ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), 441 - }, 442 - }, 443 - object: { 444 - // base({ $, additional, pipes, shape }) { 445 - // if (additional === undefined) { 446 - // return pipes.push($('v').attr('looseObject').call(shape)); 447 - // } 448 - // return; 449 - // }, 450 - }, 451 - string: { 452 - formats: { 453 - // date: ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), 454 - // 'date-time': ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), 455 - }, 456 }, 457 // validator({ $, plugin, schema, v }) { 458 // const vShadow = plugin.symbol('v'); 459 // const test = plugin.symbol('test');
··· 431 }, 432 }, 433 '~resolvers': { 434 + number(ctx) { 435 + const { $, plugin } = ctx; 436 + const { v } = ctx.symbols; 437 + const big = plugin.symbolOnce('Big', { 438 + external: 'big.js', 439 + importKind: 'default', 440 + meta: { 441 + category: 'external', 442 + resource: 'big.js', 443 + }, 444 + }); 445 + return $(v).attr('instance').call(big); 446 }, 447 + // object(ctx) { 448 + // const { $ } = ctx; 449 + // const additional = ctx.nodes.additionalProperties(ctx); 450 + // const shape = ctx.nodes.shape(ctx); 451 + // if (additional === undefined) { 452 + // return $('v').attr('looseObject').call(shape); 453 + // } 454 + // return; 455 + // }, 456 + // string(ctx) { 457 + // const { $, schema } = ctx; 458 + // if (schema.format === 'date' || schema.format === 'date-time') { 459 + // ctx.nodes.format = () => $('v').attr('isoDateTime').call(); 460 + // } 461 + // return; 462 + // }, 463 // validator({ $, plugin, schema, v }) { 464 // const vShadow = plugin.symbol('v'); 465 // const test = plugin.symbol('test');
+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 + };
+21
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 existing = symbol?.meta ? this.querySymbol(symbol.meta) : undefined; 398 + if (existing) return existing; 399 + return this.symbol(name, symbol); 400 } 401 402 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 + push: PushPipes; 37 + toNode: PipesToNode; 38 + } 39 + 40 + /** 41 + * Functions for working with pipes. 42 + */ 43 + export const pipes: PipesUtils = { 44 + /** 45 + * Push pipes into target array. 46 + */ 47 + push: pushPipes, 48 + /** 49 + * Convert pipes to a single node. 50 + */ 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
+92 -78
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 & ··· 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 - v: Symbol; 338 }; 339 340 - export type FormatResolverArgs = SharedResolverArgs & { 341 - schema: IR.SchemaObject; 342 }; 343 344 - export type ObjectBaseResolverArgs = SharedResolverArgs & { 345 - /** Null = never */ 346 - additional?: ReturnType<typeof $.call | typeof $.expr> | null; 347 - schema: IR.SchemaObject; 348 - shape: ReturnType<typeof $.object>; 349 }; 350 351 - type ResolverResult = boolean | number; 352 353 export type ValidatorResolverArgs = SharedResolverArgs & { 354 operation: IR.Operation; ··· 361 362 type Resolvers = Plugin.Resolvers<{ 363 /** 364 - * Resolvers for number schemas. 365 * 366 - * Allows customization of how number types are rendered, including 367 - * per-format handling. 368 */ 369 - number?: { 370 - /** 371 - * Controls the base segment for number schemas. 372 - * 373 - * Returning `undefined` will execute the default resolver logic. 374 - */ 375 - base?: (args: FormatResolverArgs) => ResolverResult | undefined; 376 - /** 377 - * Resolvers for number formats (e.g., `float`, `double`, `int32`). 378 - * 379 - * Each key represents a specific format name with a custom 380 - * resolver function that controls how that format is rendered. 381 - * 382 - * Example path: `~resolvers.number.formats.float` 383 - * 384 - * Returning `undefined` from a resolver will apply the default 385 - * generation behavior for that format. 386 - */ 387 - formats?: Record< 388 - string, 389 - (args: FormatResolverArgs) => ResolverResult | undefined 390 - >; 391 - }; 392 /** 393 - * Resolvers for object schemas. 394 * 395 * Allows customization of how object types are rendered. 396 * 397 - * Example path: `~resolvers.object.base` 398 - * 399 - * Returning `undefined` from a resolver will apply the default 400 - * generation behavior for the object schema. 401 */ 402 - object?: { 403 - /** 404 - * Controls how object schemas are constructed. 405 - * 406 - * Called with the fully assembled shape (properties) and any additional 407 - * property schema, allowing the resolver to choose the correct Valibot 408 - * base constructor and modify the schema chain if needed. 409 - * 410 - * Returning `undefined` will execute the default resolver logic. 411 - */ 412 - base?: (args: ObjectBaseResolverArgs) => ResolverResult | undefined; 413 - }; 414 /** 415 - * Resolvers for string schemas. 416 * 417 - * Allows customization of how string types are rendered, including 418 - * per-format handling. 419 */ 420 - string?: { 421 - /** 422 - * Resolvers for string formats (e.g., `uuid`, `email`, `date-time`). 423 - * 424 - * Each key represents a specific format name with a custom 425 - * resolver function that controls how that format is rendered. 426 - * 427 - * Example path: `~resolvers.string.formats.uuid` 428 - * 429 - * Returning `undefined` from a resolver will apply the default 430 - * generation behavior for that format. 431 - */ 432 - formats?: Record< 433 - string, 434 - (args: FormatResolverArgs) => ResolverResult | undefined 435 - >; 436 - }; 437 /** 438 * Resolvers for request and response validators. 439 *
··· 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 { Pipe, PipeResult, PipesUtils } from './shared/pipes'; 17 + import type { Ast, PluginState } from './shared/types'; 18 19 export type UserConfig = Plugin.Name<'valibot'> & 20 Plugin.Hooks & ··· 330 331 type SharedResolverArgs = DollarTsDsl & { 332 /** 333 + * Functions for working with pipes. 334 + */ 335 + pipes: PipesUtils; 336 + plugin: ValibotPlugin['Instance']; 337 + /** 338 * The current builder state being processed by this resolver. 339 * 340 * In Valibot, this represents the current list of call expressions ("pipes") 341 * being assembled to form a schema definition. 342 * 343 * Each pipe can be extended, modified, or replaced to customize how the 344 + * resulting schema is constructed. 345 */ 346 + result: Pipes; 347 + /** 348 + * Provides access to commonly used symbols within the Valibot plugin. 349 + */ 350 + symbols: { 351 + v: Symbol; 352 + }; 353 }; 354 355 + export type NumberResolverContext = SharedResolverArgs & { 356 + /** 357 + * Nodes used to build different parts of the number schema. 358 + */ 359 + nodes: { 360 + base: (ctx: NumberResolverContext) => PipeResult; 361 + const: (ctx: NumberResolverContext) => PipeResult | undefined; 362 + max: (ctx: NumberResolverContext) => PipeResult | undefined; 363 + min: (ctx: NumberResolverContext) => PipeResult | undefined; 364 + }; 365 + schema: SchemaWithType<'integer' | 'number'>; 366 + /** 367 + * Utility functions for number schema processing. 368 + */ 369 + utils: { 370 + getIntegerLimit: GetIntegerLimit; 371 + maybeBigInt: MaybeBigInt; 372 + shouldCoerceToBigInt: ShouldCoerceToBigInt; 373 + }; 374 }; 375 376 + export type ObjectResolverContext = SharedResolverArgs & { 377 + /** 378 + * Nodes used to build different parts of the object schema. 379 + */ 380 + nodes: { 381 + /** 382 + * If `additionalProperties` is `false` or `{ type: 'never' }`, returns `null` 383 + * to indicate no additional properties are allowed. 384 + */ 385 + additionalProperties: ( 386 + ctx: ObjectResolverContext, 387 + ) => Pipe | null | undefined; 388 + base: (ctx: ObjectResolverContext) => PipeResult; 389 + shape: (ctx: ObjectResolverContext) => ReturnType<typeof $.object>; 390 + }; 391 + schema: SchemaWithType<'object'>; 392 + /** 393 + * Utility functions for object schema processing. 394 + */ 395 + utils: { 396 + ast: Partial<Omit<Ast, 'typeName'>>; 397 + state: Refs<PluginState>; 398 + }; 399 }; 400 401 + export type StringResolverContext = SharedResolverArgs & { 402 + /** 403 + * Nodes used to build different parts of the string schema. 404 + */ 405 + nodes: { 406 + base: (ctx: StringResolverContext) => PipeResult; 407 + const: (ctx: StringResolverContext) => PipeResult | undefined; 408 + format: (ctx: StringResolverContext) => PipeResult | undefined; 409 + length: (ctx: StringResolverContext) => PipeResult | undefined; 410 + maxLength: (ctx: StringResolverContext) => PipeResult | undefined; 411 + minLength: (ctx: StringResolverContext) => PipeResult | undefined; 412 + pattern: (ctx: StringResolverContext) => PipeResult | undefined; 413 + }; 414 + schema: SchemaWithType<'string'>; 415 + }; 416 417 export type ValidatorResolverArgs = SharedResolverArgs & { 418 operation: IR.Operation; ··· 425 426 type Resolvers = Plugin.Resolvers<{ 427 /** 428 + * Resolver for number schemas. 429 + * 430 + * Allows customization of how number types are rendered. 431 * 432 + * Returning `undefined` will execute the default resolver logic. 433 */ 434 + number?: (args: NumberResolverContext) => PipeResult | undefined; 435 /** 436 + * Resolver for object schemas. 437 * 438 * Allows customization of how object types are rendered. 439 * 440 + * Returning `undefined` will execute the default resolver logic. 441 */ 442 + object?: (ctx: ObjectResolverContext) => PipeResult | undefined; 443 /** 444 + * Resolver for string schemas. 445 + * 446 + * Allows customization of how string types are rendered. 447 * 448 + * Returning `undefined` will execute the default resolver logic. 449 */ 450 + string?: (ctx: StringResolverContext) => PipeResult | undefined; 451 /** 452 * Resolvers for request and response validators. 453 *
+24 -19
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; ··· 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;
··· 1 import { $ } from '~/ts-dsl'; 2 3 + import { pipes } from '../shared/pipes'; 4 import type { ValidatorArgs } from '../shared/types'; 5 import type { ValidatorResolverArgs } from '../types'; 6 import { identifiers } from './constants'; 7 8 + const validatorResolver = ( 9 + ctx: ValidatorResolverArgs, 10 + ): ReturnType<typeof $.return> => { 11 + const { schema } = ctx; 12 + const { v } = ctx.symbols; 13 + return $(v) 14 + .attr(identifiers.async.parseAsync) 15 + .call(schema, 'data') 16 + .await() 17 + .return(); 18 + }; 19 20 export const createRequestValidatorV1 = ({ 21 operation, ··· 30 }); 31 if (!symbol) return; 32 33 const args: ValidatorResolverArgs = { 34 $, 35 operation, 36 + pipes, 37 plugin, 38 + result: [], 39 schema: symbol, 40 + symbols: { 41 + v: plugin.external('valibot.v'), 42 + }, 43 }; 44 const validator = plugin.config['~resolvers']?.validator; 45 const resolver = 46 typeof validator === 'function' ? validator : validator?.request; 47 + const candidates = [resolver, validatorResolver]; 48 for (const candidate of candidates) { 49 const statements = candidate?.(args); 50 if (statements === null) return; ··· 71 }); 72 if (!symbol) return; 73 74 const args: ValidatorResolverArgs = { 75 $, 76 operation, 77 + pipes, 78 plugin, 79 + result: [], 80 schema: symbol, 81 + symbols: { 82 + v: plugin.external('valibot.v'), 83 + }, 84 }; 85 const validator = plugin.config['~resolvers']?.validator; 86 const resolver = 87 typeof validator === 'function' ? validator : validator?.response; 88 + const candidates = [resolver, validatorResolver]; 89 for (const candidate of candidates) { 90 const statements = candidate?.(args); 91 if (statements === null) return;
+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'>,
+111 -94
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 type { FormatResolverArgs } from '../../types'; 12 import { identifiers } from '../constants'; 13 14 - const defaultBaseResolver = ({ 15 - pipes, 16 - schema, 17 - v, 18 - }: FormatResolverArgs): boolean | number => { 19 - if (shouldCoerceToBigInt(schema.format)) { 20 - pipes.push( 21 $(v) 22 .attr(identifiers.schemas.union) 23 .call( ··· 30 $(v) 31 .attr(identifiers.actions.transform) 32 .call($.func().param('x').do($('BigInt').call('x').return())), 33 - ); 34 - } else { 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 } 40 41 - return true; 42 - }; 43 44 - export const numberToAst = ({ 45 - plugin, 46 - schema, 47 - }: IrSchemaToAstOptions & { 48 - schema: SchemaWithType<'integer' | 'number'>; 49 - }) => { 50 - const v = plugin.referenceSymbol({ 51 - category: 'external', 52 - resource: 'valibot.v', 53 - }); 54 55 - if (schema.const !== undefined) { 56 return $(v) 57 - .attr(identifiers.schemas.literal) 58 - .call(maybeBigInt(schema.const, schema.format)); 59 } 60 61 - const pipes: Array<ReturnType<typeof $.call>> = []; 62 63 - const args: FormatResolverArgs = { $, pipes, plugin, schema, v }; 64 - const resolver = plugin.config['~resolvers']?.number?.base; 65 - if (!resolver?.(args)) defaultBaseResolver(args); 66 67 - let hasLowerBound = false; 68 - let hasUpperBound = false; 69 - 70 - if (schema.exclusiveMinimum !== undefined) { 71 - pipes.push( 72 - $(v) 73 - .attr(identifiers.actions.gtValue) 74 - .call(maybeBigInt(schema.exclusiveMinimum, schema.format)), 75 - ); 76 - hasLowerBound = true; 77 - } else if (schema.minimum !== undefined) { 78 - pipes.push( 79 - $(v) 80 - .attr(identifiers.actions.minValue) 81 - .call(maybeBigInt(schema.minimum, schema.format)), 82 - ); 83 - hasLowerBound = true; 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 - hasUpperBound = true; 93 - } else if (schema.maximum !== undefined) { 94 - pipes.push( 95 - $(v) 96 - .attr(identifiers.actions.maxValue) 97 - .call(maybeBigInt(schema.maximum, schema.format)), 98 - ); 99 - hasUpperBound = true; 100 - } 101 - 102 - const integerLimit = getIntegerLimit(schema.format); 103 - if (integerLimit) { 104 - if (!hasLowerBound) { 105 - pipes.push( 106 - $(v) 107 - .attr(identifiers.actions.minValue) 108 - .call( 109 - maybeBigInt(integerLimit.minValue, schema.format), 110 - $.literal(integerLimit.minError), 111 - ), 112 - ); 113 - hasLowerBound = true; 114 - } 115 116 - if (!hasUpperBound) { 117 - pipes.push( 118 - $(v) 119 - .attr(identifiers.actions.maxValue) 120 - .call( 121 - maybeBigInt(integerLimit.maxValue, schema.format), 122 - $.literal(integerLimit.maxError), 123 - ), 124 - ); 125 - hasUpperBound = true; 126 - } 127 - } 128 129 - return pipesToAst(pipes, plugin); 130 };
··· 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 } = ctx; 17 + const { v } = ctx.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 } = ctx; 44 + const { v } = ctx.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 } = ctx; 53 + const { v } = ctx.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 } = ctx; 78 + const { v } = ctx.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.result, constNode); 104 105 + const baseNode = ctx.nodes.base(ctx); 106 + if (baseNode) ctx.pipes.push(ctx.result, baseNode); 107 108 + const minNode = ctx.nodes.min(ctx); 109 + if (minNode) ctx.pipes.push(ctx.result, minNode); 110 111 + const maxNode = ctx.nodes.max(ctx); 112 + if (maxNode) ctx.pipes.push(ctx.result, maxNode); 113 114 + return ctx.result; 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 + plugin, 133 + result: [], 134 + schema, 135 + symbols: { 136 + v: plugin.external('valibot.v'), 137 + }, 138 + utils: { 139 + getIntegerLimit, 140 + maybeBigInt, 141 + shouldCoerceToBigInt, 142 + }, 143 + }; 144 + const resolver = plugin.config['~resolvers']?.number; 145 + const node = resolver?.(ctx) ?? numberResolver(ctx); 146 + return ctx.pipes.toNode(node, plugin); 147 };
+74 -71
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 defaultBaseResolver({ 13 - additional, 14 - pipes, 15 - shape, 16 - v, 17 - }: ObjectBaseResolverArgs): number { 18 - // Handle `additionalProperties: { type: 'never' }` → v.strictObject() 19 if (additional === null) { 20 - return pipes.push($(v).attr(identifiers.schemas.strictObject).call(shape)); 21 } 22 23 - // Handle additionalProperties as schema → v.record() or v.objectWithRest() 24 if (additional) { 25 if (shape.isEmpty) { 26 - return pipes.push( 27 - $(v) 28 - .attr(identifiers.schemas.record) 29 - .call($(v).attr(identifiers.schemas.string).call(), additional), 30 - ); 31 } 32 33 - // If there are named properties, use v.objectWithRest() to validate both 34 - return pipes.push( 35 - $(v).attr(identifiers.schemas.objectWithRest).call(shape, additional), 36 - ); 37 } 38 39 - // Default case → v.object() 40 - return pipes.push($(v).attr(identifiers.schemas.object).call(shape)); 41 } 42 43 - export const objectToAst = ({ 44 - plugin, 45 - schema, 46 - state, 47 - }: IrSchemaToAstOptions & { 48 - schema: SchemaWithType<'object'>; 49 - }): Omit<Ast, 'typeName'> => { 50 - const v = plugin.referenceSymbol({ 51 - category: 'external', 52 - resource: 'valibot.v', 53 - }); 54 - 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 - v, 107 }; 108 - const resolver = plugin.config['~resolvers']?.object?.base; 109 - if (!resolver?.(args)) defaultBaseResolver(args); 110 - 111 - result.pipes = [pipesToAst(pipes, plugin)]; 112 - return result as Omit<Ast, 'typeName'>; 113 };
··· 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 { v } = ctx.symbols; 35 + 36 + const additional = ctx.nodes.additionalProperties(ctx); 37 + const shape = ctx.nodes.shape(ctx); 38 + 39 if (additional === null) { 40 + return $(v).attr(identifiers.schemas.strictObject).call(shape); 41 } 42 43 if (additional) { 44 if (shape.isEmpty) { 45 + return $(v) 46 + .attr(identifiers.schemas.record) 47 + .call($(v).attr(identifiers.schemas.string).call(), additional); 48 } 49 50 + return $(v) 51 + .attr(identifiers.schemas.objectWithRest) 52 + .call(shape, additional); 53 } 54 55 + return $(v).attr(identifiers.schemas.object).call(shape); 56 } 57 58 + function objectResolver(ctx: ObjectResolverContext): PipeResult { 59 // TODO: parser - handle constants 60 + return ctx.nodes.base(ctx); 61 + } 62 63 + function shapeNode(ctx: ObjectResolverContext): ReturnType<typeof $.object> { 64 + const { plugin, schema } = ctx; 65 const shape = $.object().pretty(); 66 67 for (const name in schema.properties) { 68 const property = schema.properties[name]!; 69 70 const propertyAst = irSchemaToAst({ 71 + optional: !schema.required?.includes(name), 72 plugin, 73 schema: property, 74 state: { 75 + ...ctx.utils.state, 76 + path: ref([...fromRef(ctx.utils.state.path), 'properties', name]), 77 }, 78 }); 79 + if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; 80 + shape.prop(name, pipes.toNode(propertyAst.pipes, plugin)); 81 } 82 83 + return shape; 84 + } 85 86 + export const objectToAst = ({ 87 + plugin, 88 + schema, 89 + state, 90 + }: IrSchemaToAstOptions & { 91 + schema: SchemaWithType<'object'>; 92 + }): Omit<Ast, 'typeName'> => { 93 + const ctx: ObjectResolverContext = { 94 $, 95 + nodes: { 96 + additionalProperties: additionalPropertiesNode, 97 + base: baseNode, 98 + shape: shapeNode, 99 + }, 100 pipes, 101 plugin, 102 + result: [], 103 schema, 104 + symbols: { 105 + v: plugin.external('valibot.v'), 106 + }, 107 + utils: { 108 + ast: {}, 109 + state, 110 + }, 111 }; 112 + const resolver = plugin.config['~resolvers']?.object; 113 + const node = resolver?.(ctx) ?? objectResolver(ctx); 114 + ctx.utils.ast.pipes = [ctx.pipes.toNode(node, plugin)]; 115 + return ctx.utils.ast as Omit<Ast, 'typeName'>; 116 };
+108 -60
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 - schema, 12 - v, 13 - }: FormatResolverArgs): boolean | number => { 14 switch (schema.format) { 15 case 'date': 16 - return pipes.push($(v).attr(identifiers.actions.isoDate).call()); 17 case 'date-time': 18 - return pipes.push($(v).attr(identifiers.actions.isoTimestamp).call()); 19 case 'email': 20 - return pipes.push($(v).attr(identifiers.actions.email).call()); 21 case 'ipv4': 22 case 'ipv6': 23 - return pipes.push($(v).attr(identifiers.actions.ip).call()); 24 case 'time': 25 - return pipes.push($(v).attr(identifiers.actions.isoTimeSecond).call()); 26 case 'uri': 27 - return pipes.push($(v).attr(identifiers.actions.url).call()); 28 case 'uuid': 29 - return pipes.push($(v).attr(identifiers.actions.uuid).call()); 30 } 31 32 - return true; 33 - }; 34 35 - export const stringToAst = ({ 36 - plugin, 37 - schema, 38 - }: IrSchemaToAstOptions & { 39 - schema: SchemaWithType<'string'>; 40 - }): ReturnType<typeof $.call | typeof $.expr> => { 41 - const v = plugin.referenceSymbol({ 42 - category: 'external', 43 - resource: 'valibot.v', 44 - }); 45 46 - if (typeof schema.const === 'string') { 47 - return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)); 48 - } 49 50 - const pipes = [$(v).attr(identifiers.schemas.string).call()]; 51 52 - if (schema.format) { 53 - const args: FormatResolverArgs = { $, pipes, plugin, schema, v }; 54 - const resolver = 55 - plugin.config['~resolvers']?.string?.formats?.[schema.format]; 56 - if (!resolver?.(args)) defaultFormatResolver(args); 57 - } 58 59 - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { 60 - pipes.push( 61 - $(v).attr(identifiers.actions.length).call($.literal(schema.minLength)), 62 - ); 63 } else { 64 - if (schema.minLength !== undefined) { 65 - pipes.push( 66 - $(v) 67 - .attr(identifiers.actions.minLength) 68 - .call($.literal(schema.minLength)), 69 - ); 70 - } 71 72 - if (schema.maxLength !== undefined) { 73 - pipes.push( 74 - $(v) 75 - .attr(identifiers.actions.maxLength) 76 - .call($.literal(schema.maxLength)), 77 - ); 78 - } 79 } 80 81 - if (schema.pattern) { 82 - pipes.push( 83 - $(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)), 84 - ); 85 - } 86 87 - return pipesToAst(pipes, plugin); 88 };
··· 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 } = ctx; 17 + const { v } = ctx.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 } = ctx; 24 + const { v } = ctx.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 } = ctx; 48 + const { v } = ctx.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 } = ctx; 58 + const { v } = ctx.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 } = ctx; 67 + const { v } = ctx.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 } = ctx; 76 + const { v } = ctx.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.result, constNode); 84 + 85 + const baseNode = ctx.nodes.base(ctx); 86 + if (baseNode) ctx.pipes.push(ctx.result, baseNode); 87 88 + const formatNode = ctx.nodes.format(ctx); 89 + if (formatNode) ctx.pipes.push(ctx.result, formatNode); 90 + 91 + const lengthNode = ctx.nodes.length(ctx); 92 + if (lengthNode) { 93 + ctx.pipes.push(ctx.result, lengthNode); 94 } else { 95 + const minLengthNode = ctx.nodes.minLength(ctx); 96 + if (minLengthNode) ctx.pipes.push(ctx.result, minLengthNode); 97 98 + const maxLengthNode = ctx.nodes.maxLength(ctx); 99 + if (maxLengthNode) ctx.pipes.push(ctx.result, maxLengthNode); 100 } 101 102 + const patternNode = ctx.nodes.pattern(ctx); 103 + if (patternNode) ctx.pipes.push(ctx.result, patternNode); 104 + 105 + return ctx.result; 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 + plugin, 127 + result: [], 128 + schema, 129 + symbols: { 130 + v: plugin.external('valibot.v'), 131 + }, 132 + }; 133 + const resolver = plugin.config['~resolvers']?.string; 134 + const node = resolver?.(ctx) ?? stringResolver(ctx); 135 + return ctx.pipes.toNode(node, plugin); 136 };
+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)
+3 -3
packages/openapi-ts/src/plugins/zod/mini/api.ts
··· 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(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 - const candidates = [resolver, defaultValidatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 - const candidates = [resolver, defaultValidatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;
··· 4 import type { ValidatorArgs } from '../shared/types'; 5 import type { ValidatorResolverArgs } from '../types'; 6 7 + const validatorResolver = ({ 8 schema, 9 }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 $(schema).attr(identifiers.parseAsync).call('data').await().return(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 + const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 + const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;
+3 -3
packages/openapi-ts/src/plugins/zod/v3/api.ts
··· 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(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 - const candidates = [resolver, defaultValidatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 - const candidates = [resolver, defaultValidatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;
··· 4 import type { ValidatorArgs } from '../shared/types'; 5 import type { ValidatorResolverArgs } from '../types'; 6 7 + const validatorResolver = ({ 8 schema, 9 }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 $(schema).attr(identifiers.parseAsync).call('data').await().return(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 + const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 + const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;
+3 -3
packages/openapi-ts/src/plugins/zod/v4/api.ts
··· 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(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 - const candidates = [resolver, defaultValidatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 - const candidates = [resolver, defaultValidatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;
··· 4 import type { ValidatorArgs } from '../shared/types'; 5 import type { ValidatorResolverArgs } from '../types'; 6 7 + const validatorResolver = ({ 8 schema, 9 }: ValidatorResolverArgs): ReturnType<typeof $.return> => 10 $(schema).attr(identifiers.parseAsync).call('data').await().return(); ··· 37 const validator = plugin.config['~resolvers']?.validator; 38 const resolver = 39 typeof validator === 'function' ? validator : validator?.request; 40 + const candidates = [resolver, validatorResolver]; 41 for (const candidate of candidates) { 42 const statements = candidate?.(args); 43 if (statements === null) return; ··· 79 const validator = plugin.config['~resolvers']?.validator; 80 const resolver = 81 typeof validator === 'function' ? validator : validator?.response; 82 + const candidates = [resolver, validatorResolver]; 83 for (const candidate of candidates) { 84 const statements = candidate?.(args); 85 if (statements === null) return;