diff --git a/packages/openapi-ts/src/ir/operation.ts b/packages/openapi-ts/src/ir/operation.ts index 2e7cda065..de9fbd8d7 100644 --- a/packages/openapi-ts/src/ir/operation.ts +++ b/packages/openapi-ts/src/ir/operation.ts @@ -145,9 +145,13 @@ export const operationResponsesMap = ( // store default response to be evaluated last let defaultResponse: IR.ResponseObject | undefined; + const errorVariants: IR.SchemaObject[] = []; + + for (const name in operation.responses) { const response = operation.responses[name]!; + switch (statusCodeToGroup({ statusCode: name })) { case '1XX': case '3XX': @@ -159,6 +163,23 @@ export const operationResponsesMap = ( case '4XX': case '5XX': errors.properties[name] = response.schema; + + // TODO cleanup + const statusCode = Number(name); + + const variant: IR.SchemaObject = { + type: 'object', + properties: { + status: { + type: 'integer', + const: statusCode, + }, + error: response.schema, + }, + required: ['status', 'error'], + }; + + errorVariants.push(variant); break; case 'default': defaultResponse = response; @@ -223,6 +244,20 @@ export const operationResponsesMap = ( if (Object.keys(errorUnion).length && errorUnion.type !== 'unknown') { result.error = errorUnion; } + + + if (errorVariants.length) { + + let errorsUnion = addItemsToSchema({ + items: errorVariants, + mutateSchemaOneItem: true, + schema: {}, + }); + errorsUnion = deduplicateSchema({ schema: errorsUnion }); + + // TODO keep it backwards compatible, do not break existing errors type + result.errors = errorsUnion; + } } const responseKeys = Object.keys(responses.properties); diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/index.ts b/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/index.ts index fe6fa9429..5f572888d 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/index.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/index.ts @@ -17,5 +17,6 @@ export type { RequestOptions, RequestResult, TDataShape, + AxiosErrorWithTypedStatus, } from './types'; export { createConfig } from './utils'; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/types.ts b/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/types.ts index 87f4b0095..bc8fcb447 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/types.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/types.ts @@ -92,6 +92,14 @@ export interface ClientOptions { throwOnError?: boolean; } +export type AxiosErrorWithTypedStatus = + U extends { error: infer E; status: infer S extends number } + ? AxiosError & { + response: AxiosResponse & { status: S }; + status?: S; + } + : never; + export type RequestResult< TData = unknown, TError = unknown, @@ -106,7 +114,7 @@ export type RequestResult< | (AxiosResponse< TData extends Record ? TData[keyof TData] : TData > & { error: undefined }) - | (AxiosError< + | (AxiosErrorWithTypedStatus< TError extends Record ? TError[keyof TError] : TError > & { data: undefined; diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts index 8dec4b950..147c3faeb 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/shared/operation.ts @@ -162,6 +162,7 @@ export const operationToType = ({ const { error, errors, response, responses } = operationResponsesMap(operation); + // TODO here if (errors) { const symbolErrors = plugin.registerSymbol({ exported: true, @@ -217,9 +218,7 @@ export const operationToType = ({ .alias(symbol.placeholder) .export(symbol.exported) .type( - $.type(symbolErrors.placeholder).idx( - $.type(symbolErrors.placeholder).keyof(), - ), + $.type(symbolErrors.placeholder).idx($.type.literal("error")), ); plugin.setSymbolValue(symbol, node); } diff --git a/packages/openapi-ts/src/plugins/@pinia/colada/useType.ts b/packages/openapi-ts/src/plugins/@pinia/colada/useType.ts index a0215d091..9b1558be5 100644 --- a/packages/openapi-ts/src/plugins/@pinia/colada/useType.ts +++ b/packages/openapi-ts/src/plugins/@pinia/colada/useType.ts @@ -29,7 +29,7 @@ export const useTypeError = ({ category: 'type', resource: 'operation', resourceId: operation.id, - role: 'error', + role: 'errors', }); let typeErrorName: string | undefined = symbolErrorType?.placeholder; @@ -39,7 +39,7 @@ export const useTypeError = ({ if (client.name === '@hey-api/client-axios') { const symbol = plugin.referenceSymbol({ category: 'external', - resource: 'axios.AxiosError', + resource: 'client', }); typeErrorName = `${symbol.placeholder}<${typeErrorName}>`; } diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/shared/useType.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/shared/useType.ts index 7fce33fe5..f758893da 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/shared/useType.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/shared/useType.ts @@ -25,15 +25,17 @@ export const useTypeError = ({ }): string => { const client = getClientPlugin(plugin.context.config); + // TODO use the errors type here const symbolErrorType = plugin.querySymbol({ category: 'type', resource: 'operation', resourceId: operation.id, - role: 'error', + role: 'errors', }); let typeErrorName: string | undefined = symbolErrorType?.placeholder; if (!typeErrorName) { + // TODO fix the default error const symbol = plugin.referenceSymbol({ category: 'external', resource: `${plugin.name}.DefaultError`, @@ -43,7 +45,7 @@ export const useTypeError = ({ if (client.name === '@hey-api/client-axios') { const symbol = plugin.referenceSymbol({ category: 'external', - resource: 'axios.AxiosError', + resource: 'AxiosErrorWithTypedStatus', }); typeErrorName = `${symbol.placeholder}<${typeErrorName}>`; } diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/plugin.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/plugin.ts index 4dbd74c71..7e1533499 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/plugin.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/plugin.ts @@ -66,14 +66,15 @@ export const handlerV5: PluginHandler = ({ plugin }) => { }, name: 'useQuery', }); + // TODO what is the proper way? plugin.registerSymbol({ - external: 'axios', + external: '../client/types.gen', kind: 'type', meta: { category: 'external', - resource: 'axios.AxiosError', + resource: 'AxiosErrorWithTypedStatus', }, - name: 'AxiosError', + name: 'AxiosErrorWithTypedStatus', }); const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk');