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.
299 lines
6.4 KiB
299 lines
6.4 KiB
/*! |
|
* 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] |
|
if (arg instanceof Error) { |
|
err = arg |
|
status = err.status || err.statusCode || status |
|
continue |
|
} |
|
switch (typeof arg) { |
|
case 'string': |
|
msg = arg |
|
break |
|
case 'number': |
|
status = arg |
|
if (i !== 0) { |
|
deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)') |
|
} |
|
break |
|
case 'object': |
|
props = arg |
|
break |
|
} |
|
} |
|
|
|
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[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[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[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[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[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 |
|
} |
|
}) |
|
|
|
// backwards-compatibility |
|
exports["I'mateapot"] = deprecate.function(exports.ImATeapot, |
|
'"I\'mateapot"; use "ImATeapot" instead') |
|
} |
|
|
|
/** |
|
* Get a class name from a name identifier. |
|
* @private |
|
*/ |
|
|
|
function toClassName (name) { |
|
return name.substr(-5) !== 'Error' |
|
? name + 'Error' |
|
: name |
|
}
|
|
|