-
-
Notifications
You must be signed in to change notification settings - Fork 288
Expand file tree
/
Copy patherrorNormalization.ts
More file actions
114 lines (110 loc) · 3.42 KB
/
Copy patherrorNormalization.ts
File metadata and controls
114 lines (110 loc) · 3.42 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
/**
* A typed error surface: a stable code plus an optional human message and
* structured details. Consumers parameterise `Code` with their own error-code
* union (e.g. headless-buy codes) so the taxonomy stays with the consumer while
* the pure extraction below is shared.
*/
export type TypedError<Code extends string> = {
code: Code;
message?: string;
details?: Record<string, unknown>;
};
/**
* Type guard for a non-null object.
*
* @param value - The value to test.
* @returns Whether the value is a record.
*/
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
/**
* Best-effort human-readable message from an arbitrary thrown/native value.
*
* @param error - The caught value.
* @returns The message, or `undefined` when none can be derived.
*/
export function getErrorMessage(error: unknown): string | undefined {
if (error instanceof Error) {
return error.message;
}
if (isRecord(error) && typeof error.message === 'string') {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return undefined;
}
/**
* Extracts a caller-recognised typed error from an arbitrary thrown/native
* value, when the value carries an explicit, valid code on one of the given
* properties. Pure: performs no side effects and applies no fallback, so
* callers keep full control over precedence (e.g. domain-specific special
* cases) and the fallback code.
*
* @param error - The caught value.
* @param options - The options.
* @param options.isValidCode - Type guard identifying a recognised code.
* @param options.codeProperties - Property names to read a code from, in
* precedence order. Defaults to `['code']`.
* @returns The typed error when an explicit valid code is present, else
* `undefined`.
*/
export function extractExplicitTypedError<Code extends string>(
error: unknown,
{
isValidCode,
codeProperties = ['code'],
}: {
isValidCode: (value: unknown) => value is Code;
codeProperties?: string[];
},
): TypedError<Code> | undefined {
if (!isRecord(error)) {
return undefined;
}
for (const property of codeProperties) {
const candidate = error[property];
if (isValidCode(candidate)) {
return {
code: candidate,
message: getErrorMessage(error),
details: isRecord(error.details) ? error.details : undefined,
};
}
}
return undefined;
}
/**
* Normalises an arbitrary thrown/native value into a {@link TypedError}, using
* the caller's recognised codes and falling back to `fallbackCode` when no
* explicit valid code is present. Pure.
*
* @param error - The caught value.
* @param options - The options.
* @param options.isValidCode - Type guard identifying a recognised code.
* @param options.fallbackCode - Code used when no explicit valid code is found.
* @param options.codeProperties - Property names to read a code from, in
* precedence order. Defaults to `['code']`.
* @returns The typed error.
*/
export function normalizeToTypedError<Code extends string>(
error: unknown,
{
isValidCode,
fallbackCode,
codeProperties,
}: {
isValidCode: (value: unknown) => value is Code;
fallbackCode: Code;
codeProperties?: string[];
},
): TypedError<Code> {
return (
extractExplicitTypedError(error, { isValidCode, codeProperties }) ?? {
code: fallbackCode,
message: getErrorMessage(error),
}
);
}