162 lines
5.1 KiB
JavaScript
162 lines
5.1 KiB
JavaScript
/**
|
|
* Error handlers to deal with outputing in 'application/json' etc.
|
|
* @see {@link http://expressjs.com/guide/error-handling.html#the-default-error-handler}
|
|
*/
|
|
'use strict';
|
|
const _ = require('lodash');
|
|
const debug = require('debug')('webconsole-api:error-handlers');
|
|
|
|
const config = require(global.configFile);
|
|
|
|
//
|
|
// Define the exports
|
|
//
|
|
module.exports = {
|
|
errorHandlerMiddleware: errorHandler
|
|
};
|
|
|
|
/**
|
|
* If the response type is `application/json` this function formats the errors
|
|
* appropriately for that response type. Otherwise it just passes them on
|
|
* for the standard handlers to deal with.
|
|
*
|
|
* We need to do this formatting for `application/json` so that the swagger
|
|
* output validation code will not report an error on the error message itself.
|
|
*
|
|
* @param {Object} err - the error
|
|
* @param {Object} req - the request
|
|
* @param {Object} res - the response object
|
|
* @param {Callbacl} next - the callback for the next middleware in the chain
|
|
*/
|
|
function errorHandler(err, req, res, next) {
|
|
//
|
|
// Save the status code
|
|
//
|
|
const status = getStatusCode(err, res);
|
|
|
|
if (config.isDevEnv) {
|
|
debug(err.stack);
|
|
}
|
|
|
|
//
|
|
// Work out what format to return it in.
|
|
// We will handle json, and let the default error handler deal with
|
|
// the rest
|
|
//
|
|
const respondInJson = shouldRespondInJson(req);
|
|
if (respondInJson) {
|
|
const payload = {
|
|
info: err.message,
|
|
code: -1
|
|
};
|
|
|
|
//
|
|
// Only include the stack and other properties in development
|
|
//
|
|
if (config.isDevEnv) {
|
|
//
|
|
// JSON accept type
|
|
//
|
|
const error = {
|
|
message: err.message
|
|
};
|
|
|
|
error.stack = err.stack;
|
|
|
|
//
|
|
// Copy over other values except statusCode and headers which are used
|
|
// to set response headers etc. and don't need repeating in the body
|
|
//
|
|
_.merge(
|
|
error,
|
|
_.omit(err, ['statusCode', 'headers'])
|
|
);
|
|
|
|
//
|
|
// Add this error to the response
|
|
//
|
|
payload.error = error;
|
|
}
|
|
|
|
//
|
|
// Report the error
|
|
//
|
|
return res.status(status).json(payload);
|
|
} else {
|
|
//
|
|
// Let anything else be handled by the defaults
|
|
//
|
|
return next(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Works out the best status code to return to caller based on where the error
|
|
* comes from.
|
|
*
|
|
* @param {Object} err - The error object
|
|
* @param {Objext} res - The express response object
|
|
* @returns {number} - The status code number to respond to the client with
|
|
*/
|
|
function getStatusCode(err, res) {
|
|
let status = 500;
|
|
if (err.status) {
|
|
status = err.status;
|
|
} else if (err.statusCode) {
|
|
status = err.statusCode;
|
|
} else if (err.failedValidation && _.isString(err.message)) {
|
|
if (err.message.indexOf('Request validation failed') === 0) {
|
|
// Error from the Swagger validator regarding the Request.
|
|
// Set the status code to 400 BAD REQUEST because it is a problem
|
|
// on the client side, not the server side.
|
|
status = 400;
|
|
} else if (err.message.indexOf('Response validation failed') === 0) {
|
|
//
|
|
// It was the response validation, so that's on our side.
|
|
//
|
|
status = 500; // Internal server error
|
|
}
|
|
} else if (res.hasOwnProperty('statusCode')) {
|
|
// Something else has set a status (like the swagger router)
|
|
// so keep it for the response.
|
|
//
|
|
// WARNING: this MUST come after the failed response validation test above as
|
|
// response validation errors still have res.statusCode set to
|
|
// 200 OK despite the error.
|
|
// If this test came before that one, we would end up keeping
|
|
// the 200 OK rather than switching to a proper error.
|
|
//
|
|
status = res.statusCode;
|
|
} else {
|
|
// Unknown error - likely an exception thrown somewhere
|
|
status = 500; // Internal server error
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Works out whether we should respond with JSON or not, based on what the client
|
|
* says and what the swagger definition defines the response as.
|
|
*
|
|
* @param {Object} req - the express request object
|
|
* @returns {boolean} - true if we should respond using JSON
|
|
*/
|
|
function shouldRespondInJson(req) {
|
|
const accept = req.headers.accept || '';
|
|
const canAcceptJson = (accept === '*/*') || (accept.indexOf('json') !== -1);
|
|
let produces = null; // Undefined
|
|
if (req.swagger) {
|
|
if (req.swagger.operation && req.swagger.operation.produces) {
|
|
produces = req.swagger.operation.produces;
|
|
} else if (req.swagger.swaggerObject && req.swagger.swaggerObject.produces) {
|
|
produces = req.swagger.swaggerObject.produces;
|
|
}
|
|
}
|
|
const canProduceJson =
|
|
produces === null || // Assume we can unless told otherwise
|
|
(produces.indexOf('application/json') !== -1);
|
|
|
|
return canAcceptJson && canProduceJson;
|
|
}
|