generated from MetaMask/metamask-module-template
-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
assert.ts
153 lines (140 loc) · 4.3 KB
/
assert.ts
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { assert as assertSuperstruct, Struct } from 'superstruct';
export type AssertionErrorConstructor =
| (new (args: { message: string }) => Error)
| ((args: { message: string }) => Error);
/**
* Type guard for determining whether the given value is an error object with a
* `message` property, such as an instance of Error.
*
* @param error - The object to check.
* @returns True or false, depending on the result.
*/
function isErrorWithMessage(error: unknown): error is { message: string } {
return typeof error === 'object' && error !== null && 'message' in error;
}
/**
* Check if a value is a constructor, i.e., a function that can be called with
* the `new` keyword.
*
* @param fn - The value to check.
* @returns `true` if the value is a constructor, or `false` otherwise.
*/
function isConstructable(
fn: AssertionErrorConstructor,
): fn is new (args: { message: string }) => Error {
/* istanbul ignore next */
return Boolean(typeof fn?.prototype?.constructor?.name === 'string');
}
/**
* Get the error message from an unknown error object. If the error object has
* a `message` property, that property is returned. Otherwise, the stringified
* error object is returned.
*
* @param error - The error object to get the message from.
* @returns The error message.
*/
function getErrorMessage(error: unknown): string {
const message = isErrorWithMessage(error) ? error.message : String(error);
// If the error ends with a period, remove it, as we'll add our own period.
if (message.endsWith('.')) {
return message.slice(0, -1);
}
return message;
}
/**
* Initialise an {@link AssertionErrorConstructor} error.
*
* @param ErrorWrapper - The error class to use.
* @param message - The error message.
* @returns The error object.
*/
function getError(ErrorWrapper: AssertionErrorConstructor, message: string) {
if (isConstructable(ErrorWrapper)) {
return new ErrorWrapper({
message,
});
}
return ErrorWrapper({
message,
});
}
/**
* The default error class that is thrown if an assertion fails.
*/
export class AssertionError extends Error {
readonly code = 'ERR_ASSERTION';
constructor(options: { message: string }) {
super(options.message);
}
}
/**
* Same as Node.js assert.
* If the value is falsy, throws an error, does nothing otherwise.
*
* @throws {@link AssertionError} If value is falsy.
* @param value - The test that should be truthy to pass.
* @param message - Message to be passed to {@link AssertionError} or an
* {@link Error} instance to throw.
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}. If a custom error class is provided for
* the `message` argument, this argument is ignored.
*/
export function assert(
value: any,
message: string | Error = 'Assertion failed.',
ErrorWrapper: AssertionErrorConstructor = AssertionError,
): asserts value {
if (!value) {
if (message instanceof Error) {
throw message;
}
throw getError(ErrorWrapper, message);
}
}
/**
* Assert a value against a Superstruct struct.
*
* @param value - The value to validate.
* @param struct - The struct to validate against.
* @param errorPrefix - A prefix to add to the error message. Defaults to
* "Assertion failed".
* @param ErrorWrapper - The error class to throw if the assertion fails.
* Defaults to {@link AssertionError}.
* @throws If the value is not valid.
*/
export function assertStruct<T, S>(
value: unknown,
struct: Struct<T, S>,
errorPrefix = 'Assertion failed',
ErrorWrapper: AssertionErrorConstructor = AssertionError,
): asserts value is T {
try {
assertSuperstruct(value, struct);
} catch (error) {
throw getError(ErrorWrapper, `${errorPrefix}: ${getErrorMessage(error)}.`);
}
}
/**
* Use in the default case of a switch that you want to be fully exhaustive.
* Using this function forces the compiler to enforce exhaustivity during
* compile-time.
*
* @example
* ```
* const number = 1;
* switch (number) {
* case 0:
* ...
* case 1:
* ...
* default:
* assertExhaustive(snapPrefix);
* }
* ```
* @param _object - The object on which the switch is being operated.
*/
export function assertExhaustive(_object: never): never {
throw new Error(
'Invalid branch reached. Should be detected during compilation.',
);
}