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  
 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  }