chore: better trpc error handling (#21948)

This commit is contained in:
Morgan
2025-06-21 11:56:23 +05:30
committed by GitHub
parent 2227b4ff91
commit ffe9d43d09
2 changed files with 108 additions and 71 deletions
+101 -71
View File
@@ -6,6 +6,100 @@ import { ERROR_STATUS } from "@calcom/platform-constants";
import { TRPCError } from "@calcom/platform-libraries";
import { Response } from "@calcom/platform-types";
export type TRPCErrorCode = TRPCError["code"];
export interface ErrorDefinition {
statusCode: number;
message: string;
}
// Define the specific TRPC error codes
export const TRPC_ERROR_CODE = {
UNAUTHORIZED: "UNAUTHORIZED",
FORBIDDEN: "FORBIDDEN",
NOT_FOUND: "NOT_FOUND",
INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
BAD_REQUEST: "BAD_REQUEST",
CONFLICT: "CONFLICT",
TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
CLIENT_CLOSED_REQUEST: "CLIENT_CLOSED_REQUEST",
METHOD_NOT_SUPPORTED: "METHOD_NOT_SUPPORTED",
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
PRECONDITION_FAILED: "PRECONDITION_FAILED",
TIMEOUT: "TIMEOUT",
UNPROCESSABLE_CONTENT: "UNPROCESSABLE_CONTENT",
PARSE_ERROR: "PARSE_ERROR",
} as const; // `as const` ensures literal types, improving type inference
// Map TRPC error codes to their HTTP status codes and default messages
export const TRPC_ERROR_MAP: Record<(typeof TRPC_ERROR_CODE)[keyof typeof TRPC_ERROR_CODE], ErrorDefinition> =
{
[TRPC_ERROR_CODE.UNAUTHORIZED]: {
statusCode: 401,
message: "You are not authorized to access this resource",
},
[TRPC_ERROR_CODE.FORBIDDEN]: {
statusCode: 403,
message: "You don't have necessary permissions to access this resource",
},
[TRPC_ERROR_CODE.NOT_FOUND]: {
statusCode: 404,
message: "The requested resource was not found",
},
[TRPC_ERROR_CODE.INTERNAL_SERVER_ERROR]: {
statusCode: 500,
message: "An unexpected error occurred on the server. Please try again later",
},
[TRPC_ERROR_CODE.BAD_REQUEST]: {
statusCode: 400,
message: "Bad request: Please check your request data and try again",
},
[TRPC_ERROR_CODE.CONFLICT]: {
statusCode: 409,
message: "Could not process the request due to a resource conflict with the current state",
},
[TRPC_ERROR_CODE.TOO_MANY_REQUESTS]: {
statusCode: 429,
message: "You have exceeded the allowed number of requests",
},
[TRPC_ERROR_CODE.PAYLOAD_TOO_LARGE]: {
statusCode: 413,
message: "The request payload is too large",
},
[TRPC_ERROR_CODE.CLIENT_CLOSED_REQUEST]: {
statusCode: 499,
message: "The client closed the connection before the server could finish processing the request",
},
[TRPC_ERROR_CODE.METHOD_NOT_SUPPORTED]: {
statusCode: 405,
message: "The requested method is not supported for this resource",
},
[TRPC_ERROR_CODE.NOT_IMPLEMENTED]: {
statusCode: 501,
message: "The requested method is not implemented on the server",
},
[TRPC_ERROR_CODE.PRECONDITION_FAILED]: {
statusCode: 412,
message: "The server does not meet one of the preconditions that the requester put on the request",
},
[TRPC_ERROR_CODE.TIMEOUT]: {
statusCode: 408,
message: "The request took too long to complete. Please try again later",
},
[TRPC_ERROR_CODE.UNPROCESSABLE_CONTENT]: {
statusCode: 422,
message:
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect",
},
[TRPC_ERROR_CODE.PARSE_ERROR]: {
statusCode: 400,
message: "The request could not be parsed due to invalid syntax.",
},
};
// --- TRPCExceptionFilter (updated) ---
@Catch(TRPCError)
export class TRPCExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger("TRPCExceptionFilter");
@@ -15,77 +109,13 @@ export class TRPCExceptionFilter implements ExceptionFilter {
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let statusCode = 500;
let errorMessage = exception.message;
const errorDefinition = TRPC_ERROR_MAP[exception.code as keyof typeof TRPC_ERROR_CODE] || {
statusCode: 500,
message: exception.message,
};
switch (exception.code) {
case "UNAUTHORIZED":
statusCode = 401;
errorMessage = "You are not authorized to access this resource";
break;
case "FORBIDDEN":
statusCode = 403;
errorMessage = "You don't have necessary permissions to access this resource";
break;
case "NOT_FOUND":
statusCode = 404;
errorMessage = "The requested resource was not found";
break;
case "INTERNAL_SERVER_ERROR":
statusCode = 500;
errorMessage = "An unexpected error occurred on the server. Please try again later";
break;
case "BAD_REQUEST":
statusCode = 400;
errorMessage = "Bad request: Please check your request data and try again";
break;
case "CONFLICT":
statusCode = 409;
errorMessage = "Could not process the request due to a resource conflict with the current state";
break;
case "TOO_MANY_REQUESTS":
statusCode = 429;
errorMessage = "You have exceeded the allowed number of requests";
break;
case "PAYLOAD_TOO_LARGE":
statusCode = 413;
errorMessage = "The request payload is too large";
break;
case "CLIENT_CLOSED_REQUEST":
statusCode = 499;
errorMessage =
"The client closed the connection before the server could finish processing the request";
break;
case "METHOD_NOT_SUPPORTED":
statusCode = 405;
errorMessage = "The requested method is not supported for this resource";
case "NOT_IMPLEMENTED":
statusCode = 501;
errorMessage = "The requested method is not implemented on the server";
break;
case "PRECONDITION_FAILED":
statusCode = 412;
errorMessage =
"The server does not meet one of the preconditions that the requester put on the request";
break;
case "TIMEOUT":
statusCode = 408;
errorMessage = "The request took too long to complete. Please try again later";
break;
case "UNPROCESSABLE_CONTENT":
statusCode = 422;
errorMessage =
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect";
break;
case "PARSE_ERROR":
statusCode = 400;
errorMessage = "The request could not be parsed due to invalid syntax.";
break;
default:
statusCode = 500;
errorMessage = exception.message;
break;
}
const statusCode = errorDefinition.statusCode;
const errorMessage = errorDefinition.message;
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
response.setHeader("X-Request-Id", requestId.toString());
@@ -103,7 +133,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
status: ERROR_STATUS,
timestamp: new Date().toISOString(),
path: request.url,
error: { code: exception.name, message: errorMessage },
error: { code: exception.name, message: errorMessage }, // exception.name is usually "TRPCError"
});
}
}
@@ -1,3 +1,4 @@
import { TRPC_ERROR_CODE, TRPC_ERROR_MAP, TRPCErrorCode } from "@/filters/trpc-exception.filter";
import { SlotsOutputService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
import type { RangeSlots, TimeSlots } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
import { SlotsWorkerService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-worker.service";
@@ -15,6 +16,7 @@ import {
VERSION_2024_06_11,
VERSION_2024_08_13,
} from "@calcom/platform-constants";
import { TRPCError } from "@calcom/platform-libraries";
import { getAvailableSlots } from "@calcom/platform-libraries/slots";
import { RemoveSelectedSlotInput_2024_04_15, ReserveSlotInput_2024_04_15 } from "@calcom/platform-types";
import { ApiResponse, GetAvailableSlotsInput_2024_04_15 } from "@calcom/platform-types";
@@ -209,7 +211,12 @@ export class SlotsController_2024_04_15 {
"Invalid time range given - check the 'startTime' and 'endTime' query parameters."
);
}
if (TRPC_ERROR_MAP[error.message as keyof typeof TRPC_ERROR_CODE]) {
throw new TRPCError({ code: error.message as TRPCErrorCode });
}
}
throw error;
}
}