Skip to content

Commit ffe9d43

Browse files
chore: better trpc error handling (calcom#21948)
1 parent 2227b4f commit ffe9d43

2 files changed

Lines changed: 108 additions & 71 deletions

File tree

apps/api/v2/src/filters/trpc-exception.filter.ts

Lines changed: 101 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,100 @@ import { ERROR_STATUS } from "@calcom/platform-constants";
66
import { TRPCError } from "@calcom/platform-libraries";
77
import { Response } from "@calcom/platform-types";
88

9+
export type TRPCErrorCode = TRPCError["code"];
10+
11+
export interface ErrorDefinition {
12+
statusCode: number;
13+
message: string;
14+
}
15+
16+
// Define the specific TRPC error codes
17+
export const TRPC_ERROR_CODE = {
18+
UNAUTHORIZED: "UNAUTHORIZED",
19+
FORBIDDEN: "FORBIDDEN",
20+
NOT_FOUND: "NOT_FOUND",
21+
INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
22+
BAD_REQUEST: "BAD_REQUEST",
23+
CONFLICT: "CONFLICT",
24+
TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
25+
PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
26+
CLIENT_CLOSED_REQUEST: "CLIENT_CLOSED_REQUEST",
27+
METHOD_NOT_SUPPORTED: "METHOD_NOT_SUPPORTED",
28+
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
29+
PRECONDITION_FAILED: "PRECONDITION_FAILED",
30+
TIMEOUT: "TIMEOUT",
31+
UNPROCESSABLE_CONTENT: "UNPROCESSABLE_CONTENT",
32+
PARSE_ERROR: "PARSE_ERROR",
33+
} as const; // `as const` ensures literal types, improving type inference
34+
35+
// Map TRPC error codes to their HTTP status codes and default messages
36+
export const TRPC_ERROR_MAP: Record<(typeof TRPC_ERROR_CODE)[keyof typeof TRPC_ERROR_CODE], ErrorDefinition> =
37+
{
38+
[TRPC_ERROR_CODE.UNAUTHORIZED]: {
39+
statusCode: 401,
40+
message: "You are not authorized to access this resource",
41+
},
42+
[TRPC_ERROR_CODE.FORBIDDEN]: {
43+
statusCode: 403,
44+
message: "You don't have necessary permissions to access this resource",
45+
},
46+
[TRPC_ERROR_CODE.NOT_FOUND]: {
47+
statusCode: 404,
48+
message: "The requested resource was not found",
49+
},
50+
[TRPC_ERROR_CODE.INTERNAL_SERVER_ERROR]: {
51+
statusCode: 500,
52+
message: "An unexpected error occurred on the server. Please try again later",
53+
},
54+
[TRPC_ERROR_CODE.BAD_REQUEST]: {
55+
statusCode: 400,
56+
message: "Bad request: Please check your request data and try again",
57+
},
58+
[TRPC_ERROR_CODE.CONFLICT]: {
59+
statusCode: 409,
60+
message: "Could not process the request due to a resource conflict with the current state",
61+
},
62+
[TRPC_ERROR_CODE.TOO_MANY_REQUESTS]: {
63+
statusCode: 429,
64+
message: "You have exceeded the allowed number of requests",
65+
},
66+
[TRPC_ERROR_CODE.PAYLOAD_TOO_LARGE]: {
67+
statusCode: 413,
68+
message: "The request payload is too large",
69+
},
70+
[TRPC_ERROR_CODE.CLIENT_CLOSED_REQUEST]: {
71+
statusCode: 499,
72+
message: "The client closed the connection before the server could finish processing the request",
73+
},
74+
[TRPC_ERROR_CODE.METHOD_NOT_SUPPORTED]: {
75+
statusCode: 405,
76+
message: "The requested method is not supported for this resource",
77+
},
78+
[TRPC_ERROR_CODE.NOT_IMPLEMENTED]: {
79+
statusCode: 501,
80+
message: "The requested method is not implemented on the server",
81+
},
82+
[TRPC_ERROR_CODE.PRECONDITION_FAILED]: {
83+
statusCode: 412,
84+
message: "The server does not meet one of the preconditions that the requester put on the request",
85+
},
86+
[TRPC_ERROR_CODE.TIMEOUT]: {
87+
statusCode: 408,
88+
message: "The request took too long to complete. Please try again later",
89+
},
90+
[TRPC_ERROR_CODE.UNPROCESSABLE_CONTENT]: {
91+
statusCode: 422,
92+
message:
93+
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect",
94+
},
95+
[TRPC_ERROR_CODE.PARSE_ERROR]: {
96+
statusCode: 400,
97+
message: "The request could not be parsed due to invalid syntax.",
98+
},
99+
};
100+
101+
// --- TRPCExceptionFilter (updated) ---
102+
9103
@Catch(TRPCError)
10104
export class TRPCExceptionFilter implements ExceptionFilter {
11105
private readonly logger = new Logger("TRPCExceptionFilter");
@@ -15,77 +109,13 @@ export class TRPCExceptionFilter implements ExceptionFilter {
15109
const response = ctx.getResponse<Response>();
16110
const request = ctx.getRequest<Request>();
17111

18-
let statusCode = 500;
19-
let errorMessage = exception.message;
112+
const errorDefinition = TRPC_ERROR_MAP[exception.code as keyof typeof TRPC_ERROR_CODE] || {
113+
statusCode: 500,
114+
message: exception.message,
115+
};
20116

21-
switch (exception.code) {
22-
case "UNAUTHORIZED":
23-
statusCode = 401;
24-
errorMessage = "You are not authorized to access this resource";
25-
break;
26-
case "FORBIDDEN":
27-
statusCode = 403;
28-
errorMessage = "You don't have necessary permissions to access this resource";
29-
break;
30-
case "NOT_FOUND":
31-
statusCode = 404;
32-
errorMessage = "The requested resource was not found";
33-
break;
34-
case "INTERNAL_SERVER_ERROR":
35-
statusCode = 500;
36-
errorMessage = "An unexpected error occurred on the server. Please try again later";
37-
break;
38-
case "BAD_REQUEST":
39-
statusCode = 400;
40-
errorMessage = "Bad request: Please check your request data and try again";
41-
break;
42-
case "CONFLICT":
43-
statusCode = 409;
44-
errorMessage = "Could not process the request due to a resource conflict with the current state";
45-
break;
46-
case "TOO_MANY_REQUESTS":
47-
statusCode = 429;
48-
errorMessage = "You have exceeded the allowed number of requests";
49-
break;
50-
case "PAYLOAD_TOO_LARGE":
51-
statusCode = 413;
52-
errorMessage = "The request payload is too large";
53-
break;
54-
case "CLIENT_CLOSED_REQUEST":
55-
statusCode = 499;
56-
errorMessage =
57-
"The client closed the connection before the server could finish processing the request";
58-
break;
59-
case "METHOD_NOT_SUPPORTED":
60-
statusCode = 405;
61-
errorMessage = "The requested method is not supported for this resource";
62-
case "NOT_IMPLEMENTED":
63-
statusCode = 501;
64-
errorMessage = "The requested method is not implemented on the server";
65-
break;
66-
case "PRECONDITION_FAILED":
67-
statusCode = 412;
68-
errorMessage =
69-
"The server does not meet one of the preconditions that the requester put on the request";
70-
break;
71-
case "TIMEOUT":
72-
statusCode = 408;
73-
errorMessage = "The request took too long to complete. Please try again later";
74-
break;
75-
case "UNPROCESSABLE_CONTENT":
76-
statusCode = 422;
77-
errorMessage =
78-
"The server was unable to process the request because the request payload is semantically incorrect or because the request is syntactically incorrect";
79-
break;
80-
case "PARSE_ERROR":
81-
statusCode = 400;
82-
errorMessage = "The request could not be parsed due to invalid syntax.";
83-
break;
84-
default:
85-
statusCode = 500;
86-
errorMessage = exception.message;
87-
break;
88-
}
117+
const statusCode = errorDefinition.statusCode;
118+
const errorMessage = errorDefinition.message;
89119

90120
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
91121
response.setHeader("X-Request-Id", requestId.toString());
@@ -103,7 +133,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
103133
status: ERROR_STATUS,
104134
timestamp: new Date().toISOString(),
105135
path: request.url,
106-
error: { code: exception.name, message: errorMessage },
136+
error: { code: exception.name, message: errorMessage }, // exception.name is usually "TRPCError"
107137
});
108138
}
109139
}

apps/api/v2/src/modules/slots/slots-2024-04-15/controllers/slots.controller.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TRPC_ERROR_CODE, TRPC_ERROR_MAP, TRPCErrorCode } from "@/filters/trpc-exception.filter";
12
import { SlotsOutputService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
23
import type { RangeSlots, TimeSlots } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
34
import { SlotsWorkerService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-worker.service";
@@ -15,6 +16,7 @@ import {
1516
VERSION_2024_06_11,
1617
VERSION_2024_08_13,
1718
} from "@calcom/platform-constants";
19+
import { TRPCError } from "@calcom/platform-libraries";
1820
import { getAvailableSlots } from "@calcom/platform-libraries/slots";
1921
import { RemoveSelectedSlotInput_2024_04_15, ReserveSlotInput_2024_04_15 } from "@calcom/platform-types";
2022
import { ApiResponse, GetAvailableSlotsInput_2024_04_15 } from "@calcom/platform-types";
@@ -209,7 +211,12 @@ export class SlotsController_2024_04_15 {
209211
"Invalid time range given - check the 'startTime' and 'endTime' query parameters."
210212
);
211213
}
214+
215+
if (TRPC_ERROR_MAP[error.message as keyof typeof TRPC_ERROR_CODE]) {
216+
throw new TRPCError({ code: error.message as TRPCErrorCode });
217+
}
212218
}
219+
213220
throw error;
214221
}
215222
}

0 commit comments

Comments
 (0)