diff --git a/dev/python/plugins.ts b/dev/python/plugins.ts index 7e931cb9c..50696e7ec 100644 --- a/dev/python/plugins.ts +++ b/dev/python/plugins.ts @@ -10,3 +10,12 @@ export function sdk( ...options, }; } + +export function pydantic( + options?: Partial, 'name'>>, +) { + return { + name: 'pydantic' as const, + ...options, + }; +} diff --git a/dev/python/presets.ts b/dev/python/presets.ts index 9e720f1b4..6d9062ac3 100644 --- a/dev/python/presets.ts +++ b/dev/python/presets.ts @@ -1,4 +1,4 @@ -import { sdk } from './plugins'; +import { pydantic, sdk } from './plugins'; export const presets = { sdk: () => [ @@ -14,6 +14,11 @@ export const presets = { }, }), ], + validated: () => [ + /** SDK + Pydantic validation */ + sdk(), + pydantic(), + ], } as const; export type PresetKey = keyof typeof presets; diff --git a/packages/openapi-python/src/index.ts b/packages/openapi-python/src/index.ts index 6345f686f..2cf1d65ae 100644 --- a/packages/openapi-python/src/index.ts +++ b/packages/openapi-python/src/index.ts @@ -41,16 +41,7 @@ declare module '@hey-api/codegen-core' { * Tags associated with this symbol. */ tags?: ReadonlyArray; - tool?: - | 'angular' - | 'arktype' - | 'fastify' - | 'json-schema' - | 'sdk' - | 'typescript' - | 'valibot' - | 'zod' - | AnyString; + tool?: 'pydantic' | 'sdk' | AnyString; variant?: 'container' | AnyString; } } @@ -59,6 +50,7 @@ declare module '@hey-api/shared' { interface PluginConfigMap { '@hey-api/client-httpx': HeyApiClientHttpxPlugin['Types']; '@hey-api/python-sdk': HeyApiSdkPlugin['Types']; + pydantic: PydanticPlugin['Types']; } } // END OVERRIDES @@ -71,6 +63,7 @@ import colorSupport from 'color-support'; import type { UserConfig } from './config/types'; import type { HeyApiClientHttpxPlugin } from './plugins/@hey-api/client-httpx'; import type { HeyApiSdkPlugin } from './plugins/@hey-api/sdk'; +import type { PydanticPlugin } from './plugins/pydantic'; colors.enabled = colorSupport().hasBasic; @@ -111,3 +104,10 @@ export { OperationStrategy, utils, } from '@hey-api/shared'; + +// Pydantic plugin +export type { PydanticPlugin } from './plugins/pydantic'; +export { + defaultConfig as defaultPydanticConfig, + defineConfig as definePydanticConfig, +} from './plugins/pydantic'; diff --git a/packages/openapi-python/src/plugins/config.ts b/packages/openapi-python/src/plugins/config.ts index 9910b6266..751b1a7d9 100644 --- a/packages/openapi-python/src/plugins/config.ts +++ b/packages/openapi-python/src/plugins/config.ts @@ -2,10 +2,12 @@ import type { Plugin, PluginConfigMap, PluginNames } from '@hey-api/shared'; import { defaultConfig as heyApiClientHttpx } from '../plugins/@hey-api/client-httpx'; import { defaultConfig as heyApiSdk } from '../plugins/@hey-api/sdk'; +import { defaultConfig as pydantic } from '../plugins/pydantic'; export const defaultPluginConfigs: { [K in PluginNames]: Plugin.Config; } = { '@hey-api/client-httpx': heyApiClientHttpx, '@hey-api/python-sdk': heyApiSdk, + pydantic, }; diff --git a/packages/openapi-python/src/plugins/pydantic/config.ts b/packages/openapi-python/src/plugins/pydantic/config.ts new file mode 100644 index 000000000..bf2668ac5 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/config.ts @@ -0,0 +1,62 @@ +import { definePluginConfig, mappers } from '@hey-api/shared'; + +import { handler } from './plugin'; +import type { PydanticPlugin } from './types'; + +export const defaultConfig: PydanticPlugin['Config'] = { + config: { + case: 'PascalCase', + comments: true, + includeInEntry: false, + strict: false, + }, + handler, + name: 'pydantic', + resolveConfig: (plugin, context) => { + plugin.config.definitions = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'PascalCase', + enabled: true, + name: '{{name}}', + }, + mappers, + value: plugin.config.definitions, + }); + + plugin.config.requests = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'PascalCase', + enabled: true, + name: '{{name}}Request', + }, + mappers, + value: plugin.config.requests, + }); + + plugin.config.responses = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'PascalCase', + enabled: true, + name: '{{name}}Response', + }, + mappers, + value: plugin.config.responses, + }); + + plugin.config.webhooks = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'PascalCase', + enabled: true, + name: '{{name}}Webhook', + }, + mappers, + value: plugin.config.webhooks, + }); + }, + tags: ['validator'], +}; + +/** + * Type helper for Pydantic plugin, returns {@link Plugin.Config} object + */ +export const defineConfig = definePluginConfig(defaultConfig); diff --git a/packages/openapi-python/src/plugins/pydantic/index.ts b/packages/openapi-python/src/plugins/pydantic/index.ts new file mode 100644 index 000000000..f05eaac4c --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/index.ts @@ -0,0 +1,2 @@ +export { defaultConfig, defineConfig } from './config'; +export type { PydanticPlugin } from './types'; diff --git a/packages/openapi-python/src/plugins/pydantic/plugin.ts b/packages/openapi-python/src/plugins/pydantic/plugin.ts new file mode 100644 index 000000000..44dbd6b37 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/plugin.ts @@ -0,0 +1,4 @@ +import type { PydanticPlugin } from './types'; +import { handlerV2 } from './v2/plugin'; + +export const handler: PydanticPlugin['Handler'] = (args) => handlerV2(args); diff --git a/packages/openapi-python/src/plugins/pydantic/shared/export.ts b/packages/openapi-python/src/plugins/pydantic/shared/export.ts new file mode 100644 index 000000000..1271ef854 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/shared/export.ts @@ -0,0 +1,71 @@ +import type { Symbol } from '@hey-api/codegen-core'; +import type { IR } from '@hey-api/shared'; + +// import { createSchemaComment } from '../../../plugins/shared/utils/schema'; +import { $ } from '../../../py-dsl'; +// import { identifiers } from '../v2/constants'; +// import { pipesToNode } from './pipes'; +import type { Ast, IrSchemaToAstOptions } from './types'; + +export function exportAst({ + // ast, + plugin, + // schema, + // state, + symbol, +}: IrSchemaToAstOptions & { + ast: Ast; + schema: IR.SchemaObject; + symbol: Symbol; +}): void { + // const v = plugin.external('valibot.v'); + const classDef = $.class(symbol); + // .export() + // .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v)) + // .$if(state.hasLazyExpression['~ref'], (c) => + // c.type($.type(v).attr(ast.typeName || identifiers.types.GenericSchema)), + // ) + // .assign(pipesToNode(ast.pipes, plugin)); + plugin.node(classDef); + // if (schema.type === 'object' && schema.properties) { + // const baseModelSymbol = plugin.external('pydantic.BaseModel'); + // const fieldSymbol = plugin.external('pydantic.Field'); + // const classDef = $.class(symbol).extends(baseModelSymbol); + + // if (plugin.config.comments && schema.description) { + // classDef.doc(schema.description); + // } + + // for (const name in schema.properties) { + // const property = schema.properties[name]!; + // const isOptional = !schema.required?.includes(name); + + // const propertyAst = irSchemaToAst({ + // optional: isOptional, + // plugin, + // schema: property, + // state: { + // ...state, + // path: ref([...fromRef(state.path), 'properties', name]), + // }, + // }); + + // let typeAnnotation = propertyAst.typeAnnotation; + + // if (isOptional && !typeAnnotation.startsWith('Optional[')) { + // typeAnnotation = `Optional[${typeAnnotation}]`; + // } + + // if (propertyAst.fieldConstraints && Object.keys(propertyAst.fieldConstraints).length > 0) { + // const constraints = Object.entries(propertyAst.fieldConstraints) + // .map(([key, value]) => `${key}=${JSON.stringify(value)}`) + // .join(', '); + // classDef.do($.stmt($.expr(`${name}: ${typeAnnotation} = Field(${constraints})`))); + // } else { + // classDef.do($.stmt($.expr(`${name}: ${typeAnnotation}`))); + // } + // } + + // plugin.node(classDef); + // } +} diff --git a/packages/openapi-python/src/plugins/pydantic/shared/index.ts b/packages/openapi-python/src/plugins/pydantic/shared/index.ts new file mode 100644 index 000000000..9709d871c --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/shared/index.ts @@ -0,0 +1 @@ +export type { Ast, IrSchemaToAstOptions, Pipes, PluginState, ResolverContext } from './types'; diff --git a/packages/openapi-python/src/plugins/pydantic/shared/types.ts b/packages/openapi-python/src/plugins/pydantic/shared/types.ts new file mode 100644 index 000000000..424292a63 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/shared/types.ts @@ -0,0 +1,80 @@ +import type { Refs, SymbolMeta } from '@hey-api/codegen-core'; +import type { IR } from '@hey-api/shared'; + +import type { PydanticPlugin } from '../types'; + +/** + * Shared types for Pydantic plugin + */ + +export type PluginState = Pick, 'path'> & + Pick, 'tags'> & { + hasLazyExpression: boolean; + }; + +/** + * AST node representation for Pydantic models + */ +export interface Ast { + /** + * Expression node for the type + */ + expression: unknown; + /** + * Field constraints for pydantic.Field() + */ + fieldConstraints?: Record; + /** + * Whether this AST node has a lazy expression (forward reference) + */ + hasLazyExpression?: boolean; + /** + * Pipes/chains for building the field definition (similar to Valibot pipes) + */ + pipes?: Pipes; + /** + * Type annotation for the field + */ + typeAnnotation: string; + /** + * Type name for the model class + */ + typeName?: string; +} + +/** + * Pipe system for building field constraints (similar to Valibot pattern) + */ +export type Pipes = Array; + +/** + * Options for converting IR schema to AST + */ +export interface IrSchemaToAstOptions { + /** + * The plugin instance + */ + plugin: PydanticPlugin['Instance']; + /** + * Current plugin state + */ + state: Refs; +} + +/** + * Context for type resolver functions + */ +export interface ResolverContext { + /** + * Field constraints being built + */ + constraints: Record; + /** + * The plugin instance + */ + plugin: PydanticPlugin['Instance']; + /** + * IR schema being processed + */ + schema: IR.SchemaObject; +} diff --git a/packages/openapi-python/src/plugins/pydantic/types.ts b/packages/openapi-python/src/plugins/pydantic/types.ts new file mode 100644 index 000000000..12f271b8d --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/types.ts @@ -0,0 +1,194 @@ +import type { + Casing, + DefinePlugin, + FeatureToggle, + NameTransformer, + NamingOptions, + Plugin, +} from '@hey-api/shared'; + +export type UserConfig = Plugin.Name<'pydantic'> & + Plugin.Hooks & + Plugin.UserComments & + Plugin.UserExports & { + /** + * Casing convention for generated names. + * + * @default 'PascalCase' + */ + case?: Casing; + /** + * Configuration for reusable schema definitions. + * + * Controls generation of shared Pydantic models that can be referenced + * across requests and responses. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string` or `function`: Shorthand for `{ name: string | function }` + * - `object`: Full configuration object + * + * @default true + */ + definitions?: + | boolean + | NameTransformer + | { + /** + * Casing convention for generated names. + * + * @default 'PascalCase' + */ + case?: Casing; + /** + * Whether this feature is enabled. + * + * @default true + */ + enabled?: boolean; + /** + * Naming pattern for generated names. + * + * @default '{{name}}' + */ + name?: NameTransformer; + }; + /** + * Configuration for request-specific Pydantic models. + * + * Controls generation of Pydantic models for request bodies, + * query parameters, path parameters, and headers. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string` or `function`: Shorthand for `{ name: string | function }` + * - `object`: Full configuration object + * + * @default true + */ + requests?: + | boolean + | NameTransformer + | { + /** + * Casing convention for generated names. + * + * @default 'PascalCase' + */ + case?: Casing; + /** + * Whether this feature is enabled. + * + * @default true + */ + enabled?: boolean; + /** + * Naming pattern for generated names. + * + * @default '{{name}}Request' + */ + name?: NameTransformer; + }; + /** + * Configuration for response-specific Pydantic models. + * + * Controls generation of Pydantic models for response bodies, + * error responses, and status codes. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string` or `function`: Shorthand for `{ name: string | function }` + * - `object`: Full configuration object + * + * @default true + */ + responses?: + | boolean + | NameTransformer + | { + /** + * Casing convention for generated names. + * + * @default 'PascalCase' + */ + case?: Casing; + /** + * Whether this feature is enabled. + * + * @default true + */ + enabled?: boolean; + /** + * Naming pattern for generated names. + * + * @default '{{name}}Response' + */ + name?: NameTransformer; + }; + /** + * Enable strict mode for Pydantic models? + * + * When enabled, extra fields not defined in the schema will be rejected. + * + * This adds `model_config = ConfigDict(extra='forbid')` + * to generated models. + * + * @default false + */ + strict?: boolean; + /** + * Configuration for webhook-specific Pydantic models. + * + * Controls generation of Pydantic models for webhook payloads. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string` or `function`: Shorthand for `{ name: string | function }` + * - `object`: Full configuration object + * + * @default true + */ + webhooks?: + | boolean + | NameTransformer + | { + /** + * Casing convention for generated names. + * + * @default 'PascalCase' + */ + case?: Casing; + /** + * Whether this feature is enabled. + * + * @default true + */ + enabled?: boolean; + /** + * Naming pattern for generated names. + * + * @default '{{name}}Webhook' + */ + name?: NameTransformer; + }; + }; + +export type Config = Plugin.Name<'pydantic'> & + Plugin.Hooks & + Plugin.Comments & + Plugin.Exports & { + /** Casing convention for generated names. */ + case: Casing; + /** Configuration for reusable schema definitions. */ + definitions: NamingOptions & FeatureToggle; + /** Configuration for request-specific Pydantic models. */ + requests: NamingOptions & FeatureToggle; + /** Configuration for response-specific Pydantic models. */ + responses: NamingOptions & FeatureToggle; + /** Enable strict mode for Pydantic models? */ + strict: boolean; + /** Configuration for webhook-specific Pydantic models. */ + webhooks: NamingOptions & FeatureToggle; + }; + +export type PydanticPlugin = DefinePlugin; diff --git a/packages/openapi-python/src/plugins/pydantic/v2/constants.ts b/packages/openapi-python/src/plugins/pydantic/v2/constants.ts new file mode 100644 index 000000000..3fb04de75 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/v2/constants.ts @@ -0,0 +1,44 @@ +export const identifiers = { + Annotated: 'Annotated', + Any: 'Any', + BaseModel: 'BaseModel', + ConfigDict: 'ConfigDict', + Dict: 'Dict', + Field: 'Field', + List: 'List', + Literal: 'Literal', + Optional: 'Optional', + Union: 'Union', + alias: 'alias', + default: 'default', + description: 'description', + ge: 'ge', + gt: 'gt', + le: 'le', + lt: 'lt', + max_length: 'max_length', + min_length: 'min_length', + model_config: 'model_config', + multiple_of: 'multiple_of', + pattern: 'pattern', +} as const; + +export const typeMappings: Record = { + array: 'list', + boolean: 'bool', + integer: 'int', + null: 'None', + number: 'float', + object: 'dict', + string: 'str', +}; + +export const pydanticTypes = { + array: 'list', + boolean: 'bool', + integer: 'int', + null: 'None', + number: 'float', + object: 'dict', + string: 'str', +} as const; diff --git a/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts b/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts new file mode 100644 index 000000000..6bd9d616f --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts @@ -0,0 +1,216 @@ +import type { SymbolMeta } from '@hey-api/codegen-core'; +import { fromRef, ref, refs } from '@hey-api/codegen-core'; +import type { IR, SchemaWithType } from '@hey-api/shared'; +import { applyNaming, deduplicateSchema, pathToJsonPointer, refToName } from '@hey-api/shared'; + +import { $ } from '../../../py-dsl'; +import { exportAst } from '../shared/export'; +import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types'; +import type { PydanticPlugin } from '../types'; +import { irSchemaWithTypeToAst } from './toAst'; + +export function irSchemaToAst({ + optional, + plugin, + schema, + state, +}: IrSchemaToAstOptions & { + optional?: boolean; + schema: IR.SchemaObject; +}): Ast { + if (schema.$ref) { + const query: SymbolMeta = { + category: 'schema', + resource: 'definition', + resourceId: schema.$ref, + tool: 'pydantic', + }; + const refSymbol = plugin.referenceSymbol(query); + const refName = typeof refSymbol === 'string' ? refSymbol : refSymbol.name; + + return { + expression: $.expr(refName), + fieldConstraints: optional ? { default: null } : undefined, + hasLazyExpression: !plugin.isSymbolRegistered(query), + pipes: [], + typeAnnotation: refName, + }; + } + + if (schema.type) { + const typeAst = irSchemaWithTypeToAst({ + plugin, + schema: schema as SchemaWithType, + state, + }); + + const constraints: Record = {}; + if (optional) { + constraints.default = null; + } + if (schema.default !== undefined) { + constraints.default = schema.default; + } + if (schema.description) { + constraints.description = schema.description; + } + + return { + ...typeAst, + fieldConstraints: { ...typeAst.fieldConstraints, ...constraints }, + pipes: [], + }; + } + + if (schema.items) { + schema = deduplicateSchema({ schema }); + + if (schema.items) { + const itemsAnnotations: string[] = []; + const itemsConstraints: Record[] = []; + + for (const item of schema.items) { + const itemAst = irSchemaToAst({ + plugin, + schema: item, + state: { + ...state, + path: ref([...fromRef(state.path), 'items']), + }, + }); + itemsAnnotations.push(itemAst.typeAnnotation); + if (itemAst.fieldConstraints) { + itemsConstraints.push(itemAst.fieldConstraints); + } + } + + const unionType = itemsAnnotations.join(' | '); + return { + expression: $.expr(`list[${unionType}]`), + fieldConstraints: itemsConstraints.length > 0 ? itemsConstraints[0] : undefined, + hasLazyExpression: false, + pipes: [], + typeAnnotation: `list[${unionType}]`, + }; + } + } + + return { + expression: $.expr('Any'), + hasLazyExpression: false, + pipes: [], + typeAnnotation: 'Any', + }; +} + +function handleComponent({ + plugin, + schema, + state, +}: IrSchemaToAstOptions & { + schema: IR.SchemaObject; +}): void { + const $ref = pathToJsonPointer(fromRef(state.path)); + const ast = irSchemaToAst({ plugin, schema, state }); + const baseName = refToName($ref); + const symbol = plugin.symbol(applyNaming(baseName, plugin.config.definitions), { + meta: { + category: 'schema', + path: fromRef(state.path), + resource: 'definition', + resourceId: $ref, + tags: fromRef(state.tags), + tool: 'pydantic', + }, + }); + exportAst({ + ast, + plugin, + schema, + state, + symbol, + }); +} + +export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => { + plugin.symbol('Any', { + external: 'typing', + importKind: 'named', + meta: { + category: 'external', + resource: 'typing.Any', + }, + }); + plugin.symbol('BaseModel', { + external: 'pydantic', + importKind: 'named', + meta: { + category: 'external', + resource: 'pydantic.BaseModel', + }, + }); + plugin.symbol('ConfigDict', { + external: 'pydantic', + importKind: 'named', + meta: { + category: 'external', + resource: 'pydantic.ConfigDict', + }, + }); + plugin.symbol('Field', { + external: 'pydantic', + importKind: 'named', + meta: { + category: 'external', + resource: 'pydantic.Field', + }, + }); + plugin.symbol('Literal', { + external: 'typing', + importKind: 'named', + meta: { + category: 'external', + resource: 'typing.Literal', + }, + }); + plugin.symbol('Optional', { + external: 'typing', + importKind: 'named', + meta: { + category: 'external', + resource: 'typing.Optional', + }, + }); + + plugin.forEach('operation', 'parameter', 'requestBody', 'schema', 'webhook', (event) => { + const state = refs({ + hasLazyExpression: false, + path: event._path, + tags: event.tags, + }); + + switch (event.type) { + case 'parameter': + handleComponent({ + plugin, + schema: event.parameter.schema, + state, + }); + break; + case 'requestBody': + handleComponent({ + plugin, + schema: event.requestBody.schema, + state, + }); + break; + case 'schema': + handleComponent({ + plugin, + schema: event.schema, + state, + }); + break; + } + }); +}; diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/index.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/index.ts new file mode 100644 index 000000000..180ec9ad6 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/index.ts @@ -0,0 +1,30 @@ +import type { SchemaWithType } from '@hey-api/shared'; + +import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; +import { objectToAst } from './object'; +import { stringToNode } from './string'; + +export function irSchemaWithTypeToAst({ + schema, + ...args +}: IrSchemaToAstOptions & { + schema: SchemaWithType; +}): Ast { + switch (schema.type) { + case 'object': + return objectToAst({ + ...args, + schema: schema as SchemaWithType<'object'>, + }); + case 'string': + return stringToNode({ + ...args, + schema: schema as SchemaWithType<'string'>, + }); + default: + return { + expression: 'Any', + typeAnnotation: 'Any', + }; + } +} diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts new file mode 100644 index 000000000..cc152eefe --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/object.ts @@ -0,0 +1,61 @@ +// import { fromRef, ref } from '@hey-api/codegen-core'; +import type { SchemaWithType } from '@hey-api/shared'; + +import { $ } from '../../../../py-dsl'; +import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; +// import { irSchemaToAst } from '../plugin'; + +export const objectToAst = ({ + plugin, + // schema, + // state, +}: IrSchemaToAstOptions & { + schema: SchemaWithType<'object'>; +}): Ast => { + const symbolBaseModel = plugin.external('pydantic.BaseModel'); + // const fieldSymbol = plugin.external('pydantic.Field'); + const symbolTemp = plugin.symbol('temp'); + + const classDef = $.class(symbolTemp).extends(symbolBaseModel); + + // if (schema.properties) { + // for (const name in schema.properties) { + // const property = schema.properties[name]!; + // const isOptional = !schema.required?.includes(name); + + // const propertyAst = irSchemaToAst({ + // optional: isOptional, + // plugin, + // schema: property, + // state: { + // ...state, + // path: ref([...fromRef(state.path), 'properties', name]), + // }, + // }); + + // let typeAnnotation = propertyAst.typeAnnotation; + + // if (isOptional && !typeAnnotation.startsWith('Optional[')) { + // typeAnnotation = `Optional[${typeAnnotation}]`; + // } + + // if (propertyAst.fieldConstraints && Object.keys(propertyAst.fieldConstraints).length > 0) { + // const constraints = Object.entries(propertyAst.fieldConstraints) + // .map(([key, value]) => `${key}=${JSON.stringify(value)}`) + // .join(', '); + // classDef.do($.expr(`${name}: ${typeAnnotation} = Field(${constraints})`)); + // } else { + // classDef.do($.expr(`${name}: ${typeAnnotation}`)); + // } + // } + // } + + return { + expression: classDef, + fieldConstraints: {}, + hasLazyExpression: false, + pipes: [], + typeAnnotation: 'DynamicModel', + typeName: 'DynamicModel', + }; +}; diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts new file mode 100644 index 000000000..c619f1326 --- /dev/null +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/string.ts @@ -0,0 +1,46 @@ +import type { SchemaWithType } from '@hey-api/shared'; + +import { $ } from '../../../../py-dsl'; +import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; + +export const stringToNode = ({ + schema, +}: IrSchemaToAstOptions & { + schema: SchemaWithType<'string'>; +}): Ast => { + const constraints: Record = {}; + + if (schema.minLength !== undefined) { + constraints.min_length = schema.minLength; + } + + if (schema.maxLength !== undefined) { + constraints.max_length = schema.maxLength; + } + + if (schema.pattern !== undefined) { + constraints.pattern = schema.pattern; + } + + if (schema.description !== undefined) { + constraints.description = schema.description; + } + + if (typeof schema.const === 'string') { + return { + expression: $.expr(`Literal["${schema.const}"]`), + fieldConstraints: constraints, + hasLazyExpression: false, + pipes: [], + typeAnnotation: `Literal["${schema.const}"]`, + }; + } + + return { + expression: $.expr('str'), + fieldConstraints: constraints, + hasLazyExpression: false, + pipes: [], + typeAnnotation: 'str', + }; +}; diff --git a/packages/openapi-ts/src/plugins/@faker-js/faker/config.ts b/packages/openapi-ts/src/plugins/@faker-js/faker/config.ts index 8c7576498..50ba6576a 100644 --- a/packages/openapi-ts/src/plugins/@faker-js/faker/config.ts +++ b/packages/openapi-ts/src/plugins/@faker-js/faker/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { Api } from './api'; // import { handler } from './plugin'; @@ -20,11 +20,7 @@ export const defaultConfig: FakerJsFakerPlugin['Config'] = { enabled: true, name: 'v{{name}}', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.definitions, }); }, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts index 177fb07d8..fa8bcbf9b 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts @@ -15,13 +15,13 @@ import { webhookToType } from '../shared/webhook'; import type { HeyApiTypeScriptPlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): MaybeTsDsl => { +}): MaybeTsDsl { if (schema.symbolRef) { const baseType = $.type(schema.symbolRef); if (schema.omit && schema.omit.length > 0) { @@ -79,15 +79,15 @@ export const irSchemaToAst = ({ }, state, }); -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}) => { +}) { const type = irSchemaToAst({ plugin, schema, state }); exportType({ plugin, @@ -95,7 +95,7 @@ const handleComponent = ({ state, type, }); -}; +} export const handlerV1: HeyApiTypeScriptPlugin['Handler'] = ({ plugin }) => { // reserve node for ClientOptions diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/index.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/index.ts index 566d8623d..716fc31e9 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/v1/toAst/index.ts @@ -15,12 +15,12 @@ import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; import { voidToAst } from './void'; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { schema: SchemaWithType; -}): MaybeTsDsl => { +}): MaybeTsDsl { const transformersPlugin = args.plugin.getPlugin('@hey-api/transformers'); if (transformersPlugin?.config.typeTransformers) { for (const typeTransformer of transformersPlugin.config.typeTransformers) { @@ -94,4 +94,4 @@ export const irSchemaWithTypeToAst = ({ schema: schema as SchemaWithType<'void'>, }); } -}; +} diff --git a/packages/openapi-ts/src/plugins/@pinia/colada/config.ts b/packages/openapi-ts/src/plugins/@pinia/colada/config.ts index 0b527f7a7..228700324 100644 --- a/packages/openapi-ts/src/plugins/@pinia/colada/config.ts +++ b/packages/openapi-ts/src/plugins/@pinia/colada/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from './plugin'; import type { PiniaColadaPlugin } from './types'; @@ -19,11 +19,7 @@ export const defaultConfig: PiniaColadaPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -34,11 +30,7 @@ export const defaultConfig: PiniaColadaPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -48,11 +40,7 @@ export const defaultConfig: PiniaColadaPlugin['Config'] = { enabled: true, name: '{{name}}Query', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); }, diff --git a/packages/openapi-ts/src/plugins/@tanstack/angular-query-experimental/config.ts b/packages/openapi-ts/src/plugins/@tanstack/angular-query-experimental/config.ts index 60b9efb71..dc6192803 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/angular-query-experimental/config.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/angular-query-experimental/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from '../../../plugins/@tanstack/query-core/plugin'; import type { TanStackAngularQueryPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: TanStackAngularQueryPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: TanStackAngularQueryPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: TanStackAngularQueryPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: TanStackAngularQueryPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: TanStackAngularQueryPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); }, diff --git a/packages/openapi-ts/src/plugins/@tanstack/react-query/config.ts b/packages/openapi-ts/src/plugins/@tanstack/react-query/config.ts index 08bfc3fb5..a79c4a864 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/react-query/config.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/react-query/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from '../../../plugins/@tanstack/query-core/plugin'; import type { TanStackReactQueryPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: TanStackReactQueryPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: TanStackReactQueryPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: TanStackReactQueryPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: TanStackReactQueryPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: TanStackReactQueryPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); diff --git a/packages/openapi-ts/src/plugins/@tanstack/solid-query/config.ts b/packages/openapi-ts/src/plugins/@tanstack/solid-query/config.ts index c68e89ebb..d1ce00c42 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/solid-query/config.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/solid-query/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from '../../../plugins/@tanstack/query-core/plugin'; import type { TanStackSolidQueryPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: TanStackSolidQueryPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: TanStackSolidQueryPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: TanStackSolidQueryPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: TanStackSolidQueryPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: TanStackSolidQueryPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); }, diff --git a/packages/openapi-ts/src/plugins/@tanstack/svelte-query/config.ts b/packages/openapi-ts/src/plugins/@tanstack/svelte-query/config.ts index 168403f68..2b43fd5df 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/svelte-query/config.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/svelte-query/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from '../../../plugins/@tanstack/query-core/plugin'; import type { TanStackSvelteQueryPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: TanStackSvelteQueryPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: TanStackSvelteQueryPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: TanStackSvelteQueryPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: TanStackSvelteQueryPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: TanStackSvelteQueryPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); }, diff --git a/packages/openapi-ts/src/plugins/@tanstack/vue-query/config.ts b/packages/openapi-ts/src/plugins/@tanstack/vue-query/config.ts index ff0b4d23f..783692710 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/vue-query/config.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/vue-query/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from '../../../plugins/@tanstack/query-core/plugin'; import type { TanStackVueQueryPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: TanStackVueQueryPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: TanStackVueQueryPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: TanStackVueQueryPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: TanStackVueQueryPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: TanStackVueQueryPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); }, diff --git a/packages/openapi-ts/src/plugins/arktype/shared/export.ts b/packages/openapi-ts/src/plugins/arktype/shared/export.ts index 2e14c1079..0dea20faa 100644 --- a/packages/openapi-ts/src/plugins/arktype/shared/export.ts +++ b/packages/openapi-ts/src/plugins/arktype/shared/export.ts @@ -7,7 +7,7 @@ import { identifiers } from '../constants'; import type { ArktypePlugin } from '../types'; import type { Ast } from './types'; -export const exportAst = ({ +export function exportAst({ ast, plugin, schema, @@ -19,7 +19,7 @@ export const exportAst = ({ schema: IR.SchemaObject; symbol: Symbol; typeInferSymbol: Symbol | undefined; -}): void => { +}): void { const type = plugin.external('arktype.type'); const statement = $.const(symbol) @@ -43,4 +43,4 @@ export const exportAst = ({ .type($.type(symbol).attr(identifiers.type.infer).typeofType()); plugin.node(inferType); } -}; +} diff --git a/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts b/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts index 5b8361216..165511dba 100644 --- a/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts +++ b/packages/openapi-ts/src/plugins/arktype/v2/plugin.ts @@ -9,7 +9,7 @@ import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types'; import type { ArktypePlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ // optional, plugin, schema, @@ -22,7 +22,7 @@ export const irSchemaToAst = ({ */ optional?: boolean; schema: IR.SchemaObject; -}): Ast => { +}): Ast { let ast: Partial = {}; // const z = plugin.referenceSymbol({ @@ -226,15 +226,15 @@ export const irSchemaToAst = ({ // } return ast as Ast; -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): void => { +}): void { const $ref = pathToJsonPointer(fromRef(state.path)); const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); @@ -267,7 +267,7 @@ const handleComponent = ({ symbol, typeInferSymbol, }); -}; +} export const handlerV2: ArktypePlugin['Handler'] = ({ plugin }) => { plugin.symbol('type', { diff --git a/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts b/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts index 06b235a92..ed62584bf 100644 --- a/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts @@ -15,12 +15,12 @@ import { stringToAst } from './string'; // import { unknownToAst } from "./unknown"; // import { voidToAst } from "./void"; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { schema: SchemaWithType; -}): Omit => { +}): Omit { switch (schema.type) { // case 'array': // return arrayToAst({ @@ -99,4 +99,4 @@ export const irSchemaWithTypeToAst = ({ expression, hasLazyExpression: false, }; -}; +} diff --git a/packages/openapi-ts/src/plugins/swr/config.ts b/packages/openapi-ts/src/plugins/swr/config.ts index 8ef25e9f5..2a9ea20b6 100644 --- a/packages/openapi-ts/src/plugins/swr/config.ts +++ b/packages/openapi-ts/src/plugins/swr/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { handler } from './plugin'; import type { SwrPlugin } from './types'; @@ -20,11 +20,7 @@ export const defaultConfig: SwrPlugin['Config'] = { name: '{{name}}InfiniteQueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryKeys, }); @@ -34,11 +30,7 @@ export const defaultConfig: SwrPlugin['Config'] = { enabled: true, name: '{{name}}InfiniteOptions', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.infiniteQueryOptions, }); @@ -48,11 +40,7 @@ export const defaultConfig: SwrPlugin['Config'] = { enabled: true, name: '{{name}}Mutation', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.mutationOptions, }); @@ -63,11 +51,7 @@ export const defaultConfig: SwrPlugin['Config'] = { name: '{{name}}QueryKey', tags: false, }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryKeys, }); @@ -78,11 +62,7 @@ export const defaultConfig: SwrPlugin['Config'] = { exported: true, name: '{{name}}Options', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.queryOptions, }); diff --git a/packages/openapi-ts/src/plugins/valibot/config.ts b/packages/openapi-ts/src/plugins/valibot/config.ts index 31d5406f5..8345ed878 100644 --- a/packages/openapi-ts/src/plugins/valibot/config.ts +++ b/packages/openapi-ts/src/plugins/valibot/config.ts @@ -1,4 +1,4 @@ -import { definePluginConfig } from '@hey-api/shared'; +import { definePluginConfig, mappers } from '@hey-api/shared'; import { Api } from './api'; import { handler } from './plugin'; @@ -21,11 +21,7 @@ export const defaultConfig: ValibotPlugin['Config'] = { enabled: true, name: 'v{{name}}', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.definitions, }); @@ -35,11 +31,7 @@ export const defaultConfig: ValibotPlugin['Config'] = { enabled: true, name: 'v{{name}}Data', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.requests, }); @@ -49,11 +41,7 @@ export const defaultConfig: ValibotPlugin['Config'] = { enabled: true, name: 'v{{name}}Response', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.responses, }); @@ -63,11 +51,7 @@ export const defaultConfig: ValibotPlugin['Config'] = { enabled: true, name: 'v{{name}}WebhookRequest', }, - mappers: { - boolean: (enabled) => ({ enabled }), - function: (name) => ({ name }), - string: (name) => ({ name }), - }, + mappers, value: plugin.config.webhooks, }); }, diff --git a/packages/openapi-ts/src/plugins/valibot/shared/export.ts b/packages/openapi-ts/src/plugins/valibot/shared/export.ts index 6aa2b6b50..d253e9b18 100644 --- a/packages/openapi-ts/src/plugins/valibot/shared/export.ts +++ b/packages/openapi-ts/src/plugins/valibot/shared/export.ts @@ -7,7 +7,7 @@ import { identifiers } from '../v1/constants'; import { pipesToNode } from './pipes'; import type { Ast, IrSchemaToAstOptions } from './types'; -export const exportAst = ({ +export function exportAst({ ast, plugin, schema, @@ -17,7 +17,7 @@ export const exportAst = ({ ast: Ast; schema: IR.SchemaObject; symbol: Symbol; -}): void => { +}): void { const v = plugin.external('valibot.v'); const statement = $.const(symbol) .export() @@ -27,4 +27,4 @@ export const exportAst = ({ ) .assign(pipesToNode(ast.pipes, plugin)); plugin.node(statement); -}; +} diff --git a/packages/openapi-ts/src/plugins/valibot/types.ts b/packages/openapi-ts/src/plugins/valibot/types.ts index 8202fde61..a9ac82cb8 100644 --- a/packages/openapi-ts/src/plugins/valibot/types.ts +++ b/packages/openapi-ts/src/plugins/valibot/types.ts @@ -31,6 +31,8 @@ export type UserConfig = Plugin.Name<'valibot'> & * - `boolean`: Shorthand for `{ enabled: boolean }` * - `string` or `function`: Shorthand for `{ name: string | function }` * - `object`: Full configuration object + * + * @default true */ definitions?: | boolean @@ -73,6 +75,8 @@ export type UserConfig = Plugin.Name<'valibot'> & * - `boolean`: Shorthand for `{ enabled: boolean }` * - `string` or `function`: Shorthand for `{ name: string | function }` * - `object`: Full configuration object + * + * @default true */ requests?: | boolean @@ -107,6 +111,8 @@ export type UserConfig = Plugin.Name<'valibot'> & * - `boolean`: Shorthand for `{ enabled: boolean }` * - `string` or `function`: Shorthand for `{ name: string | function }` * - `object`: Full configuration object + * + * @default true */ responses?: | boolean @@ -173,44 +179,17 @@ export type Config = Plugin.Name<'valibot'> & Plugin.Comments & Plugin.Exports & Resolvers & { - /** - * Casing convention for generated names. - */ + /** Casing convention for generated names. */ case: Casing; - /** - * Configuration for reusable schema definitions. - * - * Controls generation of shared Valibot schemas that can be referenced - * across requests and responses. - */ + /** Configuration for reusable schema definitions. */ definitions: NamingOptions & FeatureToggle; - /** - * Enable Valibot metadata support? It's often useful to associate a schema - * with some additional metadata for documentation, code generation, AI - * structured outputs, form validation, and other purposes. - * - * @default false - */ + /** Enable Valibot metadata support? */ metadata: boolean; - /** - * Configuration for request-specific Valibot schemas. - * - * Controls generation of Valibot schemas for request bodies, query - * parameters, path parameters, and headers. - */ + /** Configuration for request-specific Valibot schemas. */ requests: NamingOptions & FeatureToggle; - /** - * Configuration for response-specific Valibot schemas. - * - * Controls generation of Valibot schemas for response bodies, error - * responses, and status codes. - */ + /** Configuration for response-specific Valibot schemas. */ responses: NamingOptions & FeatureToggle; - /** - * Configuration for webhook-specific Valibot schemas. - * - * Controls generation of Valibot schemas for webhook payloads. - */ + /** Configuration for webhook-specific Valibot schemas. */ webhooks: NamingOptions & FeatureToggle; }; diff --git a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts index b5bac304a..2bb870223 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/plugin.ts @@ -14,7 +14,7 @@ import type { ValibotPlugin } from '../types'; import { identifiers } from './constants'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ optional, plugin, schema, @@ -27,7 +27,7 @@ export const irSchemaToAst = ({ */ optional?: boolean; schema: IR.SchemaObject; -}): Ast => { +}): Ast { const ast: Ast = { pipes: [], }; @@ -134,15 +134,15 @@ export const irSchemaToAst = ({ } return ast as Ast; -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): void => { +}): void { const $ref = pathToJsonPointer(fromRef(state.path)); const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); @@ -163,7 +163,7 @@ const handleComponent = ({ state, symbol, }); -}; +} export const handlerV1: ValibotPlugin['Handler'] = ({ plugin }) => { plugin.symbol('v', { 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 3dfcb8c4e..5ee4d3072 100644 --- a/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/valibot/v1/toAst/index.ts @@ -17,7 +17,7 @@ import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; import { voidToAst } from './void'; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { @@ -25,7 +25,7 @@ export const irSchemaWithTypeToAst = ({ }): { anyType?: string; expression: ReturnType; -} => { +} { switch (schema.type) { case 'array': return { @@ -127,4 +127,4 @@ export const irSchemaWithTypeToAst = ({ }), }; } -}; +} diff --git a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts index 4d383a983..d949f7f4a 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/plugin.ts @@ -14,7 +14,7 @@ import { irWebhookToAst } from '../shared/webhook'; import type { ZodPlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ optional, plugin, schema, @@ -27,7 +27,7 @@ export const irSchemaToAst = ({ */ optional?: boolean; schema: IR.SchemaObject; -}): Ast => { +}): Ast { let ast: Partial = {}; const z = plugin.external('zod.z'); @@ -153,15 +153,15 @@ export const irSchemaToAst = ({ } return ast as Ast; -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): void => { +}): void { const $ref = pathToJsonPointer(fromRef(state.path)); const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); @@ -195,7 +195,7 @@ const handleComponent = ({ symbol, typeInferSymbol, }); -}; +} export const handlerMini: ZodPlugin['Handler'] = ({ plugin }) => { plugin.symbol('z', { diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts index dc037e37c..f4962c0ee 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/index.ts @@ -15,12 +15,12 @@ import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; import { voidToAst } from './void'; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { schema: SchemaWithType; -}): Omit => { +}): Omit { switch (schema.type) { case 'array': return arrayToAst({ @@ -89,4 +89,4 @@ export const irSchemaWithTypeToAst = ({ schema: schema as SchemaWithType<'void'>, }); } -}; +} diff --git a/packages/openapi-ts/src/plugins/zod/shared/export.ts b/packages/openapi-ts/src/plugins/zod/shared/export.ts index b2481b42f..3fabac42b 100644 --- a/packages/openapi-ts/src/plugins/zod/shared/export.ts +++ b/packages/openapi-ts/src/plugins/zod/shared/export.ts @@ -7,7 +7,7 @@ import { identifiers } from '../constants'; import type { ZodPlugin } from '../types'; import type { Ast } from './types'; -export const exportAst = ({ +export function exportAst({ ast, plugin, schema, @@ -19,7 +19,7 @@ export const exportAst = ({ schema: IR.SchemaObject; symbol: Symbol; typeInferSymbol: Symbol | undefined; -}): void => { +}): void { const z = plugin.external('zod.z'); const statement = $.const(symbol) @@ -36,4 +36,4 @@ export const exportAst = ({ .type($.type(z).attr(identifiers.infer).generic($(symbol).typeofType())); plugin.node(inferType); } -}; +} diff --git a/packages/openapi-ts/src/plugins/zod/types.ts b/packages/openapi-ts/src/plugins/zod/types.ts index 7d2ca09b3..a679942f0 100644 --- a/packages/openapi-ts/src/plugins/zod/types.ts +++ b/packages/openapi-ts/src/plugins/zod/types.ts @@ -410,147 +410,60 @@ export type Config = Plugin.Name<'zod'> & Plugin.Comments & Plugin.Exports & Resolvers & { - /** - * Casing convention for generated names. - */ + /** Casing convention for generated names. */ case: Casing; - /** - * The compatibility version to target for generated output. - * - * Can be: - * - `4`: [Zod 4](https://zod.dev/packages/zod) (default). - * - `3`: [Zod 3](https://v3.zod.dev/). - * - `'mini'`: [Zod Mini](https://zod.dev/packages/mini). - * - * @default 4 - */ + /** The compatibility version to target for generated output. */ compatibilityVersion: 3 | 4 | 'mini'; - /** - * Configuration for date handling in generated Zod schemas. - * - * Controls how date values are processed and validated using Zod's - * date validation features. - */ + /** Configuration for date handling in generated Zod schemas. */ dates: { - /** - * Whether to allow unqualified (timezone-less) datetimes: - * - * When enabled, Zod will accept datetime strings without timezone information. - * When disabled, Zod will require timezone information in datetime strings. - * - * @default false - */ + /** Whether to allow unqualified (timezone-less) datetimes. */ local: boolean; - /** - * Whether to include timezone offset information when handling dates. - * - * When enabled, date strings will preserve timezone information. - * When disabled, dates will be treated as local time. - * - * @default false - */ + /** Whether to include timezone offset information when handling dates. */ offset: boolean; }; - /** - * Configuration for reusable schema definitions. - * - * Controls generation of shared Zod schemas that can be referenced across - * requests and responses. - */ + /** Configuration for reusable schema definitions. */ definitions: NamingOptions & FeatureToggle & { - /** - * Configuration for TypeScript type generation from Zod schemas. - * - * Controls generation of TypeScript types based on the generated Zod schemas. - */ + /** Configuration for TypeScript type generation from Zod schemas. */ types: { - /** - * Configuration for `infer` types. - */ + /** Configuration for `infer` types. */ infer: NamingOptions & FeatureToggle; }; }; - /** - * Enable Zod metadata support? It's often useful to associate a schema with - * some additional metadata for documentation, code generation, AI - * structured outputs, form validation, and other purposes. - * - * @default false - */ + /** Enable Zod metadata support? */ metadata: boolean; - /** - * Configuration for request-specific Zod schemas. - * - * Controls generation of Zod schemas for request bodies, query parameters, path - * parameters, and headers. - */ + /** Configuration for request-specific Zod schemas. */ requests: NamingOptions & FeatureToggle & { - /** - * Configuration for TypeScript type generation from Zod schemas. - * - * Controls generation of TypeScript types based on the generated Zod schemas. - */ + /** Configuration for TypeScript type generation from Zod schemas. */ types: { - /** - * Configuration for `infer` types. - */ + /** Configuration for `infer` types. */ infer: NamingOptions & FeatureToggle; }; }; - /** - * Configuration for response-specific Zod schemas. - * - * Controls generation of Zod schemas for response bodies, error responses, - * and status codes. - */ + /** Configuration for response-specific Zod schemas. */ responses: NamingOptions & FeatureToggle & { - /** - * Configuration for TypeScript type generation from Zod schemas. - * - * Controls generation of TypeScript types based on the generated Zod schemas. - */ + /** Configuration for TypeScript type generation from Zod schemas. */ types: { - /** - * Configuration for `infer` types. - */ + /** Configuration for `infer` types. */ infer: NamingOptions & FeatureToggle; }; }; - /** - * Configuration for TypeScript type generation from Zod schemas. - * - * Controls generation of TypeScript types based on the generated Zod schemas. - */ + /** Configuration for TypeScript type generation from Zod schemas. */ types: { - /** - * Configuration for `infer` types. - */ + /** Configuration for `infer` types. */ infer: FeatureToggle & { - /** - * Casing convention for generated names. - */ + /** Casing convention for generated names. */ case: Casing; }; }; - /** - * Configuration for webhook-specific Zod schemas. - * - * Controls generation of Zod schemas for webhook payloads. - */ + /** Configuration for webhook-specific Zod schemas. */ webhooks: NamingOptions & FeatureToggle & { - /** - * Configuration for TypeScript type generation from Zod schemas. - * - * Controls generation of TypeScript types based on the generated Zod schemas. - */ + /** Configuration for TypeScript type generation from Zod schemas. */ types: { - /** - * Configuration for `infer` types. - */ + /** Configuration for `infer` types. */ infer: NamingOptions & FeatureToggle; }; }; diff --git a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts index 55bdcd4fb..5d29f4e91 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/plugin.ts @@ -14,7 +14,7 @@ import { irWebhookToAst } from '../shared/webhook'; import type { ZodPlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ optional, plugin, schema, @@ -27,7 +27,7 @@ export const irSchemaToAst = ({ */ optional?: boolean; schema: IR.SchemaObject; -}): Ast => { +}): Ast { let ast: Partial = {}; const z = plugin.external('zod.z'); @@ -151,15 +151,15 @@ export const irSchemaToAst = ({ } return ast as Ast; -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): void => { +}): void { const $ref = pathToJsonPointer(fromRef(state.path)); const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); @@ -193,7 +193,7 @@ const handleComponent = ({ symbol, typeInferSymbol, }); -}; +} export const handlerV3: ZodPlugin['Handler'] = ({ plugin }) => { plugin.symbol('z', { diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts index 5adde1fd7..00a1b5fef 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/index.ts @@ -15,14 +15,14 @@ import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; import { voidToAst } from './void'; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { schema: SchemaWithType; }): Omit & { anyType?: string; -} => { +} { switch (schema.type) { case 'array': return arrayToAst({ @@ -109,4 +109,4 @@ export const irSchemaWithTypeToAst = ({ }), }; } -}; +} diff --git a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts index 294a967f6..db3ff16d1 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/plugin.ts @@ -14,7 +14,7 @@ import { irWebhookToAst } from '../shared/webhook'; import type { ZodPlugin } from '../types'; import { irSchemaWithTypeToAst } from './toAst'; -export const irSchemaToAst = ({ +export function irSchemaToAst({ optional, plugin, schema, @@ -27,7 +27,7 @@ export const irSchemaToAst = ({ */ optional?: boolean; schema: IR.SchemaObject; -}): Ast => { +}): Ast { let ast: Partial = {}; const z = plugin.external('zod.z'); @@ -155,15 +155,15 @@ export const irSchemaToAst = ({ } return ast as Ast; -}; +} -const handleComponent = ({ +function handleComponent({ plugin, schema, state, }: IrSchemaToAstOptions & { schema: IR.SchemaObject; -}): void => { +}): void { const $ref = pathToJsonPointer(fromRef(state.path)); const ast = irSchemaToAst({ plugin, schema, state }); const baseName = refToName($ref); @@ -197,7 +197,7 @@ const handleComponent = ({ symbol, typeInferSymbol, }); -}; +} export const handlerV4: ZodPlugin['Handler'] = ({ plugin }) => { plugin.symbol('z', { diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts index dc037e37c..f4962c0ee 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/index.ts @@ -15,12 +15,12 @@ import { undefinedToAst } from './undefined'; import { unknownToAst } from './unknown'; import { voidToAst } from './void'; -export const irSchemaWithTypeToAst = ({ +export function irSchemaWithTypeToAst({ schema, ...args }: IrSchemaToAstOptions & { schema: SchemaWithType; -}): Omit => { +}): Omit { switch (schema.type) { case 'array': return arrayToAst({ @@ -89,4 +89,4 @@ export const irSchemaWithTypeToAst = ({ schema: schema as SchemaWithType<'void'>, }); } -}; +} diff --git a/renovate.json b/renovate.json index 5d849cc3a..e273f3513 100644 --- a/renovate.json +++ b/renovate.json @@ -3,6 +3,13 @@ "dependencyDashboardTitle": "Dependencies 📦", "extends": ["config:recommended"], "minimumReleaseAge": "3 days", + "packageRules": [ + { + "matchPackageNames": ["zod"], + "matchRepositories": ["./packages/openapi-ts-tests/zod/v3"], + "allowedVersions": "<4.0.0" + } + ], "prConcurrentLimit": 4, "schedule": ["before 3am on Monday"], "updatePinnedDependencies": true