bridge-node-server/node_server/swagger_api/api_error_handler.js
Martin Donnelly 57bd6c8e6a init
2018-06-24 21:15:03 +01:00

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;
}