Skip to content

feat(event-handler): add error handling functionality to BaseRouter #4316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/event-handler/src/rest/BaseRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import type {
} from '../types/rest.js';
import { HttpVerbs } from './constants.js';
import { ErrorHandlerRegistry } from './ErrorHandlerRegistry.js';
import {
MethodNotAllowedError,
NotFoundError,
ServiceError,
} from './errors.js';
import { Route } from './Route.js';
import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';

Expand Down Expand Up @@ -54,13 +59,37 @@ abstract class BaseRouter {
this.isDev = isDevMode();
}

/**
* Registers a custom error handler for specific error types.
*
* @param errorType - The error constructor(s) to handle
* @param handler - The error handler function that returns an ErrorResponse
*/
public errorHandler<T extends Error>(
errorType: ErrorConstructor<T> | ErrorConstructor<T>[],
handler: ErrorHandler<T>
): void {
this.errorHandlerRegistry.register(errorType, handler);
}

/**
* Registers a custom handler for 404 Not Found errors.
*
* @param handler - The error handler function for NotFoundError
*/
public notFound(handler: ErrorHandler<NotFoundError>): void {
this.errorHandlerRegistry.register(NotFoundError, handler);
}

/**
* Registers a custom handler for 405 Method Not Allowed errors.
*
* @param handler - The error handler function for MethodNotAllowedError
*/
public methodNotAllowed(handler: ErrorHandler<MethodNotAllowedError>): void {
this.errorHandlerRegistry.register(MethodNotAllowedError, handler);
}

public abstract resolve(
event: unknown,
context: Context,
Expand All @@ -76,6 +105,62 @@ abstract class BaseRouter {
}
}

/**
* Handles errors by finding a registered error handler or falling
* back to a default handler.
*
* @param error - The error to handle
* @returns A Response object with appropriate status code and error details
*/
protected async handleError(error: Error): Promise<Response> {
const handler = this.errorHandlerRegistry.resolve(error);
if (handler !== null) {
try {
const body = await handler(error);
return new Response(JSON.stringify(body), {
status: body.statusCode,
headers: { 'Content-Type': 'application/json' },
});
} catch (handlerError) {
return this.#defaultErrorHandler(handlerError as Error);
}
}

if (error instanceof ServiceError) {
return new Response(JSON.stringify(error.toJSON()), {
status: error.statusCode,
headers: { 'Content-Type': 'application/json' },
});
}

return this.#defaultErrorHandler(error);
}

/**
* Default error handler that returns a 500 Internal Server Error response.
* In development mode, includes stack trace and error details.
*
* @param error - The error to handle
* @returns A Response object with 500 status and error details
*/
#defaultErrorHandler(error: Error): Response {
return new Response(
JSON.stringify({
statusCode: 500,
error: 'Internal Server Error',
message: isDevMode() ? error.message : 'Internal Server Error',
...(isDevMode() && {
stack: error.stack,
details: { errorName: error.name },
}),
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}

#handleHttpMethod(
method: HttpMethod,
path: Path,
Expand Down
4 changes: 2 additions & 2 deletions packages/event-handler/src/rest/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export class ParameterValidationError extends RouteMatchingError {
}
}

abstract class ServiceError extends Error {
export abstract class ServiceError extends Error {
abstract readonly statusCode: HttpStatusCode;
abstract readonly errorType: string;
public readonly details?: Record<string, unknown>;

constructor(
protected constructor(
message?: string,
options?: ErrorOptions,
details?: Record<string, unknown>
Expand Down
2 changes: 1 addition & 1 deletion packages/event-handler/src/types/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface ErrorContext {
type ErrorHandler<T extends Error = Error> = (
error: T,
context?: ErrorContext
) => ErrorResponse;
) => Promise<ErrorResponse>;

interface ErrorConstructor<T extends Error = Error> {
new (...args: any[]): T;
Expand Down
Loading