// /middlewares/globalErrorHandler.js const logger = require('../utils/logger'); const { errorResponse } = require('../utils/apiResponse'); const { getLocalizedErrorMessage } = require('../utils/errorLocalization'); const formatDetailsMessage = (details) => { if (!details) return null; if (typeof details === 'string') return details; if (Array.isArray(details)) { const parts = details .map((item) => { if (!item) return null; if (typeof item === 'string') return item; return item.message || item.reason || null; }) .filter(Boolean); return parts.length ? parts.join(' — ') : null; } if (typeof details === 'object') { const parts = Object.entries(details).map(([key, value]) => { if (value == null) return null; if (typeof value === 'string') return `${key}: ${value}`; if (typeof value === 'object' && value.message) return `${key}: ${value.message}`; if (key === 'keyValue') return Object.entries(value).map(([k, v]) => `${k}=${v}`).join(', '); return null; }).filter(Boolean); return parts.length ? parts.join(' — ') : null; } return null; }; const globalErrorHandler = (err, req, res, next) => { let statusCode = err.statusCode || 500; let errorCode = err.errorCode || 'INTERNAL_SERVER_ERROR'; let details = err.details || null; // Handle Mongoose CastError (e.g. invalid ObjectId) if (err.name === 'CastError') { statusCode = 400; errorCode = 'VALIDATION_FAILED'; details = { field: err.path, value: err.value, reason: 'Invalid ObjectId format' }; } // Handle Mongoose Duplicate Key Error (E11000) if (err.code === 11000) { statusCode = 409; errorCode = 'DUPLICATE_KEY'; details = { keyPattern: err.keyPattern, keyValue: err.keyValue }; } // Handle Mongoose ValidationError if (err.name === 'ValidationError') { statusCode = 400; errorCode = 'VALIDATION_FAILED'; details = Object.keys(err.errors || {}).reduce((acc, key) => { acc[key] = err.errors[key].message; return acc; }, {}); } // Handle Multer upload errors if (err.name === 'MulterError') { statusCode = 400; errorCode = err.code === 'LIMIT_FILE_SIZE' ? 'VALIDATION_FAILED' : 'FILE_REQUIRED'; details = { reason: err.code, field: err.field }; } // Handle Joi validation errors if forwarded as standard Error if (err.isJoi) { statusCode = 400; errorCode = 'VALIDATION_FAILED'; details = err.details ? err.details.map((d) => ({ message: d.message, path: d.path })) : null; } // Localized catalog message (Accept-Language / ?lang=) let localizedMessage = err.overrideMessage ? err.overrideMessage : getLocalizedErrorMessage(errorCode, req); const detailsMessage = formatDetailsMessage(details); if (detailsMessage && errorCode === 'VALIDATION_FAILED' && !err.overrideMessage) { localizedMessage = `${localizedMessage} ${detailsMessage}`; } else if (detailsMessage && errorCode === 'DUPLICATE_KEY') { localizedMessage = `${localizedMessage} (${detailsMessage})`; } // Log error (stack trace for non-operational / 500 errors) if (!err.isOperational || statusCode >= 500) { logger.error(`[PROGRAMMING_ERROR] ${err.message}`, { stack: err.stack, path: req.originalUrl, method: req.method }); } else { logger.warn(`[OPERATIONAL_ERROR] Code: ${errorCode} | Path: ${req.originalUrl} | Message: ${localizedMessage}`); } return errorResponse( res, statusCode, errorCode, localizedMessage, details ); }; module.exports = globalErrorHandler;