index.js (5878B)
1 /*! 2 * http-errors 3 * Copyright(c) 2014 Jonathan Ong 4 * Copyright(c) 2016 Douglas Christopher Wilson 5 * MIT Licensed 6 */ 7 8 'use strict' 9 10 /** 11 * Module dependencies. 12 * @private 13 */ 14 15 var deprecate = require('depd')('http-errors') 16 var setPrototypeOf = require('setprototypeof') 17 var statuses = require('statuses') 18 var inherits = require('inherits') 19 var toIdentifier = require('toidentifier') 20 21 /** 22 * Module exports. 23 * @public 24 */ 25 26 module.exports = createError 27 module.exports.HttpError = createHttpErrorConstructor() 28 29 // Populate exports for all constructors 30 populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError) 31 32 /** 33 * Get the code class of a status code. 34 * @private 35 */ 36 37 function codeClass (status) { 38 return Number(String(status).charAt(0) + '00') 39 } 40 41 /** 42 * Create a new HTTP Error. 43 * 44 * @returns {Error} 45 * @public 46 */ 47 48 function createError () { 49 // so much arity going on ~_~ 50 var err 51 var msg 52 var status = 500 53 var props = {} 54 for (var i = 0; i < arguments.length; i++) { 55 var arg = arguments[i] 56 if (arg instanceof Error) { 57 err = arg 58 status = err.status || err.statusCode || status 59 continue 60 } 61 switch (typeof arg) { 62 case 'string': 63 msg = arg 64 break 65 case 'number': 66 status = arg 67 if (i !== 0) { 68 deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)') 69 } 70 break 71 case 'object': 72 props = arg 73 break 74 } 75 } 76 77 if (typeof status === 'number' && (status < 400 || status >= 600)) { 78 deprecate('non-error status code; use only 4xx or 5xx status codes') 79 } 80 81 if (typeof status !== 'number' || 82 (!statuses[status] && (status < 400 || status >= 600))) { 83 status = 500 84 } 85 86 // constructor 87 var HttpError = createError[status] || createError[codeClass(status)] 88 89 if (!err) { 90 // create error 91 err = HttpError 92 ? new HttpError(msg) 93 : new Error(msg || statuses[status]) 94 Error.captureStackTrace(err, createError) 95 } 96 97 if (!HttpError || !(err instanceof HttpError) || err.status !== status) { 98 // add properties to generic error 99 err.expose = status < 500 100 err.status = err.statusCode = status 101 } 102 103 for (var key in props) { 104 if (key !== 'status' && key !== 'statusCode') { 105 err[key] = props[key] 106 } 107 } 108 109 return err 110 } 111 112 /** 113 * Create HTTP error abstract base class. 114 * @private 115 */ 116 117 function createHttpErrorConstructor () { 118 function HttpError () { 119 throw new TypeError('cannot construct abstract class') 120 } 121 122 inherits(HttpError, Error) 123 124 return HttpError 125 } 126 127 /** 128 * Create a constructor for a client error. 129 * @private 130 */ 131 132 function createClientErrorConstructor (HttpError, name, code) { 133 var className = name.match(/Error$/) ? name : name + 'Error' 134 135 function ClientError (message) { 136 // create the error object 137 var msg = message != null ? message : statuses[code] 138 var err = new Error(msg) 139 140 // capture a stack trace to the construction point 141 Error.captureStackTrace(err, ClientError) 142 143 // adjust the [[Prototype]] 144 setPrototypeOf(err, ClientError.prototype) 145 146 // redefine the error message 147 Object.defineProperty(err, 'message', { 148 enumerable: true, 149 configurable: true, 150 value: msg, 151 writable: true 152 }) 153 154 // redefine the error name 155 Object.defineProperty(err, 'name', { 156 enumerable: false, 157 configurable: true, 158 value: className, 159 writable: true 160 }) 161 162 return err 163 } 164 165 inherits(ClientError, HttpError) 166 nameFunc(ClientError, className) 167 168 ClientError.prototype.status = code 169 ClientError.prototype.statusCode = code 170 ClientError.prototype.expose = true 171 172 return ClientError 173 } 174 175 /** 176 * Create a constructor for a server error. 177 * @private 178 */ 179 180 function createServerErrorConstructor (HttpError, name, code) { 181 var className = name.match(/Error$/) ? name : name + 'Error' 182 183 function ServerError (message) { 184 // create the error object 185 var msg = message != null ? message : statuses[code] 186 var err = new Error(msg) 187 188 // capture a stack trace to the construction point 189 Error.captureStackTrace(err, ServerError) 190 191 // adjust the [[Prototype]] 192 setPrototypeOf(err, ServerError.prototype) 193 194 // redefine the error message 195 Object.defineProperty(err, 'message', { 196 enumerable: true, 197 configurable: true, 198 value: msg, 199 writable: true 200 }) 201 202 // redefine the error name 203 Object.defineProperty(err, 'name', { 204 enumerable: false, 205 configurable: true, 206 value: className, 207 writable: true 208 }) 209 210 return err 211 } 212 213 inherits(ServerError, HttpError) 214 nameFunc(ServerError, className) 215 216 ServerError.prototype.status = code 217 ServerError.prototype.statusCode = code 218 ServerError.prototype.expose = false 219 220 return ServerError 221 } 222 223 /** 224 * Set the name of a function, if possible. 225 * @private 226 */ 227 228 function nameFunc (func, name) { 229 var desc = Object.getOwnPropertyDescriptor(func, 'name') 230 231 if (desc && desc.configurable) { 232 desc.value = name 233 Object.defineProperty(func, 'name', desc) 234 } 235 } 236 237 /** 238 * Populate the exports object with constructors for every error class. 239 * @private 240 */ 241 242 function populateConstructorExports (exports, codes, HttpError) { 243 codes.forEach(function forEachCode (code) { 244 var CodeError 245 var name = toIdentifier(statuses[code]) 246 247 switch (codeClass(code)) { 248 case 400: 249 CodeError = createClientErrorConstructor(HttpError, name, code) 250 break 251 case 500: 252 CodeError = createServerErrorConstructor(HttpError, name, code) 253 break 254 } 255 256 if (CodeError) { 257 // export the constructor 258 exports[code] = CodeError 259 exports[name] = CodeError 260 } 261 }) 262 263 // backwards-compatibility 264 exports["I'mateapot"] = deprecate.function(exports.ImATeapot, 265 '"I\'mateapot"; use "ImATeapot" instead') 266 }