You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
13 KiB
440 lines
13 KiB
3 years ago
|
'use strict';
|
||
|
|
||
|
// Load modules
|
||
|
|
||
|
const Hoek = require('hoek');
|
||
|
|
||
|
|
||
|
// Declare internals
|
||
|
|
||
|
const internals = {
|
||
|
codes: new Map([
|
||
|
[100, 'Continue'],
|
||
|
[101, 'Switching Protocols'],
|
||
|
[102, 'Processing'],
|
||
|
[200, 'OK'],
|
||
|
[201, 'Created'],
|
||
|
[202, 'Accepted'],
|
||
|
[203, 'Non-Authoritative Information'],
|
||
|
[204, 'No Content'],
|
||
|
[205, 'Reset Content'],
|
||
|
[206, 'Partial Content'],
|
||
|
[207, 'Multi-Status'],
|
||
|
[300, 'Multiple Choices'],
|
||
|
[301, 'Moved Permanently'],
|
||
|
[302, 'Moved Temporarily'],
|
||
|
[303, 'See Other'],
|
||
|
[304, 'Not Modified'],
|
||
|
[305, 'Use Proxy'],
|
||
|
[307, 'Temporary Redirect'],
|
||
|
[400, 'Bad Request'],
|
||
|
[401, 'Unauthorized'],
|
||
|
[402, 'Payment Required'],
|
||
|
[403, 'Forbidden'],
|
||
|
[404, 'Not Found'],
|
||
|
[405, 'Method Not Allowed'],
|
||
|
[406, 'Not Acceptable'],
|
||
|
[407, 'Proxy Authentication Required'],
|
||
|
[408, 'Request Time-out'],
|
||
|
[409, 'Conflict'],
|
||
|
[410, 'Gone'],
|
||
|
[411, 'Length Required'],
|
||
|
[412, 'Precondition Failed'],
|
||
|
[413, 'Request Entity Too Large'],
|
||
|
[414, 'Request-URI Too Large'],
|
||
|
[415, 'Unsupported Media Type'],
|
||
|
[416, 'Requested Range Not Satisfiable'],
|
||
|
[417, 'Expectation Failed'],
|
||
|
[418, 'I\'m a teapot'],
|
||
|
[422, 'Unprocessable Entity'],
|
||
|
[423, 'Locked'],
|
||
|
[424, 'Failed Dependency'],
|
||
|
[425, 'Unordered Collection'],
|
||
|
[426, 'Upgrade Required'],
|
||
|
[428, 'Precondition Required'],
|
||
|
[429, 'Too Many Requests'],
|
||
|
[431, 'Request Header Fields Too Large'],
|
||
|
[451, 'Unavailable For Legal Reasons'],
|
||
|
[500, 'Internal Server Error'],
|
||
|
[501, 'Not Implemented'],
|
||
|
[502, 'Bad Gateway'],
|
||
|
[503, 'Service Unavailable'],
|
||
|
[504, 'Gateway Time-out'],
|
||
|
[505, 'HTTP Version Not Supported'],
|
||
|
[506, 'Variant Also Negotiates'],
|
||
|
[507, 'Insufficient Storage'],
|
||
|
[509, 'Bandwidth Limit Exceeded'],
|
||
|
[510, 'Not Extended'],
|
||
|
[511, 'Network Authentication Required']
|
||
|
])
|
||
|
};
|
||
|
|
||
|
|
||
|
module.exports = internals.Boom = class extends Error {
|
||
|
|
||
|
static [Symbol.hasInstance](instance) {
|
||
|
|
||
|
return internals.Boom.isBoom(instance);
|
||
|
}
|
||
|
|
||
|
constructor(message, options = {}) {
|
||
|
|
||
|
if (message instanceof Error) {
|
||
|
return internals.Boom.boomify(Hoek.clone(message), options);
|
||
|
}
|
||
|
|
||
|
const { statusCode = 500, data = null, ctor = internals.Boom } = options;
|
||
|
const error = new Error(message ? message : undefined); // Avoids settings null message
|
||
|
Error.captureStackTrace(error, ctor); // Filter the stack to our external API
|
||
|
error.data = data;
|
||
|
internals.initialize(error, statusCode);
|
||
|
error.typeof = ctor;
|
||
|
|
||
|
if (options.decorate) {
|
||
|
Object.assign(error, options.decorate);
|
||
|
}
|
||
|
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
static isBoom(err) {
|
||
|
|
||
|
return (err instanceof Error && !!err.isBoom);
|
||
|
}
|
||
|
|
||
|
static boomify(err, options) {
|
||
|
|
||
|
Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
|
||
|
|
||
|
options = options || {};
|
||
|
|
||
|
if (options.data !== undefined) {
|
||
|
err.data = options.data;
|
||
|
}
|
||
|
|
||
|
if (options.decorate) {
|
||
|
Object.assign(err, options.decorate);
|
||
|
}
|
||
|
|
||
|
if (!err.isBoom) {
|
||
|
return internals.initialize(err, options.statusCode || 500, options.message);
|
||
|
}
|
||
|
|
||
|
if (options.override === false || // Defaults to true
|
||
|
(!options.statusCode && !options.message)) {
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
return internals.initialize(err, options.statusCode || err.output.statusCode, options.message);
|
||
|
}
|
||
|
|
||
|
// 4xx Client Errors
|
||
|
|
||
|
static badRequest(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 400, data, ctor: internals.Boom.badRequest });
|
||
|
}
|
||
|
|
||
|
static unauthorized(message, scheme, attributes) { // Or function (message, wwwAuthenticate[])
|
||
|
|
||
|
const err = new internals.Boom(message, { statusCode: 401, ctor: internals.Boom.unauthorized });
|
||
|
|
||
|
if (!scheme) {
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
let wwwAuthenticate = '';
|
||
|
|
||
|
if (typeof scheme === 'string') {
|
||
|
|
||
|
// function (message, scheme, attributes)
|
||
|
|
||
|
wwwAuthenticate = scheme;
|
||
|
|
||
|
if (attributes || message) {
|
||
|
err.output.payload.attributes = {};
|
||
|
}
|
||
|
|
||
|
if (attributes) {
|
||
|
if (typeof attributes === 'string') {
|
||
|
wwwAuthenticate = wwwAuthenticate + ' ' + Hoek.escapeHeaderAttribute(attributes);
|
||
|
err.output.payload.attributes = attributes;
|
||
|
}
|
||
|
else {
|
||
|
const names = Object.keys(attributes);
|
||
|
for (let i = 0; i < names.length; ++i) {
|
||
|
const name = names[i];
|
||
|
if (i) {
|
||
|
wwwAuthenticate = wwwAuthenticate + ',';
|
||
|
}
|
||
|
|
||
|
let value = attributes[name];
|
||
|
if (value === null ||
|
||
|
value === undefined) { // Value can be zero
|
||
|
|
||
|
value = '';
|
||
|
}
|
||
|
|
||
|
wwwAuthenticate = wwwAuthenticate + ' ' + name + '="' + Hoek.escapeHeaderAttribute(value.toString()) + '"';
|
||
|
err.output.payload.attributes[name] = value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (message) {
|
||
|
if (attributes) {
|
||
|
wwwAuthenticate = wwwAuthenticate + ',';
|
||
|
}
|
||
|
|
||
|
wwwAuthenticate = wwwAuthenticate + ' error="' + Hoek.escapeHeaderAttribute(message) + '"';
|
||
|
err.output.payload.attributes.error = message;
|
||
|
}
|
||
|
else {
|
||
|
err.isMissing = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
|
||
|
// function (message, wwwAuthenticate[])
|
||
|
|
||
|
const wwwArray = scheme;
|
||
|
for (let i = 0; i < wwwArray.length; ++i) {
|
||
|
if (i) {
|
||
|
wwwAuthenticate = wwwAuthenticate + ', ';
|
||
|
}
|
||
|
|
||
|
wwwAuthenticate = wwwAuthenticate + wwwArray[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static paymentRequired(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 402, data, ctor: internals.Boom.paymentRequired });
|
||
|
}
|
||
|
|
||
|
static forbidden(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 403, data, ctor: internals.Boom.forbidden });
|
||
|
}
|
||
|
|
||
|
static notFound(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 404, data, ctor: internals.Boom.notFound });
|
||
|
}
|
||
|
|
||
|
static methodNotAllowed(message, data, allow) {
|
||
|
|
||
|
const err = new internals.Boom(message, { statusCode: 405, data, ctor: internals.Boom.methodNotAllowed });
|
||
|
|
||
|
if (typeof allow === 'string') {
|
||
|
allow = [allow];
|
||
|
}
|
||
|
|
||
|
if (Array.isArray(allow)) {
|
||
|
err.output.headers.Allow = allow.join(', ');
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static notAcceptable(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 406, data, ctor: internals.Boom.notAcceptable });
|
||
|
}
|
||
|
|
||
|
static proxyAuthRequired(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 407, data, ctor: internals.Boom.proxyAuthRequired });
|
||
|
}
|
||
|
|
||
|
static clientTimeout(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 408, data, ctor: internals.Boom.clientTimeout });
|
||
|
}
|
||
|
|
||
|
static conflict(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 409, data, ctor: internals.Boom.conflict });
|
||
|
}
|
||
|
|
||
|
static resourceGone(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 410, data, ctor: internals.Boom.resourceGone });
|
||
|
}
|
||
|
|
||
|
static lengthRequired(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 411, data, ctor: internals.Boom.lengthRequired });
|
||
|
}
|
||
|
|
||
|
static preconditionFailed(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 412, data, ctor: internals.Boom.preconditionFailed });
|
||
|
}
|
||
|
|
||
|
static entityTooLarge(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 413, data, ctor: internals.Boom.entityTooLarge });
|
||
|
}
|
||
|
|
||
|
static uriTooLong(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 414, data, ctor: internals.Boom.uriTooLong });
|
||
|
}
|
||
|
|
||
|
static unsupportedMediaType(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 415, data, ctor: internals.Boom.unsupportedMediaType });
|
||
|
}
|
||
|
|
||
|
static rangeNotSatisfiable(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 416, data, ctor: internals.Boom.rangeNotSatisfiable });
|
||
|
}
|
||
|
|
||
|
static expectationFailed(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 417, data, ctor: internals.Boom.expectationFailed });
|
||
|
}
|
||
|
|
||
|
static teapot(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 418, data, ctor: internals.Boom.teapot });
|
||
|
}
|
||
|
|
||
|
static badData(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 422, data, ctor: internals.Boom.badData });
|
||
|
}
|
||
|
|
||
|
static locked(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 423, data, ctor: internals.Boom.locked });
|
||
|
}
|
||
|
|
||
|
static failedDependency(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 424, data, ctor: internals.Boom.failedDependency });
|
||
|
}
|
||
|
|
||
|
static preconditionRequired(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 428, data, ctor: internals.Boom.preconditionRequired });
|
||
|
}
|
||
|
|
||
|
static tooManyRequests(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 429, data, ctor: internals.Boom.tooManyRequests });
|
||
|
}
|
||
|
|
||
|
static illegal(message, data) {
|
||
|
|
||
|
return new internals.Boom(message, { statusCode: 451, data, ctor: internals.Boom.illegal });
|
||
|
}
|
||
|
|
||
|
// 5xx Server Errors
|
||
|
|
||
|
static internal(message, data, statusCode = 500) {
|
||
|
|
||
|
return internals.serverError(message, data, statusCode, internals.Boom.internal);
|
||
|
}
|
||
|
|
||
|
static notImplemented(message, data) {
|
||
|
|
||
|
return internals.serverError(message, data, 501, internals.Boom.notImplemented);
|
||
|
}
|
||
|
|
||
|
static badGateway(message, data) {
|
||
|
|
||
|
return internals.serverError(message, data, 502, internals.Boom.badGateway);
|
||
|
}
|
||
|
|
||
|
static serverUnavailable(message, data) {
|
||
|
|
||
|
return internals.serverError(message, data, 503, internals.Boom.serverUnavailable);
|
||
|
}
|
||
|
|
||
|
static gatewayTimeout(message, data) {
|
||
|
|
||
|
return internals.serverError(message, data, 504, internals.Boom.gatewayTimeout);
|
||
|
}
|
||
|
|
||
|
static badImplementation(message, data) {
|
||
|
|
||
|
const err = internals.serverError(message, data, 500, internals.Boom.badImplementation);
|
||
|
err.isDeveloperError = true;
|
||
|
return err;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
internals.initialize = function (err, statusCode, message) {
|
||
|
|
||
|
const numberCode = parseInt(statusCode, 10);
|
||
|
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
|
||
|
|
||
|
err.isBoom = true;
|
||
|
err.isServer = numberCode >= 500;
|
||
|
|
||
|
if (!err.hasOwnProperty('data')) {
|
||
|
err.data = null;
|
||
|
}
|
||
|
|
||
|
err.output = {
|
||
|
statusCode: numberCode,
|
||
|
payload: {},
|
||
|
headers: {}
|
||
|
};
|
||
|
|
||
|
err.reformat = internals.reformat;
|
||
|
|
||
|
if (!message &&
|
||
|
!err.message) {
|
||
|
|
||
|
err.reformat();
|
||
|
message = err.output.payload.error;
|
||
|
}
|
||
|
|
||
|
if (message) {
|
||
|
err.message = (message + (err.message ? ': ' + err.message : ''));
|
||
|
err.output.payload.message = err.message;
|
||
|
}
|
||
|
|
||
|
err.reformat();
|
||
|
return err;
|
||
|
};
|
||
|
|
||
|
|
||
|
internals.reformat = function (debug = false) {
|
||
|
|
||
|
this.output.payload.statusCode = this.output.statusCode;
|
||
|
this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
|
||
|
|
||
|
if (this.output.statusCode === 500 && debug !== true) {
|
||
|
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
|
||
|
}
|
||
|
else if (this.message) {
|
||
|
this.output.payload.message = this.message;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
internals.serverError = function (message, data, statusCode, ctor) {
|
||
|
|
||
|
if (data instanceof Error &&
|
||
|
!data.isBoom) {
|
||
|
|
||
|
return internals.Boom.boomify(data, { statusCode, message });
|
||
|
}
|
||
|
|
||
|
return new internals.Boom(message, { statusCode, data, ctor });
|
||
|
};
|