2025-04-02 06:50:39 -04:00

300 lines
6.3 KiB
JavaScript

/*!
* http-errors
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2016 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict';
/**
* Module dependencies.
* @private
*/
var deprecate = require('depd')('http-errors');
var setPrototypeOf = require('setprototypeof');
var statuses = require('statuses');
var inherits = require('inherits');
var toIdentifier = require('toidentifier');
/**
* Module exports.
* @public
*/
module.exports = createError;
module.exports.HttpError = createHttpErrorConstructor();
module.exports.isHttpError = createIsHttpErrorFunction(
module.exports.HttpError
);
// Populate exports for all constructors
populateConstructorExports(
module.exports,
statuses.codes,
module.exports.HttpError
);
/**
* Get the code class of a status code.
* @private
*/
function codeClass(status) {
return Number(String(status).charAt(0) + '00');
}
/**
* Create a new HTTP Error.
*
* @returns {Error}
* @public
*/
function createError() {
// so much arity going on ~_~
var err;
var msg;
var status = 500;
var props = {};
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
var type = typeof arg;
if (type === 'object' && arg instanceof Error) {
err = arg;
status = err.status || err.statusCode || status;
} else if (type === 'number' && i === 0) {
status = arg;
} else if (type === 'string') {
msg = arg;
} else if (type === 'object') {
props = arg;
} else {
throw new TypeError('argument #' + (i + 1) + ' unsupported type ' + type);
}
}
if (typeof status === 'number' && (status < 400 || status >= 600)) {
deprecate('non-error status code; use only 4xx or 5xx status codes');
}
if (
typeof status !== 'number' ||
(!statuses.message[status] && (status < 400 || status >= 600))
) {
status = 500;
}
// constructor
var HttpError = createError[status] || createError[codeClass(status)];
if (!err) {
// create error
err =
HttpError ?
new HttpError(msg)
: new Error(msg || statuses.message[status]);
Error.captureStackTrace(err, createError);
}
if (!HttpError || !(err instanceof HttpError) || err.status !== status) {
// add properties to generic error
err.expose = status < 500;
err.status = err.statusCode = status;
}
for (var key in props) {
if (key !== 'status' && key !== 'statusCode') {
err[key] = props[key];
}
}
return err;
}
/**
* Create HTTP error abstract base class.
* @private
*/
function createHttpErrorConstructor() {
function HttpError() {
throw new TypeError('cannot construct abstract class');
}
inherits(HttpError, Error);
return HttpError;
}
/**
* Create a constructor for a client error.
* @private
*/
function createClientErrorConstructor(HttpError, name, code) {
var className = toClassName(name);
function ClientError(message) {
// create the error object
var msg = message != null ? message : statuses.message[code];
var err = new Error(msg);
// capture a stack trace to the construction point
Error.captureStackTrace(err, ClientError);
// adjust the [[Prototype]]
setPrototypeOf(err, ClientError.prototype);
// redefine the error message
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true,
});
// redefine the error name
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true,
});
return err;
}
inherits(ClientError, HttpError);
nameFunc(ClientError, className);
ClientError.prototype.status = code;
ClientError.prototype.statusCode = code;
ClientError.prototype.expose = true;
return ClientError;
}
/**
* Create function to test is a value is a HttpError.
* @private
*/
function createIsHttpErrorFunction(HttpError) {
return function isHttpError(val) {
if (!val || typeof val !== 'object') {
return false;
}
if (val instanceof HttpError) {
return true;
}
return (
val instanceof Error &&
typeof val.expose === 'boolean' &&
typeof val.statusCode === 'number' &&
val.status === val.statusCode
);
};
}
/**
* Create a constructor for a server error.
* @private
*/
function createServerErrorConstructor(HttpError, name, code) {
var className = toClassName(name);
function ServerError(message) {
// create the error object
var msg = message != null ? message : statuses.message[code];
var err = new Error(msg);
// capture a stack trace to the construction point
Error.captureStackTrace(err, ServerError);
// adjust the [[Prototype]]
setPrototypeOf(err, ServerError.prototype);
// redefine the error message
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true,
});
// redefine the error name
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true,
});
return err;
}
inherits(ServerError, HttpError);
nameFunc(ServerError, className);
ServerError.prototype.status = code;
ServerError.prototype.statusCode = code;
ServerError.prototype.expose = false;
return ServerError;
}
/**
* Set the name of a function, if possible.
* @private
*/
function nameFunc(func, name) {
var desc = Object.getOwnPropertyDescriptor(func, 'name');
if (desc && desc.configurable) {
desc.value = name;
Object.defineProperty(func, 'name', desc);
}
}
/**
* Populate the exports object with constructors for every error class.
* @private
*/
function populateConstructorExports(exports, codes, HttpError) {
codes.forEach(function forEachCode(code) {
var CodeError;
var name = toIdentifier(statuses.message[code]);
switch (codeClass(code)) {
case 400:
CodeError = createClientErrorConstructor(HttpError, name, code);
break;
case 500:
CodeError = createServerErrorConstructor(HttpError, name, code);
break;
}
if (CodeError) {
// export the constructor
exports[code] = CodeError;
exports[name] = CodeError;
}
});
}
/**
* Get a class name from a name identifier.
* @private
*/
function toClassName(name) {
return name.substr(-5) !== 'Error' ? name + 'Error' : name;
}