You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 lines
5.6 KiB

  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. /**
  17. * Module exports.
  18. * @public
  19. */
  20. module.exports = createError
  21. module.exports.HttpError = createHttpErrorConstructor()
  22. // Populate exports for all constructors
  23. populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError)
  24. /**
  25. * Get the code class of a status code.
  26. * @private
  27. */
  28. function codeClass (status) {
  29. return Number(String(status).charAt(0) + '00')
  30. }
  31. /**
  32. * Create a new HTTP Error.
  33. *
  34. * @returns {Error}
  35. * @public
  36. */
  37. function createError () {
  38. // so much arity going on ~_~
  39. var err
  40. var msg
  41. var status = 500
  42. var props = {}
  43. for (var i = 0; i < arguments.length; i++) {
  44. var arg = arguments[i]
  45. if (arg instanceof Error) {
  46. err = arg
  47. status = err.status || err.statusCode || status
  48. continue
  49. }
  50. switch (typeof arg) {
  51. case 'string':
  52. msg = arg
  53. break
  54. case 'number':
  55. status = arg
  56. if (i !== 0) {
  57. deprecate('non-first-argument status code; replace with createError(' + arg + ', ...)')
  58. }
  59. break
  60. case 'object':
  61. props = arg
  62. break
  63. }
  64. }
  65. if (typeof status === 'number' && (status < 400 || status >= 600)) {
  66. deprecate('non-error status code; use only 4xx or 5xx status codes')
  67. }
  68. if (typeof status !== 'number' ||
  69. (!statuses[status] && (status < 400 || status >= 600))) {
  70. status = 500
  71. }
  72. // constructor
  73. var HttpError = createError[status] || createError[codeClass(status)]
  74. if (!err) {
  75. // create error
  76. err = HttpError
  77. ? new HttpError(msg)
  78. : new Error(msg || statuses[status])
  79. Error.captureStackTrace(err, createError)
  80. }
  81. if (!HttpError || !(err instanceof HttpError) || err.status !== status) {
  82. // add properties to generic error
  83. err.expose = status < 500
  84. err.status = err.statusCode = status
  85. }
  86. for (var key in props) {
  87. if (key !== 'status' && key !== 'statusCode') {
  88. err[key] = props[key]
  89. }
  90. }
  91. return err
  92. }
  93. /**
  94. * Create HTTP error abstract base class.
  95. * @private
  96. */
  97. function createHttpErrorConstructor () {
  98. function HttpError () {
  99. throw new TypeError('cannot construct abstract class')
  100. }
  101. inherits(HttpError, Error)
  102. return HttpError
  103. }
  104. /**
  105. * Create a constructor for a client error.
  106. * @private
  107. */
  108. function createClientErrorConstructor (HttpError, name, code) {
  109. var className = name.match(/Error$/) ? name : name + 'Error'
  110. function ClientError (message) {
  111. // create the error object
  112. var msg = message != null ? message : statuses[code]
  113. var err = new Error(msg)
  114. // capture a stack trace to the construction point
  115. Error.captureStackTrace(err, ClientError)
  116. // adjust the [[Prototype]]
  117. setPrototypeOf(err, ClientError.prototype)
  118. // redefine the error message
  119. Object.defineProperty(err, 'message', {
  120. enumerable: true,
  121. configurable: true,
  122. value: msg,
  123. writable: true
  124. })
  125. // redefine the error name
  126. Object.defineProperty(err, 'name', {
  127. enumerable: false,
  128. configurable: true,
  129. value: className,
  130. writable: true
  131. })
  132. return err
  133. }
  134. inherits(ClientError, HttpError)
  135. ClientError.prototype.status = code
  136. ClientError.prototype.statusCode = code
  137. ClientError.prototype.expose = true
  138. return ClientError
  139. }
  140. /**
  141. * Create a constructor for a server error.
  142. * @private
  143. */
  144. function createServerErrorConstructor (HttpError, name, code) {
  145. var className = name.match(/Error$/) ? name : name + 'Error'
  146. function ServerError (message) {
  147. // create the error object
  148. var msg = message != null ? message : statuses[code]
  149. var err = new Error(msg)
  150. // capture a stack trace to the construction point
  151. Error.captureStackTrace(err, ServerError)
  152. // adjust the [[Prototype]]
  153. setPrototypeOf(err, ServerError.prototype)
  154. // redefine the error message
  155. Object.defineProperty(err, 'message', {
  156. enumerable: true,
  157. configurable: true,
  158. value: msg,
  159. writable: true
  160. })
  161. // redefine the error name
  162. Object.defineProperty(err, 'name', {
  163. enumerable: false,
  164. configurable: true,
  165. value: className,
  166. writable: true
  167. })
  168. return err
  169. }
  170. inherits(ServerError, HttpError)
  171. ServerError.prototype.status = code
  172. ServerError.prototype.statusCode = code
  173. ServerError.prototype.expose = false
  174. return ServerError
  175. }
  176. /**
  177. * Populate the exports object with constructors for every error class.
  178. * @private
  179. */
  180. function populateConstructorExports (exports, codes, HttpError) {
  181. codes.forEach(function forEachCode (code) {
  182. var CodeError
  183. var name = toIdentifier(statuses[code])
  184. switch (codeClass(code)) {
  185. case 400:
  186. CodeError = createClientErrorConstructor(HttpError, name, code)
  187. break
  188. case 500:
  189. CodeError = createServerErrorConstructor(HttpError, name, code)
  190. break
  191. }
  192. if (CodeError) {
  193. // export the constructor
  194. exports[code] = CodeError
  195. exports[name] = CodeError
  196. }
  197. })
  198. // backwards-compatibility
  199. exports["I'mateapot"] = deprecate.function(exports.ImATeapot,
  200. '"I\'mateapot"; use "ImATeapot" instead')
  201. }
  202. /**
  203. * Convert a string of words to a JavaScript identifier.
  204. * @private
  205. */
  206. function toIdentifier (str) {
  207. return str.split(' ').map(function (token) {
  208. return token.slice(0, 1).toUpperCase() + token.slice(1)
  209. }).join('').replace(/[^ _0-9a-z]/gi, '')
  210. }