83 lines
2.0 KiB
TypeScript
83 lines
2.0 KiB
TypeScript
import { Request, Response, NextFunction } from 'express';
|
|
|
|
export class AppError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly statusCode: number,
|
|
public readonly code: string,
|
|
public readonly isOperational = true,
|
|
) {
|
|
super(message);
|
|
this.name = this.constructor.name;
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
}
|
|
|
|
export class ValidationError extends AppError {
|
|
constructor(message: string, public readonly details?: unknown) {
|
|
super(message, 400, 'VALIDATION_ERROR');
|
|
}
|
|
}
|
|
|
|
export class AuthenticationError extends AppError {
|
|
constructor(message = 'Unauthorized') {
|
|
super(message, 401, 'AUTHENTICATION_ERROR');
|
|
}
|
|
}
|
|
|
|
export class ForbiddenError extends AppError {
|
|
constructor(message = 'Forbidden') {
|
|
super(message, 403, 'FORBIDDEN');
|
|
}
|
|
}
|
|
|
|
export class NotFoundError extends AppError {
|
|
constructor(resource: string) {
|
|
super(`${resource} not found`, 404, 'NOT_FOUND');
|
|
}
|
|
}
|
|
|
|
export class ConflictError extends AppError {
|
|
constructor(message: string) {
|
|
super(message, 409, 'CONFLICT');
|
|
}
|
|
}
|
|
|
|
export class RateLimitError extends AppError {
|
|
constructor() {
|
|
super('Too many requests', 429, 'RATE_LIMIT');
|
|
}
|
|
}
|
|
|
|
export function globalErrorHandler(
|
|
err: Error,
|
|
req: Request,
|
|
res: Response,
|
|
_next: NextFunction,
|
|
): void {
|
|
const logger = (req as Request & { log?: { warn: Function; error: Function } }).log;
|
|
|
|
if (err instanceof AppError && err.isOperational) {
|
|
if (logger) {
|
|
logger.warn({ err, statusCode: err.statusCode }, err.message);
|
|
}
|
|
const body: Record<string, unknown> = { error: err.message, code: err.code };
|
|
if (err instanceof ValidationError && err.details !== undefined) {
|
|
body['details'] = err.details;
|
|
}
|
|
res.status(err.statusCode).json(body);
|
|
return;
|
|
}
|
|
|
|
if (logger) {
|
|
logger.error({ err }, 'Unhandled error');
|
|
} else {
|
|
console.error('Unhandled error', err);
|
|
}
|
|
|
|
res.status(500).json({
|
|
error: process.env['NODE_ENV'] === 'production' ? 'Internal server error' : err.message,
|
|
code: 'INTERNAL_ERROR',
|
|
});
|
|
}
|