Files
root ffdc887b8a s
2026-05-17 12:09:14 +00:00

74 lines
2.0 KiB
TypeScript

type JsonErrorPayload = {
error?: string;
message?: string;
detail?: string;
};
const MAX_ERROR_TEXT = 240;
function summarizeResponseText(raw: string): string {
const compact = raw.replace(/\s+/g, " ").trim();
if (!compact) return "";
if (compact.length <= MAX_ERROR_TEXT) return compact;
return `${compact.slice(0, MAX_ERROR_TEXT - 3)}...`;
}
function defaultHttpMessage(response: Response): string {
const suffix = response.statusText?.trim() ? ` ${response.statusText.trim()}` : "";
return `HTTP ${response.status}${suffix}`;
}
function extractErrorMessage(
response: Response,
raw: string,
payload: JsonErrorPayload | null,
): string {
const candidate =
(typeof payload?.error === "string" && payload.error.trim()) ||
(typeof payload?.message === "string" && payload.message.trim()) ||
(typeof payload?.detail === "string" && payload.detail.trim()) ||
summarizeResponseText(raw);
return candidate || defaultHttpMessage(response);
}
export async function fetchJsonResponse<T>(url: string, init?: RequestInit): Promise<T> {
const response = await fetch(url, { cache: "no-store", ...init });
const raw = await response.text();
const trimmed = raw.trim();
let payload: (T & JsonErrorPayload) | null = null;
if (trimmed) {
try {
payload = JSON.parse(trimmed) as T & JsonErrorPayload;
} catch {
if (!response.ok) {
throw new Error(extractErrorMessage(response, trimmed, null));
}
throw new Error(`Invalid JSON response from ${url}`);
}
}
if (!response.ok) {
throw new Error(extractErrorMessage(response, trimmed, payload));
}
if (payload !== null) {
return payload as T;
}
return {} as T;
}
export async function readErrorMessage(response: Response): Promise<string> {
const raw = (await response.text()).trim();
let payload: JsonErrorPayload | null = null;
if (raw) {
try {
payload = JSON.parse(raw) as JsonErrorPayload;
} catch {
payload = null;
}
}
return extractErrorMessage(response, raw, payload);
}