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.

266 lines
5.7 KiB

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