-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.ts
More file actions
115 lines (100 loc) · 4.29 KB
/
Copy patherrors.ts
File metadata and controls
115 lines (100 loc) · 4.29 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
/**
* Defines the service error taxonomy and upstream-code sanitization boundary.
*
* The four intentional error classes split failures by audience: callers see
* `ClientError`, operators see `ConfigurationError`, pipeline steps degrade
* provider failures through `UpstreamError`, and unexpected bugs become
* `InternalError`. `UpstreamError` owns the sanitizeUpstreamCode security
* boundary so untrusted external content cannot leak arbitrary identifiers into
* logs or rendered diagnostics.
*/
const MAX_UPSTREAM_CODE_LENGTH = 40;
/** Base class for every error this service raises intentionally. */
export abstract class BaseError extends Error {
public readonly code: string;
public readonly details?: unknown;
/** Creates a base error with a stable string code. */
constructor(message: string, code: string, details?: unknown, cause?: unknown) {
super(message, cause === undefined ? undefined : { cause });
this.name = new.target.name;
this.code = code;
this.details = details;
}
/** Returns the safe diagnostic representation for rendered failure reports. */
toDiagnosticString(): string {
return this.code;
}
}
/** Represents a 4xx-class failure caused by the caller's request. */
export class ClientError extends BaseError {
public readonly statusCode: number;
/** Creates a client error with an HTTP 4xx status. */
constructor(message: string, code: string, statusCode: number, details?: unknown) {
super(message, code, details);
if (statusCode < 400 || statusCode >= 500) throw new Error(`ClientError statusCode must be 4xx, got ${statusCode}`);
this.statusCode = statusCode;
}
/** Returns a caller-safe diagnostic string. */
override toDiagnosticString(): string {
return `${this.code}: ${this.message}`;
}
}
/** Represents invalid operator-provided startup or app assembly configuration. */
export class ConfigurationError extends BaseError {
/** Creates a configuration error with an optional stable code. */
constructor(message: string, code = "configuration_error", details?: unknown) {
super(message, code, details, extractCause(details));
}
}
/**
* Represents a degradable failure from an external service. The raw
* upstream code is sanitized at construction so producers cannot forget
* the sanitizeUpstreamCode security boundary.
*/
export class UpstreamError extends BaseError {
public readonly upstreamCode: string;
public readonly upstreamStatus?: number;
/** Creates an upstream error with a sanitized upstream code. */
constructor(
message: string,
rawCode: unknown,
details?: { readonly upstreamStatus?: number; readonly cause?: unknown }
) {
super(message, "upstream_error", undefined, details?.cause);
this.upstreamCode = sanitizeUpstreamCode(rawCode);
this.upstreamStatus = details?.upstreamStatus;
}
/** Returns a diagnostic string with only sanitized upstream identifiers. */
override toDiagnosticString(): string {
return `${this.code}: ${this.upstreamCode}`;
}
}
/** Represents a bug or unexpected condition that should produce a 500. */
export class InternalError extends BaseError {
/** Creates an internal error with an optional stable code. */
constructor(message: string, code = "internal_error", details?: unknown) {
super(message, code, details, extractCause(details));
}
}
/** Bounds and slugifies upstream error identifiers before diagnostics or logs. */
export function sanitizeUpstreamCode(value: unknown): string {
const raw = value instanceof Error ? value.message : String(value ?? "unknown_error");
const slug = raw
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
return (slug || "unknown_error").slice(0, MAX_UPSTREAM_CODE_LENGTH);
}
/** Detects DOM abort errors produced by fetch-compatible APIs. */
export function isAbortError(error: unknown): boolean {
return (
(error instanceof DOMException && error.name === "AbortError") ||
(error instanceof Error && error.name === "AbortError")
);
}
/** Extracts a native error cause from detail payloads used by internal errors. */
function extractCause(details: unknown): unknown {
if (details instanceof Error) return details;
if (!details || typeof details !== "object" || !("cause" in details)) return undefined;
return (details as { readonly cause?: unknown }).cause;
}