import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; import { Prisma } from '@prisma/client'; import { logger } from '../logger'; export interface ApiErrorResponse { error: string; message: string; code?: string; details?: any; } export const setupErrorHandler = (server: FastifyInstance) => { server.setErrorHandler((error: any, request: FastifyRequest, reply: FastifyReply) => { // Log the error logger.error({ err: error, path: request.url, method: request.method, requestId: request.id }, 'API Error detected'); // 1. Prisma Errors mapping if (error instanceof Prisma.PrismaClientKnownRequestError) { // P2002: Unique constraint failed if (error.code === 'P2002') { return reply.code(409).send({ error: 'Conflict', message: `A record with this ${error.meta?.target} already exists.`, code: 'PRISMA_P2002' }); } // P2025: Record not found if (error.code === 'P2025') { return reply.code(404).send({ error: 'Not Found', message: 'The requested record does not exist.', code: 'PRISMA_P2025' }); } } // 2. Validation Errors (Fastify/Ajv) if (error.validation) { return reply.code(400).send({ error: 'Bad Request', message: 'Validation failed', details: error.validation, code: 'VALIDATION_ERROR' }); } // 3. JWT/Auth Errors if (error.statusCode === 401 || error.code === 'FST_JWT_NO_AUTHORIZATION_TOKEN') { return reply.code(401).send({ error: 'Unauthorized', message: error.message || 'Authentication required', code: 'AUTH_ERROR' }); } // 4. Fallback for generic errors const statusCode = error.statusCode || 500; return reply.code(statusCode).send({ error: statusCode === 500 ? 'Internal Server Error' : error.name || 'Error', message: statusCode === 500 ? 'An unexpected error occurred' : error.message, code: error.code || 'GENERIC_ERROR' }); }); };