Source

api/middlewares/errorHandler.js

/**
 * @module ErrorHandler
 * @author Alexis L. <alexis.lecomte@supinfo.com>
 * @category Middlewares
 * @subcategory Request flow
 */

import { isString } from "lodash-es";
import { APIResp, APIError, APIWarn, Logger } from "../../global/global.js";

/**
 * @const
 * @type {module:Logger}
 */
const logger = new Logger({ separator: ": " });

/* ---- Logger ---------------------------------- */
/**
 * Logs the error in the console and can send a mail
 * @function
 *
 * @param {module:APIError|module:APIWarn|Error|string} err
 * @param {e.Request} request
 * @param {e.Response} response
 * @param {function} next
 */
function expressLogger(err, request, response, next) {
	const url = `${request.protocol}://${request.headers.host}${request.originalUrl}`;
	const error = isString(err) ? new Error(err) : err;
	const types = {
		isAPIErr: (error instanceof APIError),
		isAPIWarn: (error instanceof APIWarn),
		isError: (error instanceof Error),
	};
	const stack = err.stack ? (`\t${err.stack.replace(/\n\r?/g, "\n\t")}`) : null;
	const message = (!stack ? err.message : (`${err.message}${err.message ? "\n" : ""}${stack}`));

	if (types.isAPIWarn) {
		logger.warn(`At ${url}\n${message}`, {ip: request.clientIP});
	} else {
		logger.error(`At ${url}\n${message}`, {ip: request.clientIP});
	}

	if (types.isError && !(types.isAPIErr || types.isAPIWarn)) {
		// TODO: Send mail
	}

	next({ url, types, ref: err });
}

/* ---- Error forwarder ------------------------- */
/**
 * Forward the error to the user
 * @function
 *
 * @param {{ url: string, types: Object<Boolean>, ref: (APIError|APIWarn|Error) }} err
 * @param {e.Request} request
 * @param {e.Response} response
 * @param {function} next
 */
function errorForwarder(err, request, response, next) {
	if (response.headersSent) next();

	const resp = new APIResp(err.ref.code).setData({
		code: err.ref.code ?? 500,
		type: (err.types.isAPIWarn ? "warning" : "error"),
		message: err.ref.message,
	});

	if (err.types.isAPIErr || err.types.isAPIWarn) {
		resp.setData({ ...resp.data, fields: err.ref.fields });
		response.status(resp.code).json(resp.toJSON());
	} else {
		switch (err.ref.type) {
			case "time-out":
				resp.setCode(408);
				response.status(resp.code).json(resp.toJSON());
				break;
			default:
				next(resp);
		}
	}
}

/* ---- FailSafe -------------------------------- */
/**
 * In case the error is unexpected, it sends a generic error message to the user
 * @function
 *
 * @param {APIResp} err
 * @param {e.Request} request
 * @param {e.Response} response
 * @param {function} next
 */
function failSafe(err, request, response, next) {
	const resp = err.setCode(500);

	if (process.env.NODE_ENV === "production") {
		resp.setData({
			...resp.data,
			message: "Le serveur a rencontré une erreur inconnue. Veuillez réessayer plus tard où contacter le support.",
		});
	}

	response.status(resp.code).json(resp.toJSON());
}

/* ---- Export ---------------------------------- */
export default { expressLogger, errorForwarder, failSafe };