index.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use strict'
  2. type PotentialError = Errlop | Error | ErrorCodeHolder | string
  3. interface ErrorCodeHolder {
  4. exitCode?: string | number
  5. errno?: string | number
  6. code?: string | number
  7. }
  8. /** Only accept codes that are numbers, otherwise discard them */
  9. function parseCode(code: any): number | null {
  10. const number = Number(code)
  11. if (isNaN(number)) return null
  12. return number
  13. }
  14. /** Fetch the code from the value */
  15. function fetchCode(value: any): string | number | null {
  16. return (
  17. value &&
  18. (parseCode(value.exitCode) ||
  19. parseCode(value.errno) ||
  20. parseCode(value.code))
  21. )
  22. }
  23. /** Prevent [a weird error on node version 4](https://github.com/bevry/errlop/issues/1) and below. */
  24. function isValid(value: any): boolean {
  25. /* eslint no-use-before-define:0 */
  26. return value instanceof Error || Errlop.isErrlop(value)
  27. }
  28. export default class Errlop extends Error {
  29. /** Duck typing as node 4 and intanceof does not work for error extensions */
  30. public klass: typeof Errlop
  31. /**
  32. * The parent error if it was provided.
  33. * If a parent was provided, then use that, otherwise use the input's parent, if it exists.
  34. */
  35. public parent?: Errlop | Error
  36. /** An array of all the ancestors. From parent, to grand parent, and so on. */
  37. public ancestors: Array<Errlop | Error>
  38. /**
  39. * A numeric code to use for the exit status if desired by the consumer.
  40. * It cycles through [input, this, ...ancestors] until it finds the first [exitCode, errno, code] that is valid.
  41. */
  42. public exitCode?: string | number
  43. /**
  44. * The stack for our instance alone, without any parents.
  45. * If the input contained a stack, then use that.
  46. */
  47. public orphanStack: string
  48. /**
  49. * The stack which now contains the accumalated stacks of its ancestors.
  50. * This is used instead of an alias like `fullStack` or the like, to ensure existing code that uses `err.stack` doesn't need to be changed to remain functional.
  51. */
  52. public stack: string
  53. /**
  54. * Syntatic sugar for Errlop class creation.
  55. * Enables `Errlop.create(...args)` to achieve `new Errlop(...args)`
  56. */
  57. static create(input: PotentialError, parent?: Errlop | Error): Errlop {
  58. return new this(input, parent)
  59. }
  60. /**
  61. * Create an instance of an error, using a message, as well as an optional parent.
  62. * If the parent is provided, then the `fullStack` property will include its stack too
  63. */
  64. constructor(input: PotentialError, parent?: Errlop | Error) {
  65. if (!input) throw new Error('Attempted to create an Errlop without a input')
  66. // Instantiate with the above
  67. super((input as any).message || input)
  68. // Apply
  69. this.klass = Errlop
  70. this.parent = parent || (input as Errlop).parent
  71. this.ancestors = []
  72. let ancestor = this.parent
  73. while (ancestor) {
  74. this.ancestors.push(ancestor)
  75. ancestor = (ancestor as Errlop).parent
  76. }
  77. // this code must support node 0.8, as well as prevent a weird bug in node v4
  78. // https://travis-ci.org/bevry/editions/jobs/408828147
  79. let exitCode = fetchCode(input)
  80. if (exitCode == null) exitCode = fetchCode(this)
  81. for (
  82. let index = 0;
  83. index < this.ancestors.length && exitCode == null;
  84. ++index
  85. ) {
  86. const error = this.ancestors[index]
  87. if (isValid(error)) exitCode = fetchCode(error)
  88. }
  89. // Apply
  90. if (exitCode != null) {
  91. this.exitCode = exitCode
  92. }
  93. this.orphanStack = ((input as any).stack || (this as any).stack).toString()
  94. this.stack = this.ancestors.reduce<string>(
  95. (accumulator, error) =>
  96. `${accumulator}\n↳ ${
  97. (error as Errlop).orphanStack || (error as Error).stack || error
  98. }`,
  99. this.orphanStack
  100. )
  101. }
  102. /** Check whether or not the value is an Errlop instance */
  103. static isErrlop(value: any): boolean {
  104. return value && (value instanceof this || value.klass === this)
  105. }
  106. /** Ensure that the value is an Errlop instance */
  107. static ensure(value: any): Errlop {
  108. return this.isErrlop(value) ? value : this.create(value)
  109. }
  110. }