
























@@ -0,0 +1,152 @@
1+export { asFiniteNumber } from "../shared/number-coercion.js";
2+import { normalizeOptionalString as trimToUndefined } from "../shared/string-coerce.js";
3+export { normalizeOptionalString as trimToUndefined } from "../shared/string-coerce.js";
4+5+export function asBoolean(value: unknown): boolean | undefined {
6+return typeof value === "boolean" ? value : undefined;
7+}
8+9+export function asObject(value: unknown): Record<string, unknown> | undefined {
10+return typeof value === "object" && value !== null && !Array.isArray(value)
11+ ? (value as Record<string, unknown>)
12+ : undefined;
13+}
14+15+export function truncateErrorDetail(detail: string, limit = 220): string {
16+return detail.length <= limit ? detail : `${detail.slice(0, limit - 1)}…`;
17+}
18+19+export async function readResponseTextLimited(
20+response: Response,
21+limitBytes = 16 * 1024,
22+): Promise<string> {
23+if (limitBytes <= 0) {
24+return "";
25+}
26+const reader = response.body?.getReader();
27+if (!reader) {
28+return "";
29+}
30+31+const decoder = new TextDecoder();
32+let total = 0;
33+let text = "";
34+let reachedLimit = false;
35+36+try {
37+while (true) {
38+const { value, done } = await reader.read();
39+if (done) {
40+break;
41+}
42+if (!value || value.byteLength === 0) {
43+continue;
44+}
45+const remaining = limitBytes - total;
46+if (remaining <= 0) {
47+reachedLimit = true;
48+break;
49+}
50+const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value;
51+total += chunk.byteLength;
52+text += decoder.decode(chunk, { stream: true });
53+if (total >= limitBytes) {
54+reachedLimit = true;
55+break;
56+}
57+}
58+text += decoder.decode();
59+} finally {
60+if (reachedLimit) {
61+await reader.cancel().catch(() => {});
62+}
63+}
64+65+return text;
66+}
67+68+export function formatProviderErrorPayload(payload: unknown): string | undefined {
69+const root = asObject(payload);
70+const detailObject = asObject(root?.detail);
71+const subject = asObject(root?.error) ?? detailObject ?? root;
72+if (!subject) {
73+return undefined;
74+}
75+const message =
76+trimToUndefined(subject.message) ??
77+trimToUndefined(subject.detail) ??
78+trimToUndefined(root?.message) ??
79+trimToUndefined(root?.error) ??
80+trimToUndefined(root?.detail);
81+const type = trimToUndefined(subject.type);
82+const code = trimToUndefined(subject.code) ?? trimToUndefined(subject.status);
83+const metadata = [type ? `type=${type}` : undefined, code ? `code=${code}` : undefined]
84+.filter((value): value is string => Boolean(value))
85+.join(", ");
86+if (message && metadata) {
87+return `${truncateErrorDetail(message)} [${metadata}]`;
88+}
89+if (message) {
90+return truncateErrorDetail(message);
91+}
92+if (metadata) {
93+return `[${metadata}]`;
94+}
95+return undefined;
96+}
97+98+export async function extractProviderErrorDetail(response: Response): Promise<string | undefined> {
99+const rawBody = trimToUndefined(await readResponseTextLimited(response));
100+if (!rawBody) {
101+return undefined;
102+}
103+try {
104+return formatProviderErrorPayload(JSON.parse(rawBody)) ?? truncateErrorDetail(rawBody);
105+} catch {
106+return truncateErrorDetail(rawBody);
107+}
108+}
109+110+export function extractProviderRequestId(response: Response): string | undefined {
111+return (
112+trimToUndefined(response.headers.get("x-request-id")) ??
113+trimToUndefined(response.headers.get("request-id"))
114+);
115+}
116+117+export function formatProviderHttpErrorMessage(params: {
118+label: string;
119+status: number;
120+detail?: string;
121+requestId?: string;
122+}): string {
123+const { label, status, detail, requestId } = params;
124+return (
125+`${label} (${status})` +
126+(detail ? `: ${detail}` : "") +
127+(requestId ? ` [request_id=${requestId}]` : "")
128+);
129+}
130+131+export async function createProviderHttpError(response: Response, label: string): Promise<Error> {
132+const detail = await extractProviderErrorDetail(response);
133+const requestId = extractProviderRequestId(response);
134+return new Error(
135+formatProviderHttpErrorMessage({
136+ label,
137+status: response.status,
138+ detail,
139+ requestId,
140+}),
141+);
142+}
143+144+export async function assertOkOrThrowProviderError(
145+response: Response,
146+label: string,
147+): Promise<void> {
148+if (response.ok) {
149+return;
150+}
151+throw await createProviderHttpError(response, label);
152+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。