index.js
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 module.exports.isHttpError = createIsHttpErrorFunction(module.exports.HttpError) 29 30 // Populate exports for all constructors 31 populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError) 32 33 /** 34 * Get the code class of a status code. 35 * @private 36 */ 37 38 function codeClass (status) { 39 return Number(String(status).charAt(0) + '00') 40 } 41 42 /** 43 * Create a new HTTP Error. 44 * 45 * @returns {Error} 46 * @public 47 */ 48 49 function createError () { 50 // so much arity going on ~_~ 51 var err 52 var msg 53 var status = 500 54 var props = {} 55 for (var i = 0; i < arguments.length; i++) { 56 var arg = arguments[i] 57 var type = typeof arg 58 if (type === 'object' && arg instanceof Error) { 59 err = arg 60 status = err.status || err.statusCode || status 61 } else if (type === 'number' && i === 0) { 62 status = arg 63 } else if (type === 'string') { 64 msg = arg 65 } else if (type === 'object') { 66 props = arg 67 } else { 68 throw new TypeError('argument #' + (i + 1) + ' unsupported type ' + type) 69 } 70 } 71 72 if (typeof status === 'number' && (status < 400 || status >= 600)) { 73 deprecate('non-error status code; use only 4xx or 5xx status codes') 74 } 75 76 if (typeof status !== 'number' || 77 (!statuses.message[status] && (status < 400 || status >= 600))) { 78 status = 500 79 } 80 81 // constructor 82 var HttpError = createError[status] || createError[codeClass(status)] 83 84 if (!err) { 85 // create error 86 err = HttpError 87 ? new HttpError(msg) 88 : new Error(msg || statuses.message[status]) 89 Error.captureStackTrace(err, createError) 90 } 91 92 if (!HttpError || !(err instanceof HttpError) || err.status !== status) { 93 // add properties to generic error 94 err.expose = status < 500 95 err.status = err.statusCode = status 96 } 97 98 for (var key in props) { 99 if (key !== 'status' && key !== 'statusCode') { 100 err[key] = props[key] 101 } 102 } 103 104 return err 105 } 106 107 /** 108 * Create HTTP error abstract base class. 109 * @private 110 */ 111 112 function createHttpErrorConstructor () { 113 function HttpError () { 114 throw new TypeError('cannot construct abstract class') 115 } 116 117 inherits(HttpError, Error) 118 119 return HttpError 120 } 121 122 /** 123 * Create a constructor for a client error. 124 * @private 125 */ 126 127 function createClientErrorConstructor (HttpError, name, code) { 128 var className = toClassName(name) 129 130 function ClientError (message) { 131 // create the error object 132 var msg = message != null ? message : statuses.message[code] 133 var err = new Error(msg) 134 135 // capture a stack trace to the construction point 136 Error.captureStackTrace(err, ClientError) 137 138 // adjust the [[Prototype]] 139 setPrototypeOf(err, ClientError.prototype) 140 141 // redefine the error message 142 Object.defineProperty(err, 'message', { 143 enumerable: true, 144 configurable: true, 145 value: msg, 146 writable: true 147 }) 148 149 // redefine the error name 150 Object.defineProperty(err, 'name', { 151 enumerable: false, 152 configurable: true, 153 value: className, 154 writable: true 155 }) 156 157 return err 158 } 159 160 inherits(ClientError, HttpError) 161 nameFunc(ClientError, className) 162 163 ClientError.prototype.status = code 164 ClientError.prototype.statusCode = code 165 ClientError.prototype.expose = true 166 167 return ClientError 168 } 169 170 /** 171 * Create function to test is a value is a HttpError. 172 * @private 173 */ 174 175 function createIsHttpErrorFunction (HttpError) { 176 return function isHttpError (val) { 177 if (!val || typeof val !== 'object') { 178 return false 179 } 180 181 if (val instanceof HttpError) { 182 return true 183 } 184 185 return val instanceof Error && 186 typeof val.expose === 'boolean' && 187 typeof val.statusCode === 'number' && val.status === val.statusCode 188 } 189 } 190 191 /** 192 * Create a constructor for a server error. 193 * @private 194 */ 195 196 function createServerErrorConstructor (HttpError, name, code) { 197 var className = toClassName(name) 198 199 function ServerError (message) { 200 // create the error object 201 var msg = message != null ? message : statuses.message[code] 202 var err = new Error(msg) 203 204 // capture a stack trace to the construction point 205 Error.captureStackTrace(err, ServerError) 206 207 // adjust the [[Prototype]] 208 setPrototypeOf(err, ServerError.prototype) 209 210 // redefine the error message 211 Object.defineProperty(err, 'message', { 212 enumerable: true, 213 configurable: true, 214 value: msg, 215 writable: true 216 }) 217 218 // redefine the error name 219 Object.defineProperty(err, 'name', { 220 enumerable: false, 221 configurable: true, 222 value: className, 223 writable: true 224 }) 225 226 return err 227 } 228 229 inherits(ServerError, HttpError) 230 nameFunc(ServerError, className) 231 232 ServerError.prototype.status = code 233 ServerError.prototype.statusCode = code 234 ServerError.prototype.expose = false 235 236 return ServerError 237 } 238 239 /** 240 * Set the name of a function, if possible. 241 * @private 242 */ 243 244 function nameFunc (func, name) { 245 var desc = Object.getOwnPropertyDescriptor(func, 'name') 246 247 if (desc && desc.configurable) { 248 desc.value = name 249 Object.defineProperty(func, 'name', desc) 250 } 251 } 252 253 /** 254 * Populate the exports object with constructors for every error class. 255 * @private 256 */ 257 258 function populateConstructorExports (exports, codes, HttpError) { 259 codes.forEach(function forEachCode (code) { 260 var CodeError 261 var name = toIdentifier(statuses.message[code]) 262 263 switch (codeClass(code)) { 264 case 400: 265 CodeError = createClientErrorConstructor(HttpError, name, code) 266 break 267 case 500: 268 CodeError = createServerErrorConstructor(HttpError, name, code) 269 break 270 } 271 272 if (CodeError) { 273 // export the constructor 274 exports[code] = CodeError 275 exports[name] = CodeError 276 } 277 }) 278 } 279 280 /** 281 * Get a class name from a name identifier. 282 * @private 283 */ 284 285 function toClassName (name) { 286 return name.substr(-5) !== 'Error' 287 ? name + 'Error' 288 : name 289 }