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