diff --git a/backend/src/plugins/error-handler.ts b/backend/src/plugins/error-handler.ts new file mode 100644 index 0000000..f8bd000 --- /dev/null +++ b/backend/src/plugins/error-handler.ts @@ -0,0 +1,58 @@ +import type { FastifyRequest, FastifyReply } from 'fastify'; +import { ZodError } from 'zod'; +import { ApiException, fail } from '../lib/envelope.js'; + +export const errorHandler = (error: any, request: FastifyRequest, reply: FastifyReply) => { + request.log.error(error); + + if (error instanceof ZodError) { + const fields: Record = {}; + for (const issue of error.issues) { + const path = issue.path.join('.'); + fields[path] = issue.message; + } + + return fail(request, reply, 400, { + code: 'VALIDATION_ERROR', + message: 'The request payload validation failed.', + fields, + }); + } + + if (error instanceof ApiException) { + return fail( + request, + reply, + error.statusCode, + { + code: error.code, + message: error.message, + fields: error.fields, + }, + error.meta, + ); + } + + if (error.statusCode) { + const code = + error.statusCode === 404 + ? 'NOT_FOUND' + : error.statusCode === 403 || error.statusCode === 401 + ? 'PERMISSION_DENIED' + : error.statusCode === 405 + ? 'METHOD_NOT_ALLOWED' + : 'NETWORK_ERROR'; + + return fail(request, reply, error.statusCode, { + code, + message: error.message, + }); + } + + return fail(request, reply, 500, { + code: 'NETWORK_ERROR', + message: 'An internal control plane error occurred.', + }); +}; + +export default errorHandler;