blob: 17ed0279ea724baf3692a18ce5f67a62e698875a [file] [log] [blame]
'use strict';
// Load modules
const Hoek = require('hoek');
// Declare internals
const internals = {
STATUS_CODES: Object.setPrototypeOf({
'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'
}, null)
};
exports.wrap = function (error, statusCode, message) {
Hoek.assert(error instanceof Error, 'Cannot wrap non-Error object');
Hoek.assert(!error.isBoom || (!statusCode && !message), 'Cannot provide statusCode or message with boom error');
return (error.isBoom ? error : internals.initialize(error, statusCode || 500, message));
};
exports.create = function (statusCode, message, data) {
return internals.create(statusCode, message, data, exports.create);
};
internals.create = function (statusCode, message, data, ctor) {
if (message instanceof Error) {
if (data) {
message.data = data;
}
return exports.wrap(message, statusCode);
}
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 || null;
internals.initialize(error, statusCode);
return error;
};
internals.initialize = function (error, statusCode, message) {
const numberCode = parseInt(statusCode, 10);
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
error.isBoom = true;
error.isServer = numberCode >= 500;
if (!error.hasOwnProperty('data')) {
error.data = null;
}
error.output = {
statusCode: numberCode,
payload: {},
headers: {}
};
error.reformat = internals.reformat;
error.reformat();
if (!message &&
!error.message) {
message = error.output.payload.error;
}
if (message) {
error.message = (message + (error.message ? ': ' + error.message : ''));
}
return error;
};
internals.reformat = function () {
this.output.payload.statusCode = this.output.statusCode;
this.output.payload.error = internals.STATUS_CODES[this.output.statusCode] || 'Unknown';
if (this.output.statusCode === 500) {
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
}
else if (this.message) {
this.output.payload.message = this.message;
}
};
// 4xx Client Errors
exports.badRequest = function (message, data) {
return internals.create(400, message, data, exports.badRequest);
};
exports.unauthorized = function (message, scheme, attributes) { // Or function (message, wwwAuthenticate[])
const err = internals.create(401, message, undefined, exports.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;
};
exports.paymentRequired = function (message, data) {
return internals.create(402, message, data, exports.paymentRequired);
};
exports.forbidden = function (message, data) {
return internals.create(403, message, data, exports.forbidden);
};
exports.notFound = function (message, data) {
return internals.create(404, message, data, exports.notFound);
};
exports.methodNotAllowed = function (message, data, allow) {
const err = internals.create(405, message, data, exports.methodNotAllowed);
if (typeof allow === 'string') {
allow = [allow];
}
if (Array.isArray(allow)) {
err.output.headers.Allow = allow.join(', ');
}
return err;
};
exports.notAcceptable = function (message, data) {
return internals.create(406, message, data, exports.notAcceptable);
};
exports.proxyAuthRequired = function (message, data) {
return internals.create(407, message, data, exports.proxyAuthRequired);
};
exports.clientTimeout = function (message, data) {
return internals.create(408, message, data, exports.clientTimeout);
};
exports.conflict = function (message, data) {
return internals.create(409, message, data, exports.conflict);
};
exports.resourceGone = function (message, data) {
return internals.create(410, message, data, exports.resourceGone);
};
exports.lengthRequired = function (message, data) {
return internals.create(411, message, data, exports.lengthRequired);
};
exports.preconditionFailed = function (message, data) {
return internals.create(412, message, data, exports.preconditionFailed);
};
exports.entityTooLarge = function (message, data) {
return internals.create(413, message, data, exports.entityTooLarge);
};
exports.uriTooLong = function (message, data) {
return internals.create(414, message, data, exports.uriTooLong);
};
exports.unsupportedMediaType = function (message, data) {
return internals.create(415, message, data, exports.unsupportedMediaType);
};
exports.rangeNotSatisfiable = function (message, data) {
return internals.create(416, message, data, exports.rangeNotSatisfiable);
};
exports.expectationFailed = function (message, data) {
return internals.create(417, message, data, exports.expectationFailed);
};
exports.teapot = function (message, data) {
return internals.create(418, message, data, exports.teapot);
};
exports.badData = function (message, data) {
return internals.create(422, message, data, exports.badData);
};
exports.locked = function (message, data) {
return internals.create(423, message, data, exports.locked);
};
exports.preconditionRequired = function (message, data) {
return internals.create(428, message, data, exports.preconditionRequired);
};
exports.tooManyRequests = function (message, data) {
return internals.create(429, message, data, exports.tooManyRequests);
};
exports.illegal = function (message, data) {
return internals.create(451, message, data, exports.illegal);
};
// 5xx Server Errors
exports.internal = function (message, data, statusCode) {
return internals.serverError(message, data, statusCode, exports.internal);
};
internals.serverError = function (message, data, statusCode, ctor) {
let error;
if (data instanceof Error) {
error = exports.wrap(data, statusCode, message);
}
else {
error = internals.create(statusCode || 500, message, undefined, ctor);
error.data = data;
}
return error;
};
exports.notImplemented = function (message, data) {
return internals.serverError(message, data, 501, exports.notImplemented);
};
exports.badGateway = function (message, data) {
return internals.serverError(message, data, 502, exports.badGateway);
};
exports.serverUnavailable = function (message, data) {
return internals.serverError(message, data, 503, exports.serverUnavailable);
};
exports.gatewayTimeout = function (message, data) {
return internals.serverError(message, data, 504, exports.gatewayTimeout);
};
exports.badImplementation = function (message, data) {
const err = internals.serverError(message, data, 500, exports.badImplementation);
err.isDeveloperError = true;
return err;
};