chore: better trpc error handling (#21948)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user