Skip to content

Commit 5aff398

Browse files
authored
feat(event-handler): add error handling functionality to BaseRouter (#4316)
1 parent 60d92b8 commit 5aff398

File tree

4 files changed

+462
-25
lines changed

4 files changed

+462
-25
lines changed

packages/event-handler/src/rest/BaseRouter.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import type {
1616
} from '../types/rest.js';
1717
import { HttpVerbs } from './constants.js';
1818
import { ErrorHandlerRegistry } from './ErrorHandlerRegistry.js';
19+
import {
20+
MethodNotAllowedError,
21+
NotFoundError,
22+
ServiceError,
23+
} from './errors.js';
1924
import { Route } from './Route.js';
2025
import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';
2126

@@ -54,13 +59,37 @@ abstract class BaseRouter {
5459
this.isDev = isDevMode();
5560
}
5661

62+
/**
63+
* Registers a custom error handler for specific error types.
64+
*
65+
* @param errorType - The error constructor(s) to handle
66+
* @param handler - The error handler function that returns an ErrorResponse
67+
*/
5768
public errorHandler<T extends Error>(
5869
errorType: ErrorConstructor<T> | ErrorConstructor<T>[],
5970
handler: ErrorHandler<T>
6071
): void {
6172
this.errorHandlerRegistry.register(errorType, handler);
6273
}
6374

75+
/**
76+
* Registers a custom handler for 404 Not Found errors.
77+
*
78+
* @param handler - The error handler function for NotFoundError
79+
*/
80+
public notFound(handler: ErrorHandler<NotFoundError>): void {
81+
this.errorHandlerRegistry.register(NotFoundError, handler);
82+
}
83+
84+
/**
85+
* Registers a custom handler for 405 Method Not Allowed errors.
86+
*
87+
* @param handler - The error handler function for MethodNotAllowedError
88+
*/
89+
public methodNotAllowed(handler: ErrorHandler<MethodNotAllowedError>): void {
90+
this.errorHandlerRegistry.register(MethodNotAllowedError, handler);
91+
}
92+
6493
public abstract resolve(
6594
event: unknown,
6695
context: Context,
@@ -76,6 +105,62 @@ abstract class BaseRouter {
76105
}
77106
}
78107

108+
/**
109+
* Handles errors by finding a registered error handler or falling
110+
* back to a default handler.
111+
*
112+
* @param error - The error to handle
113+
* @returns A Response object with appropriate status code and error details
114+
*/
115+
protected async handleError(error: Error): Promise<Response> {
116+
const handler = this.errorHandlerRegistry.resolve(error);
117+
if (handler !== null) {
118+
try {
119+
const body = await handler(error);
120+
return new Response(JSON.stringify(body), {
121+
status: body.statusCode,
122+
headers: { 'Content-Type': 'application/json' },
123+
});
124+
} catch (handlerError) {
125+
return this.#defaultErrorHandler(handlerError as Error);
126+
}
127+
}
128+
129+
if (error instanceof ServiceError) {
130+
return new Response(JSON.stringify(error.toJSON()), {
131+
status: error.statusCode,
132+
headers: { 'Content-Type': 'application/json' },
133+
});
134+
}
135+
136+
return this.#defaultErrorHandler(error);
137+
}
138+
139+
/**
140+
* Default error handler that returns a 500 Internal Server Error response.
141+
* In development mode, includes stack trace and error details.
142+
*
143+
* @param error - The error to handle
144+
* @returns A Response object with 500 status and error details
145+
*/
146+
#defaultErrorHandler(error: Error): Response {
147+
return new Response(
148+
JSON.stringify({
149+
statusCode: 500,
150+
error: 'Internal Server Error',
151+
message: isDevMode() ? error.message : 'Internal Server Error',
152+
...(isDevMode() && {
153+
stack: error.stack,
154+
details: { errorName: error.name },
155+
}),
156+
}),
157+
{
158+
status: 500,
159+
headers: { 'Content-Type': 'application/json' },
160+
}
161+
);
162+
}
163+
79164
#handleHttpMethod(
80165
method: HttpMethod,
81166
path: Path,

packages/event-handler/src/rest/errors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ export class ParameterValidationError extends RouteMatchingError {
1919
}
2020
}
2121

22-
abstract class ServiceError extends Error {
22+
export abstract class ServiceError extends Error {
2323
abstract readonly statusCode: HttpStatusCode;
2424
abstract readonly errorType: string;
2525
public readonly details?: Record<string, unknown>;
2626

27-
constructor(
27+
protected constructor(
2828
message?: string,
2929
options?: ErrorOptions,
3030
details?: Record<string, unknown>

packages/event-handler/src/types/rest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface ErrorContext {
2020
type ErrorHandler<T extends Error = Error> = (
2121
error: T,
2222
context?: ErrorContext
23-
) => ErrorResponse;
23+
) => Promise<ErrorResponse>;
2424

2525
interface ErrorConstructor<T extends Error = Error> {
2626
new (...args: any[]): T;

0 commit comments

Comments
 (0)