index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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. if (arg instanceof Error) {
  48. err = arg
  49. status = err.status || err.statusCode || status
  50. continue
  51. }
  52. switch (typeof arg) {
  53. case 'string':
  54. msg = arg
  55. break
  56. case 'number':
  57. status = arg
  58. if (i !== 0) {
  59. deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)')
  60. }
  61. break
  62. case 'object':
  63. props = arg
  64. break
  65. }
  66. }
  67. if (typeof status === 'number' && (status < 400 || status >= 600)) {
  68. deprecate('non-error status code; use only 4xx or 5xx status codes')
  69. }
  70. if (typeof status !== 'number' ||
  71. (!statuses[status] && (status < 400 || status >= 600))) {
  72. status = 500
  73. }
  74. // constructor
  75. var HttpError = createError[status] || createError[codeClass(status)]
  76. if (!err) {
  77. // create error
  78. err = HttpError
  79. ? new HttpError(msg)
  80. : new Error(msg || statuses[status])
  81. Error.captureStackTrace(err, createError)
  82. }
  83. if (!HttpError || !(err instanceof HttpError) || err.status !== status) {
  84. // add properties to generic error
  85. err.expose = status < 500
  86. err.status = err.statusCode = status
  87. }
  88. for (var key in props) {
  89. if (key !== 'status' && key !== 'statusCode') {
  90. err[key] = props[key]
  91. }
  92. }
  93. return err
  94. }
  95. /**
  96. * Create HTTP error abstract base class.
  97. * @private
  98. */
  99. function createHttpErrorConstructor () {
  100. function HttpError () {
  101. throw new TypeError('cannot construct abstract class')
  102. }
  103. inherits(HttpError, Error)
  104. return HttpError
  105. }
  106. /**
  107. * Create a constructor for a client error.
  108. * @private
  109. */
  110. function createClientErrorConstructor (HttpError, name, code) {
  111. var className = toClassName(name)
  112. function ClientError (message) {
  113. // create the error object
  114. var msg = message != null ? message : statuses[code]
  115. var err = new Error(msg)
  116. // capture a stack trace to the construction point
  117. Error.captureStackTrace(err, ClientError)
  118. // adjust the [[Prototype]]
  119. setPrototypeOf(err, ClientError.prototype)
  120. // redefine the error message
  121. Object.defineProperty(err, 'message', {
  122. enumerable: true,
  123. configurable: true,
  124. value: msg,
  125. writable: true
  126. })
  127. // redefine the error name
  128. Object.defineProperty(err, 'name', {
  129. enumerable: false,
  130. configurable: true,
  131. value: className,
  132. writable: true
  133. })
  134. return err
  135. }
  136. inherits(ClientError, HttpError)
  137. nameFunc(ClientError, className)
  138. ClientError.prototype.status = code
  139. ClientError.prototype.statusCode = code
  140. ClientError.prototype.expose = true
  141. return ClientError
  142. }
  143. /**
  144. * Create function to test is a value is a HttpError.
  145. * @private
  146. */
  147. function createIsHttpErrorFunction (HttpError) {
  148. return function isHttpError (val) {
  149. if (!val || typeof val !== 'object') {
  150. return false
  151. }
  152. if (val instanceof HttpError) {
  153. return true
  154. }
  155. return val instanceof Error &&
  156. typeof val.expose === 'boolean' &&
  157. typeof val.statusCode === 'number' && val.status === val.statusCode
  158. }
  159. }
  160. /**
  161. * Create a constructor for a server error.
  162. * @private
  163. */
  164. function createServerErrorConstructor (HttpError, name, code) {
  165. var className = toClassName(name)
  166. function ServerError (message) {
  167. // create the error object
  168. var msg = message != null ? message : statuses[code]
  169. var err = new Error(msg)
  170. // capture a stack trace to the construction point
  171. Error.captureStackTrace(err, ServerError)
  172. // adjust the [[Prototype]]
  173. setPrototypeOf(err, ServerError.prototype)
  174. // redefine the error message
  175. Object.defineProperty(err, 'message', {
  176. enumerable: true,
  177. configurable: true,
  178. value: msg,
  179. writable: true
  180. })
  181. // redefine the error name
  182. Object.defineProperty(err, 'name', {
  183. enumerable: false,
  184. configurable: true,
  185. value: className,
  186. writable: true
  187. })
  188. return err
  189. }
  190. inherits(ServerError, HttpError)
  191. nameFunc(ServerError, className)
  192. ServerError.prototype.status = code
  193. ServerError.prototype.statusCode = code
  194. ServerError.prototype.expose = false
  195. return ServerError
  196. }
  197. /**
  198. * Set the name of a function, if possible.
  199. * @private
  200. */
  201. function nameFunc (func, name) {
  202. var desc = Object.getOwnPropertyDescriptor(func, 'name')
  203. if (desc && desc.configurable) {
  204. desc.value = name
  205. Object.defineProperty(func, 'name', desc)
  206. }
  207. }
  208. /**
  209. * Populate the exports object with constructors for every error class.
  210. * @private
  211. */
  212. function populateConstructorExports (exports, codes, HttpError) {
  213. codes.forEach(function forEachCode (code) {
  214. var CodeError
  215. var name = toIdentifier(statuses[code])
  216. switch (codeClass(code)) {
  217. case 400:
  218. CodeError = createClientErrorConstructor(HttpError, name, code)
  219. break
  220. case 500:
  221. CodeError = createServerErrorConstructor(HttpError, name, code)
  222. break
  223. }
  224. if (CodeError) {
  225. // export the constructor
  226. exports[code] = CodeError
  227. exports[name] = CodeError
  228. }
  229. })
  230. // backwards-compatibility
  231. exports["I'mateapot"] = deprecate.function(exports.ImATeapot,
  232. '"I\'mateapot"; use "ImATeapot" instead')
  233. }
  234. /**
  235. * Get a class name from a name identifier.
  236. * @private
  237. */
  238. function toClassName (name) {
  239. return name.substr(-5) !== 'Error'
  240. ? name + 'Error'
  241. : name
  242. }