/** * Error handling utilities for The Order services */ import { FastifyError, FastifyReply, FastifyRequest } from 'fastify'; /** * Custom application error class */ export class AppError extends Error { constructor( public statusCode: number, public code: string, message: string, public details?: unknown, public retryable = false, public timestamp = new Date() ) { super(message); this.name = 'AppError'; Error.captureStackTrace(this, this.constructor); } /** * Check if error is retryable */ isRetryable(): boolean { return this.retryable; } /** * Get error context */ getContext(): Record { return { statusCode: this.statusCode, code: this.code, message: this.message, details: this.details, retryable: this.retryable, timestamp: this.timestamp.toISOString(), }; } } /** * Global error handler for Fastify */ export async function errorHandler( error: FastifyError, request: FastifyRequest, reply: FastifyReply ): Promise { request.log.error({ err: error, url: request.url, method: request.method, statusCode: error.statusCode || 500, }); if (error instanceof AppError) { return reply.status(error.statusCode).send({ error: { code: error.code, message: error.message, details: error.details, }, }); } // Handle validation errors if (error.validation) { return reply.status(400).send({ error: { code: 'VALIDATION_ERROR', message: 'Validation failed', details: error.validation, }, }); } // Don't expose internal errors in production const isProduction = process.env.NODE_ENV === 'production'; const statusCode = error.statusCode || 500; // Log error details for monitoring request.log.error({ error: { message: error.message, stack: error.stack, code: error.code || 'INTERNAL_ERROR', statusCode, }, request: { method: request.method, url: request.url, headers: request.headers, }, }); return reply.status(statusCode).send({ error: { code: 'INTERNAL_ERROR', message: isProduction ? 'Internal server error' : error.message, ...(isProduction ? {} : { stack: error.stack }), ...(error instanceof AppError && error.retryable ? { retryable: true } : {}), }, }); } /** * Create a standardized error response */ export function createErrorResponse( statusCode: number, code: string, message: string, details?: unknown, retryable = false ): AppError { return new AppError(statusCode, code, message, details, retryable); } /** * Create a retryable error */ export function createRetryableError( statusCode: number, code: string, message: string, details?: unknown ): AppError { return new AppError(statusCode, code, message, details, true); } /** * Create a non-retryable error */ export function createNonRetryableError( statusCode: number, code: string, message: string, details?: unknown ): AppError { return new AppError(statusCode, code, message, details, false); }