agmission/Development/server/middlewares/error_handler.js

107 lines
4.0 KiB
JavaScript

'use strict';
const { MulterError } = require("multer"),
{ ValidationError } = require("joi"),
{ Errors, LIMIT_FILE_SIZE_ERR } = require('../helpers/constants'),
{ AppError, AppAuthError } = require("../helpers/app_error"),
env = require('../helpers/env'),
utils = require('../helpers/utils'),
debug = require('debug')('agm:errHandler');
function createAppErrObj(tag = Errors.UNKNOWN_APP_ERROR, err = null) {
const errObj = { error: { '.tag': tag } };
// Check if the tag (passed message) contains underscore (indicating it's a constant from Errors)
if (tag && tag.indexOf('_') === -1) {
// Tag doesn't contain underscore - treat as custom message
// Use defaultMessage for .tag value and original tag as message
errObj.error['.tag'] = err?.defaultMessage || Errors.UNKNOWN_APP_ERROR;
if (!env.PRODUCTION) {
errObj.error.message = tag;
}
} else {
// Tag contains underscore - treat as constant from Errors or fallback to defaultMessage or UNKNOWN_APP_ERROR
errObj.error['.tag'] = tag || err?.defaultMessage || Errors.UNKNOWN_APP_ERROR;
}
if (!env.PRODUCTION && err) {
// Include messageLong or the error info dev mode for debugging
const errMessage = typeof err === 'string' ? err : err?.messageLong || err?.toString();
errObj.error.message = errMessage;
}
return errObj;
}
function createAppValidationErrObj(valError) {
let errObj = createAppErrObj(Errors.INVALID_PARAM);
if (!utils.isEmptyObj(valError) && valError.message) {
errObj.error.message = valError.message;
}
return errObj;
}
/**
* Handle Application error for a given request with the provided error object
* @param {*} req The Request object of the request
* @param {*} res The Response object of the request
* @param {*} err The error object (runtime error object or App* error classes)
* Notes: return HTTP statusCode for the custom App errors with 409 as default.
* For Stripe errors the code will always be 402. The client should base on the .code value for detail error.
* @returns
*/
function ErrorHandler(err, req, res, next) {
const statusCode = err && err.statusCode || 409; // Application error default HTTP status code
try {
// Check if response has already been sent or is in invalid state
if (res.headersSent || res.writableEnded || res.destroyed) {
env.LOG_ALL_ERRORS && debug('Response already sent, cannot send error:', err);
return;
}
if (err.type && err.type.startsWith('Stripe')) {
/*
For Stripe errors the code will always be 402. The client should base on the .code value for detailed error.
TODO: In Production mode => consider filtering Stripe errors to send back less detail.
*/
return res.status(402).send(err);
}
// Handle Joi ValidationError
if (err instanceof ValidationError) {
return res.status(statusCode).send(createAppValidationErrObj(err));
}
// Handle Mongoose ValidationError as AppParamError
if (err.name === 'ValidationError') {
// const messages = Object.values(err.errors).map((e) => e.message).join(', ');
return res.status(409).send(createAppValidationErrObj(err));
}
if (err instanceof AppError) {
return res.status(err.statusCode).send(createAppErrObj(err.message, err));
}
// Handle Mongoose duplicate key error (e.g., unique index violation)
if (typeof err === "object" && err.code === 11000) {
return res.status(statusCode).send(createAppErrObj(Errors.DUPLICATED_VALUE));
}
// Handle Multer file upload errors
if (err instanceof MulterError) {
return res.status(statusCode).send(createAppErrObj(err.code == LIMIT_FILE_SIZE_ERR ? Errors.FILE_TOO_LARGE : Errors.INVALID_PARAM));
}
res.status(500).send(createAppErrObj(Errors.UNKNOWN_APP_ERROR, err));
// Handle unknown errors
} catch (error) {
res.status(500).send(createAppErrObj(Errors.UNKNOWN_ERROR, error));
} finally {
env.LOG_ALL_ERRORS && (debug(err instanceof AppAuthError ? err.message : err));
}
}
module.exports = { ErrorHandler }