From ebf4f3410553fae62a74385e7df2edc7a329b620 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 4 Nov 2025 16:12:23 +0800 Subject: [PATCH] feat(sdk): support setting validators per operation --- dev/openapi-ts.config.ts | 12 +-- .../src/plugins/@hey-api/sdk/config.ts | 52 +++++++----- .../plugins/@hey-api/sdk/shared/operation.ts | 13 ++- .../plugins/@hey-api/sdk/shared/validator.ts | 62 +++++++------- .../src/plugins/@hey-api/sdk/types.d.ts | 82 ++++++++----------- 5 files changed, 108 insertions(+), 113 deletions(-) diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index c919e1ed0..664c212e7 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -272,11 +272,13 @@ export default defineConfig(() => { // signature: 'object', // transformer: '@hey-api/transformers', // transformer: true, - // validator: true, - // validator: { - // request: 'zod', - // response: 'zod', + // validator(operation) { + // return 'zod'; // }, + validator: { + request: 'valibot', + response: 'zod', + }, '~hooks': { symbols: { // getFilePath: (symbol) => { @@ -339,7 +341,7 @@ export default defineConfig(() => { }, }, { - name: 'arktype', + // name: 'arktype', // types: { // infer: true, // }, diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts index 8000cf8d7..b6f908280 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts @@ -1,4 +1,5 @@ import { definePluginConfig } from '~/plugins/shared/utils/config'; +import type { PluginValidatorNames } from '~/plugins/types'; import { Api } from './api'; import { handler } from './plugin'; @@ -47,32 +48,40 @@ export const defaultConfig: HeyApiSdkPlugin['Config'] = { plugin.config.transformer = false; } - if (typeof plugin.config.validator !== 'object') { - plugin.config.validator = { - request: plugin.config.validator, - response: plugin.config.validator, - }; - } - - if (plugin.config.validator.request) { - if (typeof plugin.config.validator.request === 'boolean') { - plugin.config.validator.request = context.pluginByTag('validator'); + const { validator } = plugin.config; + plugin.config.validator = ((operation) => { + if (typeof validator === 'boolean') { + const validatorPlugin = validator + ? context.pluginByTag('validator') + : undefined; + const validatorValue = validatorPlugin + ? (validatorPlugin as PluginValidatorNames) + : false; + return { + request: validatorValue, + response: validatorValue, + }; } - plugin.dependencies.add(plugin.config.validator.request!); - } else { - plugin.config.validator.request = false; - } + if (typeof validator === 'string') { + return { + request: validator, + response: validator, + }; + } - if (plugin.config.validator.response) { - if (typeof plugin.config.validator.response === 'boolean') { - plugin.config.validator.response = context.pluginByTag('validator'); + if (typeof validator === 'function') { + const result = validator(operation); + if (typeof result === 'object') { + // result.request + } } - plugin.dependencies.add(plugin.config.validator.response!); - } else { - plugin.config.validator.response = false; - } + return { + request: false, + response: false, + }; + }) satisfies HeyApiSdkPlugin['Types']['resolvedConfig']['validator']; if (plugin.config.instance) { if (typeof plugin.config.instance !== 'string') { @@ -91,6 +100,7 @@ export const defaultConfig: HeyApiSdkPlugin['Config'] = { } } }, + tags: ['sdk'], }; /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts index 26084d154..b62a0de0b 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/operation.ts @@ -19,7 +19,7 @@ import type { HeyApiSdkPlugin } from '../types'; import { operationAuth } from './auth'; import { nuxtTypeComposable, nuxtTypeDefault } from './constants'; import { getSignatureParameters } from './signature'; -import { createRequestValidator, createResponseValidator } from './validator'; +import { createValidators } from './validator'; interface ClassNameEntry { /** @@ -502,11 +502,11 @@ export const operationStatements = ({ }); } - const requestValidator = createRequestValidator({ operation, plugin }); - if (requestValidator) { + const validators = createValidators({ operation, plugin }); + if (validators.request) { requestOptions.push({ key: 'requestValidator', - value: requestValidator, + value: validators.request, }); } @@ -553,11 +553,10 @@ export const operationStatements = ({ } } - const responseValidator = createResponseValidator({ operation, plugin }); - if (responseValidator) { + if (validators.response) { requestOptions.push({ key: 'responseValidator', - value: responseValidator, + value: validators.response, }); } diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/validator.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/validator.ts index 26dd56784..64627d145 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/validator.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/shared/validator.ts @@ -4,39 +4,39 @@ import type { IR } from '~/ir/types'; import type { HeyApiSdkPlugin } from '../types'; -interface ValidatorProps { - operation: IR.OperationObject; - plugin: HeyApiSdkPlugin['Instance']; -} - -export const createRequestValidator = ({ - operation, - plugin, -}: ValidatorProps): ts.ArrowFunction | undefined => { - if (!plugin.config.validator.request) return; - - const validator = plugin.getPluginOrThrow(plugin.config.validator.request); - if (!validator.api.createRequestValidator) return; - - return validator.api.createRequestValidator({ - operation, - // @ts-expect-error - plugin: validator, - }); +type Validators = { + request?: ts.ArrowFunction; + response?: ts.ArrowFunction; }; -export const createResponseValidator = ({ +export const createValidators = ({ operation, plugin, -}: ValidatorProps): ts.ArrowFunction | undefined => { - if (!plugin.config.validator.response) return; - - const validator = plugin.getPluginOrThrow(plugin.config.validator.response); - if (!validator.api.createResponseValidator) return; - - return validator.api.createResponseValidator({ - operation, - // @ts-expect-error - plugin: validator, - }); +}: { + operation: IR.OperationObject; + plugin: HeyApiSdkPlugin['Instance']; +}): Validators => { + const validators: Validators = {}; + const values = plugin.config.validator(operation); + if (values.request) { + const validator = plugin.getPluginOrThrow(values.request); + if (validator.api.createRequestValidator) { + validators.request = validator.api.createRequestValidator({ + operation, + // @ts-expect-error + plugin: validator, + }); + } + } + if (values.response) { + const validator = plugin.getPluginOrThrow(values.response); + if (validator.api.createResponseValidator) { + validators.response = validator.api.createResponseValidator({ + operation, + // @ts-expect-error + plugin: validator, + }); + } + } + return validators; }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts index ca21b3627..312b0ea96 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts @@ -5,6 +5,34 @@ import type { StringName } from '~/types/case'; import type { IApi } from './api'; +type ValidatorValue = PluginValidatorNames | boolean; +type ValidatorResult = + | ValidatorValue + | { + /** + * Validate request data against schema before sending. + * + * Can be a validator plugin name or boolean (true to auto-select, false + * to disable). + * + * @default false + */ + request?: + | ValidatorValue + | ((operation: IR.OperationObject) => ValidatorValue); + /** + * Validate response data against schema before returning. + * + * Can be a validator plugin name or boolean (true to auto-select, false + * to disable). + * + * @default false + */ + response?: + | ValidatorValue + | ((operation: IR.OperationObject) => ValidatorValue); + }; + export type UserConfig = Plugin.Name<'@hey-api/sdk'> & Plugin.Hooks & { /** @@ -137,50 +165,18 @@ export type UserConfig = Plugin.Name<'@hey-api/sdk'> & * @default false */ validator?: - | PluginValidatorNames - | boolean - | { - /** - * Validate request data against schema before sending. - * - * Can be a validator plugin name or boolean (true to auto-select, false - * to disable). - * - * @default false - */ - request?: PluginValidatorNames | boolean; - /** - * Validate response data against schema before returning. - * - * Can be a validator plugin name or boolean (true to auto-select, false - * to disable). - * - * @default false - */ - response?: PluginValidatorNames | boolean; - }; + | ValidatorResult + | ((operation: IR.OperationObject) => ValidatorResult); // DEPRECATED OPTIONS BELOW - /** - * **This feature works only with the legacy parser** - * - * Filter endpoints to be included in the generated SDK. The provided - * string should be a regular expression where matched results will be - * included in the output. The input pattern this string will be tested - * against is `{method} {path}`. For example, you can match - * `POST /api/v1/foo` with `^POST /api/v1/foo$`. - * - * @deprecated - */ - // eslint-disable-next-line typescript-sort-keys/interface - filter?: string; /** * Define shape of returned value from service calls * * @deprecated * @default 'body' */ + // eslint-disable-next-line typescript-sort-keys/interface response?: 'body' | 'response'; }; @@ -303,7 +299,7 @@ export type Config = Plugin.Name<'@hey-api/sdk'> & * to a desired shape. However, validation adds runtime overhead, so it's * not recommended to use unless absolutely necessary. */ - validator: { + validator: (operation: IR.OperationObject) => { /** * The validator plugin to use for request validation, or false to disable. * @@ -320,25 +316,13 @@ export type Config = Plugin.Name<'@hey-api/sdk'> & // DEPRECATED OPTIONS BELOW - /** - * **This feature works only with the legacy parser** - * - * Filter endpoints to be included in the generated SDK. The provided - * string should be a regular expression where matched results will be - * included in the output. The input pattern this string will be tested - * against is `{method} {path}`. For example, you can match - * `POST /api/v1/foo` with `^POST /api/v1/foo$`. - * - * @deprecated - */ - // eslint-disable-next-line typescript-sort-keys/interface - filter?: string; /** * Define shape of returned value from service calls * * @deprecated * @default 'body' */ + // eslint-disable-next-line typescript-sort-keys/interface response: 'body' | 'response'; };