| import type { Request, Response, NextFunction } from "express"; |
| import { ZodError } from "zod"; |
| import { HttpError } from "../errors.js"; |
|
|
| export interface ErrorContext { |
| error: { message: string; stack?: string; name?: string; details?: unknown; raw?: unknown }; |
| method: string; |
| url: string; |
| reqBody?: unknown; |
| reqParams?: unknown; |
| reqQuery?: unknown; |
| } |
|
|
| function attachErrorContext( |
| req: Request, |
| res: Response, |
| payload: ErrorContext["error"], |
| rawError?: Error, |
| ) { |
| (res as any).__errorContext = { |
| error: payload, |
| method: req.method, |
| url: req.originalUrl, |
| reqBody: req.body, |
| reqParams: req.params, |
| reqQuery: req.query, |
| } satisfies ErrorContext; |
| if (rawError) { |
| (res as any).err = rawError; |
| } |
| } |
|
|
| export function errorHandler( |
| err: unknown, |
| req: Request, |
| res: Response, |
| _next: NextFunction, |
| ) { |
| if (err instanceof HttpError) { |
| if (err.status >= 500) { |
| attachErrorContext( |
| req, |
| res, |
| { message: err.message, stack: err.stack, name: err.name, details: err.details }, |
| err, |
| ); |
| } |
| res.status(err.status).json({ |
| error: err.message, |
| ...(err.details ? { details: err.details } : {}), |
| }); |
| return; |
| } |
|
|
| if (err instanceof ZodError) { |
| res.status(400).json({ error: "Validation error", details: err.errors }); |
| return; |
| } |
|
|
| const rootError = err instanceof Error ? err : new Error(String(err)); |
| attachErrorContext( |
| req, |
| res, |
| err instanceof Error |
| ? { message: err.message, stack: err.stack, name: err.name } |
| : { message: String(err), raw: err, stack: rootError.stack, name: rootError.name }, |
| rootError, |
| ); |
|
|
| res.status(500).json({ error: "Internal server error" }); |
| } |
|
|