diff --git a/src/adapters/node-http/core.ts b/src/adapters/node-http/core.ts index 6982704a..aa04b980 100644 --- a/src/adapters/node-http/core.ts +++ b/src/adapters/node-http/core.ts @@ -173,6 +173,7 @@ export const createOpenApiNodeHttpHandler = < const statusCode = meta?.status ?? TRPC_ERROR_CODE_HTTP_STATUS[error.code] ?? 500; const headers = meta?.headers ?? {}; const body: OpenApiErrorResponse = { + ...errorShape, // Pass the error through message: isInputValidationError ? 'Input validation failed' : errorShape?.message ?? error.message ?? 'An error occurred', diff --git a/src/generator/schema.ts b/src/generator/schema.ts index 430dc38b..78f9e816 100644 --- a/src/generator/schema.ts +++ b/src/generator/schema.ts @@ -140,43 +140,54 @@ export const getRequestBodyObject = ( export const hasInputs = (schema: unknown) => instanceofZodType(schema) && !instanceofZodTypeLikeVoid(unwrapZodType(schema, true)); +const errorResponseObjectByCode: Record = {}; + export const errorResponseObject = ( - code?: string, + code = 'INTERNAL_SERVER_ERROR', message?: string, issues?: { message: string }[], -): ZodOpenApiResponseObject => ({ - description: message ?? 'An error response', - content: { - 'application/json': { - schema: z - .object({ - message: z.string().openapi({ - description: 'The error message', - example: message ?? 'Internal server error', - }), - code: z - .string() - .openapi({ description: 'The error code', example: code ?? 'INTERNAL_SERVER_ERROR' }), - issues: z - .array(z.object({ message: z.string() })) - .optional() +): ZodOpenApiResponseObject => { + if (!errorResponseObjectByCode[code]) { + errorResponseObjectByCode[code] = { + description: message ?? 'An error response', + content: { + 'application/json': { + schema: z + .object({ + message: z.string().openapi({ + description: 'The error message', + example: message ?? 'Internal server error', + }), + code: z + .string() + .openapi({ + description: 'The error code', + example: code ?? 'INTERNAL_SERVER_ERROR', + }), + issues: z + .array(z.object({ message: z.string() })) + .optional() + .openapi({ + description: 'An array of issues that were responsible for the error', + example: issues ?? [], + }), + }) .openapi({ - description: 'An array of issues that were responsible for the error', - example: issues ?? [], + title: 'Error', + description: 'The error information', + example: { + code: code ?? 'INTERNAL_SERVER_ERROR', + message: message ?? 'Internal server error', + issues: issues ?? [], + }, + ref: `error.${code}`, }), - }) - .openapi({ - title: 'Error', - description: 'The error information', - example: { - code: code ?? 'INTERNAL_SERVER_ERROR', - message: message ?? 'Internal server error', - issues: issues ?? [], - }, - }), - }, - }, -}); + }, + }, + }; + } + return errorResponseObjectByCode[code]!; +}; export const getResponsesObject = ( schema: ZodTypeAny, diff --git a/src/types.ts b/src/types.ts index 9a5e6599..a24b120f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { Procedure, ProcedureParams, Router } from '@trpc/server'; import type { RootConfig } from '@trpc/server/dist/core/internals/config'; -import { TRPC_ERROR_CODE_KEY } from '@trpc/server/rpc'; -import type { RouterDef } from '@trpc/server/src/core/router'; +import type { RouterDef } from '@trpc/server/dist/core/router'; +import { TRPC_ERROR_CODE_KEY } from '@trpc/server/dist/rpc'; import { AnyZodObject, ZodBigInt, diff --git a/src/utils/zod.ts b/src/utils/zod.ts index d4bb29cb..1422caba 100644 --- a/src/utils/zod.ts +++ b/src/utils/zod.ts @@ -32,6 +32,19 @@ export const instanceofZodTypeLikeVoid = (type: z.ZodTypeAny): type is ZodTypeLi }; export const unwrapZodType = (type: z.ZodTypeAny, unwrapPreprocess: boolean): z.ZodTypeAny => { + // TODO: Allow parsing array query params + // if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodArray)) { + // return unwrapZodType(type.element, unwrapPreprocess); + // } + if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodEnum)) { + return unwrapZodType(z.string(), unwrapPreprocess); + } + if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodNullable)) { + return unwrapZodType(type.unwrap(), unwrapPreprocess); + } + if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodBranded)) { + return unwrapZodType(type.unwrap(), unwrapPreprocess); + } if (instanceofZodTypeKind(type, z.ZodFirstPartyTypeKind.ZodOptional)) { return unwrapZodType(type.unwrap(), unwrapPreprocess); }