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  }