index.js 6.2 KB

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