diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index 8409fd1ea..ef8d5e878 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -431,29 +431,35 @@ export default defineConfig(() => { }, }, '~resolvers': { - number: { - // base({ $, pipes, v }) { - // return pipes.push($(v).attr('test').call()); - // }, - formats: { - // date: ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), - // 'date-time': ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), - }, - }, - object: { - // base({ $, additional, pipes, shape }) { - // if (additional === undefined) { - // return pipes.push($('v').attr('looseObject').call(shape)); - // } - // return; - // }, - }, - string: { - formats: { - // date: ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), - // 'date-time': ({ $, pipes }) => pipes.push($('v').attr('isoDateTime').call()), - }, + number(ctx) { + const { $, plugin } = ctx; + const { v } = ctx.symbols; + const big = plugin.symbolOnce('Big', { + external: 'big.js', + importKind: 'default', + meta: { + category: 'external', + resource: 'big.js', + }, + }); + return $(v).attr('instance').call(big); }, + // object(ctx) { + // const { $ } = ctx; + // const additional = ctx.nodes.additionalProperties(ctx); + // const shape = ctx.nodes.shape(ctx); + // if (additional === undefined) { + // return $('v').attr('looseObject').call(shape); + // } + // return; + // }, + // string(ctx) { + // const { $, schema } = ctx; + // if (schema.format === 'date' || schema.format === 'date-time') { + // ctx.nodes.format = () => $('v').attr('isoDateTime').call(); + // } + // return; + // }, // validator({ $, plugin, schema, v }) { // const vShadow = plugin.symbol('v'); // const test = plugin.symbol('test'); diff --git a/packages/openapi-ts/src/plugins/shared/utils/coerce.ts b/packages/openapi-ts/src/plugins/shared/utils/coerce.ts index 71bd74498..c506b04db 100644 --- a/packages/openapi-ts/src/plugins/shared/utils/coerce.ts +++ b/packages/openapi-ts/src/plugins/shared/utils/coerce.ts @@ -1,12 +1,15 @@ import { $ } from '~/ts-dsl'; -export const shouldCoerceToBigInt = (format: string | undefined): boolean => - format === 'int64' || format === 'uint64'; - -export const maybeBigInt = ( +export type MaybeBigInt = ( value: unknown, format: string | undefined, -): ReturnType => { +) => ReturnType; +export type ShouldCoerceToBigInt = (format: string | undefined) => boolean; + +export const shouldCoerceToBigInt: ShouldCoerceToBigInt = (format) => + format === 'int64' || format === 'uint64'; + +export const maybeBigInt: MaybeBigInt = (value, format) => { if (!shouldCoerceToBigInt(format)) { return $.fromValue(value); } diff --git a/packages/openapi-ts/src/plugins/shared/utils/formats.ts b/packages/openapi-ts/src/plugins/shared/utils/formats.ts index b29ec10d2..9515b7c22 100644 --- a/packages/openapi-ts/src/plugins/shared/utils/formats.ts +++ b/packages/openapi-ts/src/plugins/shared/utils/formats.ts @@ -7,6 +7,10 @@ interface IntegerLimit { minValue: Range; } +export type GetIntegerLimit = ( + format: string | undefined, +) => IntegerLimit | undefined; + const rangeErrors = (format: string, range: [Range, Range]) => ({ maxError: `Invalid value: Expected ${format} to be <= ${range[1]}`, minError: `Invalid value: Expected ${format} to be >= ${range[0]}`, @@ -23,12 +27,10 @@ const integerRange: Record = { uint8: [0, 255], }; -export function getIntegerLimit( - format: string | undefined, -): IntegerLimit | undefined { +export const getIntegerLimit: GetIntegerLimit = (format) => { if (!format) return; const range = integerRange[format]; if (!range) return; const errors = rangeErrors(format, range); return { maxValue: range[1], minValue: range[0], ...errors }; -} +}; diff --git a/packages/openapi-ts/src/plugins/shared/utils/instance.ts b/packages/openapi-ts/src/plugins/shared/utils/instance.ts index f85b09d21..41ef53165 100644 --- a/packages/openapi-ts/src/plugins/shared/utils/instance.ts +++ b/packages/openapi-ts/src/plugins/shared/utils/instance.ts @@ -106,6 +106,17 @@ export class PluginInstance { this.package = props.context.package; } + external( + resource: Required['resource'], + meta?: Omit, + ): Symbol { + return this.gen.symbols.reference({ + ...meta, + category: 'external', + resource, + }); + } + /** * Iterates over various input elements as specified by the event types, in * a specific order: servers, schemas, parameters, request bodies, then @@ -378,6 +389,16 @@ export class PluginInstance { return symbolOut; } + /** + * Registers a symbol only if it does not already exist based on the provided + * metadata. This prevents duplicate symbols from being created in the project. + */ + symbolOnce(name: SymbolIn['name'], symbol?: Omit): Symbol { + const existing = symbol?.meta ? this.querySymbol(symbol.meta) : undefined; + if (existing) return existing; + return this.symbol(name, symbol); + } + private buildEventHooks(): EventHooks { const result: EventHooks = { 'node:set:after': [], diff --git a/packages/openapi-ts/src/plugins/valibot/shared/export.ts b/packages/openapi-ts/src/plugins/valibot/shared/export.ts index 21200813c..f09ede243 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/export.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/export.ts @@ -5,7 +5,7 @@ import { createSchemaComment } from '~/plugins/shared/utils/schema'; import { $ } from '~/ts-dsl'; import { identifiers } from '../v1/constants'; -import { pipesToAst } from './pipesToAst'; +import { pipesToNode } from './pipes'; import type { Ast, IrSchemaToAstOptions } from './types'; export const exportAst = ({ @@ -32,6 +32,6 @@ export const exportAst = ({ .$if(state.hasLazyExpression['~ref'], (c) => c.type($.type(v).attr(ast.typeName || identifiers.types.GenericSchema)), ) - .assign(pipesToAst(ast.pipes, plugin)); + .assign(pipesToNode(ast.pipes, plugin)); plugin.node(statement); }; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/pipes.ts b/packages/openapi-ts/src/plugins/valibot/shared/pipes.ts new file mode 100644 index 000000000..57e07404f --- /dev/null +++ b/packages/openapi-ts/src/plugins/valibot/shared/pipes.ts @@ -0,0 +1,52 @@ +import { $ } from '~/ts-dsl'; + +import type { ValibotPlugin } from '../types'; +import { identifiers } from '../v1/constants'; + +export type Pipe = ReturnType; +export type Pipes = Array; +export type PipeResult = Pipes | Pipe; + +type PushPipes = (target: Pipes, pipes: PipeResult) => Pipes; +type PipesToNode = ( + pipes: PipeResult, + plugin: ValibotPlugin['Instance'], +) => Pipe; + +export const pipesToNode: PipesToNode = (pipes, plugin) => { + if (!(pipes instanceof Array)) return pipes; + if (pipes.length === 1) return pipes[0]!; + + const v = plugin.external('valibot.v'); + return $(v) + .attr(identifiers.methods.pipe) + .call(...pipes); +}; + +export const pushPipes: PushPipes = (target, pipes) => { + if (pipes instanceof Array) { + target.push(...pipes); + } else { + target.push(pipes); + } + return target; +}; + +export interface PipesUtils { + push: PushPipes; + toNode: PipesToNode; +} + +/** + * Functions for working with pipes. + */ +export const pipes: PipesUtils = { + /** + * Push pipes into target array. + */ + push: pushPipes, + /** + * Convert pipes to a single node. + */ + toNode: pipesToNode, +}; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts b/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts deleted file mode 100644 index 3f595c4a1..000000000 --- a/packages/openapi-ts/src/plugins/valibot/shared/pipesToAst.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { $ } from '~/ts-dsl'; - -import type { ValibotPlugin } from '../types'; -import { identifiers } from '../v1/constants'; - -export const pipesToAst = ( - pipes: ReadonlyArray>, - plugin: ValibotPlugin['Instance'], -): ReturnType => { - if (pipes.length === 1) { - return pipes[0]!; - } - - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); - return $(v) - .attr(identifiers.methods.pipe) - .call(...pipes); -}; diff --git a/packages/openapi-ts/src/plugins/valibot/shared/types.d.ts b/packages/openapi-ts/src/plugins/valibot/shared/types.d.ts index 6ba568c47..64c3fe199 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/types.d.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/types.d.ts @@ -2,13 +2,13 @@ import type { Refs, SymbolMeta } from '@hey-api/codegen-core'; import type ts from 'typescript'; import type { IR } from '~/ir/types'; -import type { $ } from '~/ts-dsl'; import type { ValibotPlugin } from '../types'; +import type { Pipes } from './pipes'; export type Ast = { hasLazyExpression?: boolean; - pipes: Array>; + pipes: Pipes; typeName?: string | ts.Identifier; }; diff --git a/packages/openapi-ts/src/plugins/valibot/types.d.ts b/packages/openapi-ts/src/plugins/valibot/types.d.ts index f79490647..436cd07bc 100644 --- a/packages/openapi-ts/src/plugins/valibot/types.d.ts +++ b/packages/openapi-ts/src/plugins/valibot/types.d.ts @@ -1,13 +1,20 @@ -import type { Symbol } from '@hey-api/codegen-core'; +import type { Refs, Symbol } from '@hey-api/codegen-core'; import type ts from 'typescript'; import type { IR } from '~/ir/types'; -import type { DefinePlugin, Plugin } from '~/plugins'; +import type { DefinePlugin, Plugin, SchemaWithType } from '~/plugins'; +import type { + MaybeBigInt, + ShouldCoerceToBigInt, +} from '~/plugins/shared/utils/coerce'; +import type { GetIntegerLimit } from '~/plugins/shared/utils/formats'; import type { $, DollarTsDsl, TsDsl } from '~/ts-dsl'; import type { StringCase, StringName } from '~/types/case'; import type { MaybeArray } from '~/types/utils'; import type { IApi } from './api'; +import type { Pipe, PipeResult, PipesUtils } from './shared/pipes'; +import type { Ast, PluginState } from './shared/types'; export type UserConfig = Plugin.Name<'valibot'> & Plugin.Hooks & @@ -322,6 +329,11 @@ export type Config = Plugin.Name<'valibot'> & }; type SharedResolverArgs = DollarTsDsl & { + /** + * Functions for working with pipes. + */ + pipes: PipesUtils; + plugin: ValibotPlugin['Instance']; /** * The current builder state being processed by this resolver. * @@ -329,26 +341,78 @@ type SharedResolverArgs = DollarTsDsl & { * being assembled to form a schema definition. * * Each pipe can be extended, modified, or replaced to customize how the - * resulting schema is constructed. Returning `undefined` from a resolver will - * use the default generation behavior. + * resulting schema is constructed. */ - pipes: Array>; - plugin: ValibotPlugin['Instance']; - v: Symbol; + result: Pipes; + /** + * Provides access to commonly used symbols within the Valibot plugin. + */ + symbols: { + v: Symbol; + }; }; -export type FormatResolverArgs = SharedResolverArgs & { - schema: IR.SchemaObject; +export type NumberResolverContext = SharedResolverArgs & { + /** + * Nodes used to build different parts of the number schema. + */ + nodes: { + base: (ctx: NumberResolverContext) => PipeResult; + const: (ctx: NumberResolverContext) => PipeResult | undefined; + max: (ctx: NumberResolverContext) => PipeResult | undefined; + min: (ctx: NumberResolverContext) => PipeResult | undefined; + }; + schema: SchemaWithType<'integer' | 'number'>; + /** + * Utility functions for number schema processing. + */ + utils: { + getIntegerLimit: GetIntegerLimit; + maybeBigInt: MaybeBigInt; + shouldCoerceToBigInt: ShouldCoerceToBigInt; + }; }; -export type ObjectBaseResolverArgs = SharedResolverArgs & { - /** Null = never */ - additional?: ReturnType | null; - schema: IR.SchemaObject; - shape: ReturnType; +export type ObjectResolverContext = SharedResolverArgs & { + /** + * Nodes used to build different parts of the object schema. + */ + nodes: { + /** + * If `additionalProperties` is `false` or `{ type: 'never' }`, returns `null` + * to indicate no additional properties are allowed. + */ + additionalProperties: ( + ctx: ObjectResolverContext, + ) => Pipe | null | undefined; + base: (ctx: ObjectResolverContext) => PipeResult; + shape: (ctx: ObjectResolverContext) => ReturnType; + }; + schema: SchemaWithType<'object'>; + /** + * Utility functions for object schema processing. + */ + utils: { + ast: Partial>; + state: Refs; + }; }; -type ResolverResult = boolean | number; +export type StringResolverContext = SharedResolverArgs & { + /** + * Nodes used to build different parts of the string schema. + */ + nodes: { + base: (ctx: StringResolverContext) => PipeResult; + const: (ctx: StringResolverContext) => PipeResult | undefined; + format: (ctx: StringResolverContext) => PipeResult | undefined; + length: (ctx: StringResolverContext) => PipeResult | undefined; + maxLength: (ctx: StringResolverContext) => PipeResult | undefined; + minLength: (ctx: StringResolverContext) => PipeResult | undefined; + pattern: (ctx: StringResolverContext) => PipeResult | undefined; + }; + schema: SchemaWithType<'string'>; +}; export type ValidatorResolverArgs = SharedResolverArgs & { operation: IR.Operation; @@ -361,79 +425,29 @@ type ValidatorResolver = ( type Resolvers = Plugin.Resolvers<{ /** - * Resolvers for number schemas. + * Resolver for number schemas. + * + * Allows customization of how number types are rendered. * - * Allows customization of how number types are rendered, including - * per-format handling. + * Returning `undefined` will execute the default resolver logic. */ - number?: { - /** - * Controls the base segment for number schemas. - * - * Returning `undefined` will execute the default resolver logic. - */ - base?: (args: FormatResolverArgs) => ResolverResult | undefined; - /** - * Resolvers for number formats (e.g., `float`, `double`, `int32`). - * - * Each key represents a specific format name with a custom - * resolver function that controls how that format is rendered. - * - * Example path: `~resolvers.number.formats.float` - * - * Returning `undefined` from a resolver will apply the default - * generation behavior for that format. - */ - formats?: Record< - string, - (args: FormatResolverArgs) => ResolverResult | undefined - >; - }; + number?: (args: NumberResolverContext) => PipeResult | undefined; /** - * Resolvers for object schemas. + * Resolver for object schemas. * * Allows customization of how object types are rendered. * - * Example path: `~resolvers.object.base` - * - * Returning `undefined` from a resolver will apply the default - * generation behavior for the object schema. + * Returning `undefined` will execute the default resolver logic. */ - object?: { - /** - * Controls how object schemas are constructed. - * - * Called with the fully assembled shape (properties) and any additional - * property schema, allowing the resolver to choose the correct Valibot - * base constructor and modify the schema chain if needed. - * - * Returning `undefined` will execute the default resolver logic. - */ - base?: (args: ObjectBaseResolverArgs) => ResolverResult | undefined; - }; + object?: (ctx: ObjectResolverContext) => PipeResult | undefined; /** - * Resolvers for string schemas. + * Resolver for string schemas. + * + * Allows customization of how string types are rendered. * - * Allows customization of how string types are rendered, including - * per-format handling. + * Returning `undefined` will execute the default resolver logic. */ - string?: { - /** - * Resolvers for string formats (e.g., `uuid`, `email`, `date-time`). - * - * Each key represents a specific format name with a custom - * resolver function that controls how that format is rendered. - * - * Example path: `~resolvers.string.formats.uuid` - * - * Returning `undefined` from a resolver will apply the default - * generation behavior for that format. - */ - formats?: Record< - string, - (args: FormatResolverArgs) => ResolverResult | undefined - >; - }; + string?: (ctx: StringResolverContext) => PipeResult | undefined; /** * Resolvers for request and response validators. * diff --git a/packages/openapi-ts/src/plugins/valibot/v1/api.ts b/packages/openapi-ts/src/plugins/valibot/v1/api.ts index 2f14b3571..76e81c219 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/api.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/api.ts @@ -1,14 +1,21 @@ import { $ } from '~/ts-dsl'; +import { pipes } from '../shared/pipes'; import type { ValidatorArgs } from '../shared/types'; import type { ValidatorResolverArgs } from '../types'; import { identifiers } from './constants'; -const defaultValidatorResolver = ({ - schema, - v, -}: ValidatorResolverArgs): ReturnType => - $(v).attr(identifiers.async.parseAsync).call(schema, 'data').await().return(); +const validatorResolver = ( + ctx: ValidatorResolverArgs, +): ReturnType => { + const { schema } = ctx; + const { v } = ctx.symbols; + return $(v) + .attr(identifiers.async.parseAsync) + .call(schema, 'data') + .await() + .return(); +}; export const createRequestValidatorV1 = ({ operation, @@ -23,22 +30,21 @@ export const createRequestValidatorV1 = ({ }); if (!symbol) return; - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); const args: ValidatorResolverArgs = { $, operation, - pipes: [], + pipes, plugin, + result: [], schema: symbol, - v, + symbols: { + v: plugin.external('valibot.v'), + }, }; const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.request; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; @@ -65,22 +71,21 @@ export const createResponseValidatorV1 = ({ }); if (!symbol) return; - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); const args: ValidatorResolverArgs = { $, operation, - pipes: [], + pipes, plugin, + result: [], schema: symbol, - v, + symbols: { + v: plugin.external('valibot.v'), + }, }; const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.response; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts index b22a52b24..6708f2f3b 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts @@ -11,7 +11,7 @@ import { pathToJsonPointer, refToName } from '~/utils/ref'; import { exportAst } from '../shared/export'; import { irOperationToAst } from '../shared/operation'; -import { pipesToAst } from '../shared/pipesToAst'; +import { pipesToNode } from '../shared/pipes'; import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types'; import { irWebhookToAst } from '../shared/webhook'; import type { ValibotPlugin } from '../types'; @@ -87,7 +87,7 @@ export const irSchemaToAst = ({ path: ref([...fromRef(state.path), 'items', index]), }, }); - return pipesToAst(itemAst.pipes, plugin); + return pipesToNode(itemAst.pipes, plugin); }); if (schema.logicalOperator === 'and') { @@ -129,7 +129,7 @@ export const irSchemaToAst = ({ $(v) .attr(identifiers.schemas.optional) .call( - pipesToAst(ast.pipes, plugin), + pipesToNode(ast.pipes, plugin), schema.type === 'integer' || schema.type === 'number' ? maybeBigInt(schema.default, schema.format) : $.fromValue(schema.default), @@ -139,7 +139,7 @@ export const irSchemaToAst = ({ ast.pipes = [ $(v) .attr(identifiers.schemas.optional) - .call(pipesToAst(ast.pipes, plugin)), + .call(pipesToNode(ast.pipes, plugin)), ]; } } diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts index fdde3a56c..85a9b5711 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/array.ts @@ -4,7 +4,7 @@ import { deduplicateSchema } from '~/ir/schema'; import type { SchemaWithType } from '~/plugins'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import { pipesToNode } from '../../shared/pipes'; import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; import { identifiers } from '../constants'; import { irSchemaToAst } from '../plugin'; @@ -54,7 +54,7 @@ export const arrayToAst = ({ if (itemAst.hasLazyExpression) { result.hasLazyExpression = true; } - return pipesToAst(itemAst.pipes, plugin); + return pipesToNode(itemAst.pipes, plugin); }); if (itemExpressions.length === 1) { diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts index 9a6c8f215..2d8d738fc 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/boolean.ts @@ -1,7 +1,7 @@ import type { SchemaWithType } from '~/plugins'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import { pipesToNode } from '../../shared/pipes'; import type { IrSchemaToAstOptions } from '../../shared/types'; import { identifiers } from '../constants'; @@ -22,9 +22,9 @@ export const booleanToAst = ({ pipes.push( $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)), ); - return pipesToAst(pipes, plugin); + return pipesToNode(pipes, plugin); } pipes.push($(v).attr(identifiers.schemas.boolean).call()); - return pipesToAst(pipes, plugin); + return pipesToNode(pipes, plugin); }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts index c3674b068..77b8c3c8c 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts @@ -2,16 +2,16 @@ import type { SchemaWithType } from '~/plugins'; import { shouldCoerceToBigInt } from '~/plugins/shared/utils/coerce'; import type { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import { pipesToNode } from '../../shared/pipes'; import type { IrSchemaToAstOptions } from '../../shared/types'; import { arrayToAst } from './array'; import { booleanToAst } from './boolean'; import { enumToAst } from './enum'; import { neverToAst } from './never'; import { nullToAst } from './null'; -import { numberToAst } from './number'; +import { numberToNode } from './number'; import { objectToAst } from './object'; -import { stringToAst } from './string'; +import { stringToNode } from './string'; import { tupleToAst } from './tuple'; import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; @@ -29,7 +29,7 @@ export const irSchemaWithTypeToAst = ({ switch (schema.type) { case 'array': return { - expression: pipesToAst( + expression: pipesToNode( arrayToAst({ ...args, schema: schema as SchemaWithType<'array'>, @@ -54,7 +54,7 @@ export const irSchemaWithTypeToAst = ({ case 'integer': case 'number': return { - expression: numberToAst({ + expression: numberToNode({ ...args, schema: schema as SchemaWithType<'integer' | 'number'>, }), @@ -75,7 +75,7 @@ export const irSchemaWithTypeToAst = ({ }; case 'object': return { - expression: pipesToAst( + expression: pipesToNode( objectToAst({ ...args, schema: schema as SchemaWithType<'object'>, @@ -86,18 +86,18 @@ export const irSchemaWithTypeToAst = ({ case 'string': return { expression: shouldCoerceToBigInt(schema.format) - ? numberToAst({ + ? numberToNode({ ...args, schema: { ...schema, type: 'number' }, }) - : stringToAst({ + : stringToNode({ ...args, schema: schema as SchemaWithType<'string'>, }), }; case 'tuple': return { - expression: pipesToAst( + expression: pipesToNode( tupleToAst({ ...args, schema: schema as SchemaWithType<'tuple'>, diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts index 9372684e4..ed339372c 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/number.ts @@ -6,18 +6,17 @@ import { import { getIntegerLimit } from '~/plugins/shared/utils/formats'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import type { Pipe, PipeResult, Pipes } from '../../shared/pipes'; +import { pipes } from '../../shared/pipes'; import type { IrSchemaToAstOptions } from '../../shared/types'; -import type { FormatResolverArgs } from '../../types'; +import type { NumberResolverContext } from '../../types'; import { identifiers } from '../constants'; -const defaultBaseResolver = ({ - pipes, - schema, - v, -}: FormatResolverArgs): boolean | number => { - if (shouldCoerceToBigInt(schema.format)) { - pipes.push( +function baseNode(ctx: NumberResolverContext): PipeResult { + const { schema } = ctx; + const { v } = ctx.symbols; + if (ctx.utils.shouldCoerceToBigInt(schema.format)) { + return [ $(v) .attr(identifiers.schemas.union) .call( @@ -30,101 +29,119 @@ const defaultBaseResolver = ({ $(v) .attr(identifiers.actions.transform) .call($.func().param('x').do($('BigInt').call('x').return())), - ); - } else { - pipes.push($(v).attr(identifiers.schemas.number).call()); - if (schema.type === 'integer') { - pipes.push($(v).attr(identifiers.actions.integer).call()); - } + ]; } + const pipes: Pipes = []; + pipes.push($(v).attr(identifiers.schemas.number).call()); + if (schema.type === 'integer') { + pipes.push($(v).attr(identifiers.actions.integer).call()); + } + return pipes; +} - return true; -}; - -export const numberToAst = ({ - plugin, - schema, -}: IrSchemaToAstOptions & { - schema: SchemaWithType<'integer' | 'number'>; -}) => { - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); +function constNode(ctx: NumberResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (schema.const === undefined) return; + return $(v) + .attr(identifiers.schemas.literal) + .call(ctx.utils.maybeBigInt(schema.const, schema.format)); +} - if (schema.const !== undefined) { +function maxNode(ctx: NumberResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (schema.exclusiveMaximum !== undefined) { return $(v) - .attr(identifiers.schemas.literal) - .call(maybeBigInt(schema.const, schema.format)); + .attr(identifiers.actions.ltValue) + .call(ctx.utils.maybeBigInt(schema.exclusiveMaximum, schema.format)); } + if (schema.maximum !== undefined) { + return $(v) + .attr(identifiers.actions.maxValue) + .call(ctx.utils.maybeBigInt(schema.maximum, schema.format)); + } + const limit = ctx.utils.getIntegerLimit(schema.format); + if (limit) { + return $(v) + .attr(identifiers.actions.maxValue) + .call( + ctx.utils.maybeBigInt(limit.maxValue, schema.format), + $.literal(limit.maxError), + ); + } + return; +} - const pipes: Array> = []; - - const args: FormatResolverArgs = { $, pipes, plugin, schema, v }; - const resolver = plugin.config['~resolvers']?.number?.base; - if (!resolver?.(args)) defaultBaseResolver(args); - - let hasLowerBound = false; - let hasUpperBound = false; - +function minNode(ctx: NumberResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; if (schema.exclusiveMinimum !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.gtValue) - .call(maybeBigInt(schema.exclusiveMinimum, schema.format)), - ); - hasLowerBound = true; - } else if (schema.minimum !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.minValue) - .call(maybeBigInt(schema.minimum, schema.format)), - ); - hasLowerBound = true; + return $(v) + .attr(identifiers.actions.gtValue) + .call(ctx.utils.maybeBigInt(schema.exclusiveMinimum, schema.format)); } - - if (schema.exclusiveMaximum !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.ltValue) - .call(maybeBigInt(schema.exclusiveMaximum, schema.format)), - ); - hasUpperBound = true; - } else if (schema.maximum !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.maxValue) - .call(maybeBigInt(schema.maximum, schema.format)), - ); - hasUpperBound = true; + if (schema.minimum !== undefined) { + return $(v) + .attr(identifiers.actions.minValue) + .call(ctx.utils.maybeBigInt(schema.minimum, schema.format)); } - - const integerLimit = getIntegerLimit(schema.format); - if (integerLimit) { - if (!hasLowerBound) { - pipes.push( - $(v) - .attr(identifiers.actions.minValue) - .call( - maybeBigInt(integerLimit.minValue, schema.format), - $.literal(integerLimit.minError), - ), - ); - hasLowerBound = true; - } - - if (!hasUpperBound) { - pipes.push( - $(v) - .attr(identifiers.actions.maxValue) - .call( - maybeBigInt(integerLimit.maxValue, schema.format), - $.literal(integerLimit.maxError), - ), + const limit = ctx.utils.getIntegerLimit(schema.format); + if (limit) { + return $(v) + .attr(identifiers.actions.minValue) + .call( + ctx.utils.maybeBigInt(limit.minValue, schema.format), + $.literal(limit.minError), ); - hasUpperBound = true; - } } + return; +} + +function numberResolver(ctx: NumberResolverContext): Pipes { + const constNode = ctx.nodes.const(ctx); + if (constNode) return ctx.pipes.push(ctx.result, constNode); + + const baseNode = ctx.nodes.base(ctx); + if (baseNode) ctx.pipes.push(ctx.result, baseNode); + + const minNode = ctx.nodes.min(ctx); + if (minNode) ctx.pipes.push(ctx.result, minNode); + + const maxNode = ctx.nodes.max(ctx); + if (maxNode) ctx.pipes.push(ctx.result, maxNode); - return pipesToAst(pipes, plugin); + return ctx.result; +} + +export const numberToNode = ({ + plugin, + schema, +}: IrSchemaToAstOptions & { + schema: SchemaWithType<'integer' | 'number'>; +}): Pipe => { + const ctx: NumberResolverContext = { + $, + nodes: { + base: baseNode, + const: constNode, + max: maxNode, + min: minNode, + }, + pipes, + plugin, + result: [], + schema, + symbols: { + v: plugin.external('valibot.v'), + }, + utils: { + getIntegerLimit, + maybeBigInt, + shouldCoerceToBigInt, + }, + }; + const resolver = plugin.config['~resolvers']?.number; + const node = resolver?.(ctx) ?? numberResolver(ctx); + return ctx.pipes.toNode(node, plugin); }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts index 633d45c4f..be015f7ce 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/object.ts @@ -3,111 +3,114 @@ import { fromRef, ref } from '@hey-api/codegen-core'; import type { SchemaWithType } from '~/plugins'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import type { Pipe, PipeResult } from '../../shared/pipes'; +import { pipes } from '../../shared/pipes'; import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; -import type { ObjectBaseResolverArgs } from '../../types'; +import type { ObjectResolverContext } from '../../types'; import { identifiers } from '../constants'; import { irSchemaToAst } from '../plugin'; -function defaultBaseResolver({ - additional, - pipes, - shape, - v, -}: ObjectBaseResolverArgs): number { - // Handle `additionalProperties: { type: 'never' }` → v.strictObject() +function additionalPropertiesNode( + ctx: ObjectResolverContext, +): Pipe | null | undefined { + const { plugin, schema } = ctx; + + if (!schema.additionalProperties || !schema.additionalProperties.type) return; + if (schema.additionalProperties.type === 'never') return null; + + const additionalAst = irSchemaToAst({ + plugin, + schema: schema.additionalProperties, + state: { + ...ctx.utils.state, + path: ref([...fromRef(ctx.utils.state.path), 'additionalProperties']), + }, + }); + if (additionalAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; + return pipes.toNode(additionalAst.pipes, plugin); +} + +function baseNode(ctx: ObjectResolverContext): PipeResult { + const { v } = ctx.symbols; + + const additional = ctx.nodes.additionalProperties(ctx); + const shape = ctx.nodes.shape(ctx); + if (additional === null) { - return pipes.push($(v).attr(identifiers.schemas.strictObject).call(shape)); + return $(v).attr(identifiers.schemas.strictObject).call(shape); } - // Handle additionalProperties as schema → v.record() or v.objectWithRest() if (additional) { if (shape.isEmpty) { - return pipes.push( - $(v) - .attr(identifiers.schemas.record) - .call($(v).attr(identifiers.schemas.string).call(), additional), - ); + return $(v) + .attr(identifiers.schemas.record) + .call($(v).attr(identifiers.schemas.string).call(), additional); } - // If there are named properties, use v.objectWithRest() to validate both - return pipes.push( - $(v).attr(identifiers.schemas.objectWithRest).call(shape, additional), - ); + return $(v) + .attr(identifiers.schemas.objectWithRest) + .call(shape, additional); } - // Default case → v.object() - return pipes.push($(v).attr(identifiers.schemas.object).call(shape)); + return $(v).attr(identifiers.schemas.object).call(shape); } -export const objectToAst = ({ - plugin, - schema, - state, -}: IrSchemaToAstOptions & { - schema: SchemaWithType<'object'>; -}): Omit => { - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); - - const result: Partial> = {}; - const pipes: Array> = []; - +function objectResolver(ctx: ObjectResolverContext): PipeResult { // TODO: parser - handle constants + return ctx.nodes.base(ctx); +} +function shapeNode(ctx: ObjectResolverContext): ReturnType { + const { plugin, schema } = ctx; const shape = $.object().pretty(); - const required = schema.required ?? []; for (const name in schema.properties) { const property = schema.properties[name]!; - const isRequired = required.includes(name); const propertyAst = irSchemaToAst({ - optional: !isRequired, + optional: !schema.required?.includes(name), plugin, schema: property, state: { - ...state, - path: ref([...fromRef(state.path), 'properties', name]), + ...ctx.utils.state, + path: ref([...fromRef(ctx.utils.state.path), 'properties', name]), }, }); - if (propertyAst.hasLazyExpression) result.hasLazyExpression = true; - - shape.prop(name, pipesToAst(propertyAst.pipes, plugin)); + if (propertyAst.hasLazyExpression) ctx.utils.ast.hasLazyExpression = true; + shape.prop(name, pipes.toNode(propertyAst.pipes, plugin)); } - let additional: ReturnType | null | undefined; - if (schema.additionalProperties && schema.additionalProperties.type) { - if (schema.additionalProperties.type === 'never') { - additional = null; - } else { - const additionalAst = irSchemaToAst({ - plugin, - schema: schema.additionalProperties, - state: { - ...state, - path: ref([...fromRef(state.path), 'additionalProperties']), - }, - }); - if (additionalAst.hasLazyExpression) result.hasLazyExpression = true; - additional = pipesToAst(additionalAst.pipes, plugin); - } - } + return shape; +} - const args: ObjectBaseResolverArgs = { +export const objectToAst = ({ + plugin, + schema, + state, +}: IrSchemaToAstOptions & { + schema: SchemaWithType<'object'>; +}): Omit => { + const ctx: ObjectResolverContext = { $, - additional, + nodes: { + additionalProperties: additionalPropertiesNode, + base: baseNode, + shape: shapeNode, + }, pipes, plugin, + result: [], schema, - shape, - v, + symbols: { + v: plugin.external('valibot.v'), + }, + utils: { + ast: {}, + state, + }, }; - const resolver = plugin.config['~resolvers']?.object?.base; - if (!resolver?.(args)) defaultBaseResolver(args); - - result.pipes = [pipesToAst(pipes, plugin)]; - return result as Omit; + const resolver = plugin.config['~resolvers']?.object; + const node = resolver?.(ctx) ?? objectResolver(ctx); + ctx.utils.ast.pipes = [ctx.pipes.toNode(node, plugin)]; + return ctx.utils.ast as Omit; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts index 4f5399083..176ac29a7 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/string.ts @@ -1,88 +1,136 @@ import type { SchemaWithType } from '~/plugins'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import type { Pipe, PipeResult, Pipes } from '../../shared/pipes'; +import { pipes } from '../../shared/pipes'; import type { IrSchemaToAstOptions } from '../../shared/types'; -import type { FormatResolverArgs } from '../../types'; +import type { StringResolverContext } from '../../types'; import { identifiers } from '../constants'; -const defaultFormatResolver = ({ - pipes, - schema, - v, -}: FormatResolverArgs): boolean | number => { +function baseNode(ctx: StringResolverContext): PipeResult { + const { v } = ctx.symbols; + return $(v).attr(identifiers.schemas.string).call(); +} + +function constNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (typeof schema.const !== 'string') return; + return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)); +} + +function formatNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; switch (schema.format) { case 'date': - return pipes.push($(v).attr(identifiers.actions.isoDate).call()); + return $(v).attr(identifiers.actions.isoDate).call(); case 'date-time': - return pipes.push($(v).attr(identifiers.actions.isoTimestamp).call()); + return $(v).attr(identifiers.actions.isoTimestamp).call(); case 'email': - return pipes.push($(v).attr(identifiers.actions.email).call()); + return $(v).attr(identifiers.actions.email).call(); case 'ipv4': case 'ipv6': - return pipes.push($(v).attr(identifiers.actions.ip).call()); + return $(v).attr(identifiers.actions.ip).call(); case 'time': - return pipes.push($(v).attr(identifiers.actions.isoTimeSecond).call()); + return $(v).attr(identifiers.actions.isoTimeSecond).call(); case 'uri': - return pipes.push($(v).attr(identifiers.actions.url).call()); + return $(v).attr(identifiers.actions.url).call(); case 'uuid': - return pipes.push($(v).attr(identifiers.actions.uuid).call()); + return $(v).attr(identifiers.actions.uuid).call(); } - return true; -}; + return; +} -export const stringToAst = ({ - plugin, - schema, -}: IrSchemaToAstOptions & { - schema: SchemaWithType<'string'>; -}): ReturnType => { - const v = plugin.referenceSymbol({ - category: 'external', - resource: 'valibot.v', - }); - - if (typeof schema.const === 'string') { - return $(v).attr(identifiers.schemas.literal).call($.literal(schema.const)); - } +function lengthNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (schema.minLength === undefined || schema.minLength !== schema.maxLength) + return; + return $(v) + .attr(identifiers.actions.length) + .call($.literal(schema.minLength)); +} - const pipes = [$(v).attr(identifiers.schemas.string).call()]; +function maxLengthNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (schema.maxLength === undefined) return; + return $(v) + .attr(identifiers.actions.maxLength) + .call($.literal(schema.maxLength)); +} - if (schema.format) { - const args: FormatResolverArgs = { $, pipes, plugin, schema, v }; - const resolver = - plugin.config['~resolvers']?.string?.formats?.[schema.format]; - if (!resolver?.(args)) defaultFormatResolver(args); - } +function minLengthNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (schema.minLength === undefined) return; + return $(v) + .attr(identifiers.actions.minLength) + .call($.literal(schema.minLength)); +} + +function patternNode(ctx: StringResolverContext): PipeResult | undefined { + const { schema } = ctx; + const { v } = ctx.symbols; + if (!schema.pattern) return; + return $(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)); +} - if (schema.minLength === schema.maxLength && schema.minLength !== undefined) { - pipes.push( - $(v).attr(identifiers.actions.length).call($.literal(schema.minLength)), - ); +function stringResolver(ctx: StringResolverContext): Pipes { + const constNode = ctx.nodes.const(ctx); + if (constNode) return ctx.pipes.push(ctx.result, constNode); + + const baseNode = ctx.nodes.base(ctx); + if (baseNode) ctx.pipes.push(ctx.result, baseNode); + + const formatNode = ctx.nodes.format(ctx); + if (formatNode) ctx.pipes.push(ctx.result, formatNode); + + const lengthNode = ctx.nodes.length(ctx); + if (lengthNode) { + ctx.pipes.push(ctx.result, lengthNode); } else { - if (schema.minLength !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.minLength) - .call($.literal(schema.minLength)), - ); - } - - if (schema.maxLength !== undefined) { - pipes.push( - $(v) - .attr(identifiers.actions.maxLength) - .call($.literal(schema.maxLength)), - ); - } - } + const minLengthNode = ctx.nodes.minLength(ctx); + if (minLengthNode) ctx.pipes.push(ctx.result, minLengthNode); - if (schema.pattern) { - pipes.push( - $(v).attr(identifiers.actions.regex).call($.regexp(schema.pattern)), - ); + const maxLengthNode = ctx.nodes.maxLength(ctx); + if (maxLengthNode) ctx.pipes.push(ctx.result, maxLengthNode); } - return pipesToAst(pipes, plugin); + const patternNode = ctx.nodes.pattern(ctx); + if (patternNode) ctx.pipes.push(ctx.result, patternNode); + + return ctx.result; +} + +export const stringToNode = ({ + plugin, + schema, +}: IrSchemaToAstOptions & { + schema: SchemaWithType<'string'>; +}): Pipe => { + const ctx: StringResolverContext = { + $, + nodes: { + base: baseNode, + const: constNode, + format: formatNode, + length: lengthNode, + maxLength: maxLengthNode, + minLength: minLengthNode, + pattern: patternNode, + }, + pipes, + plugin, + result: [], + schema, + symbols: { + v: plugin.external('valibot.v'), + }, + }; + const resolver = plugin.config['~resolvers']?.string; + const node = resolver?.(ctx) ?? stringResolver(ctx); + return ctx.pipes.toNode(node, plugin); }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts b/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts index f86b6795b..a6480e7f5 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/tuple.ts @@ -3,7 +3,7 @@ import { fromRef, ref } from '@hey-api/codegen-core'; import type { SchemaWithType } from '~/plugins'; import { $ } from '~/ts-dsl'; -import { pipesToAst } from '../../shared/pipesToAst'; +import { pipesToNode } from '../../shared/pipes'; import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; import { identifiers } from '../constants'; import { irSchemaToAst } from '../plugin'; @@ -48,7 +48,7 @@ export const tupleToAst = ({ if (schemaPipes.hasLazyExpression) { result.hasLazyExpression = true; } - return pipesToAst(schemaPipes.pipes, plugin); + return pipesToNode(schemaPipes.pipes, plugin); }); result.pipes = [ $(v) diff --git a/packages/openapi-ts/src/plugins/zod/mini/api.ts b/packages/openapi-ts/src/plugins/zod/mini/api.ts index f90894ecc..0f3ddcd8e 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/api.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/api.ts @@ -4,7 +4,7 @@ import { identifiers } from '../constants'; import type { ValidatorArgs } from '../shared/types'; import type { ValidatorResolverArgs } from '../types'; -const defaultValidatorResolver = ({ +const validatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => $(schema).attr(identifiers.parseAsync).call('data').await().return(); @@ -37,7 +37,7 @@ export const createRequestValidatorMini = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.request; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; @@ -79,7 +79,7 @@ export const createResponseValidatorMini = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.response; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; diff --git a/packages/openapi-ts/src/plugins/zod/v3/api.ts b/packages/openapi-ts/src/plugins/zod/v3/api.ts index 9c535342f..2fc8111cf 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/api.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/api.ts @@ -4,7 +4,7 @@ import { identifiers } from '../constants'; import type { ValidatorArgs } from '../shared/types'; import type { ValidatorResolverArgs } from '../types'; -const defaultValidatorResolver = ({ +const validatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => $(schema).attr(identifiers.parseAsync).call('data').await().return(); @@ -37,7 +37,7 @@ export const createRequestValidatorV3 = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.request; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; @@ -79,7 +79,7 @@ export const createResponseValidatorV3 = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.response; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; diff --git a/packages/openapi-ts/src/plugins/zod/v4/api.ts b/packages/openapi-ts/src/plugins/zod/v4/api.ts index 9feba80dc..0ec6602a8 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/api.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/api.ts @@ -4,7 +4,7 @@ import { identifiers } from '../constants'; import type { ValidatorArgs } from '../shared/types'; import type { ValidatorResolverArgs } from '../types'; -const defaultValidatorResolver = ({ +const validatorResolver = ({ schema, }: ValidatorResolverArgs): ReturnType => $(schema).attr(identifiers.parseAsync).call('data').await().return(); @@ -37,7 +37,7 @@ export const createRequestValidatorV4 = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.request; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return; @@ -79,7 +79,7 @@ export const createResponseValidatorV4 = ({ const validator = plugin.config['~resolvers']?.validator; const resolver = typeof validator === 'function' ? validator : validator?.response; - const candidates = [resolver, defaultValidatorResolver]; + const candidates = [resolver, validatorResolver]; for (const candidate of candidates) { const statements = candidate?.(args); if (statements === null) return;