Spaces:
Runtime error
Runtime error
File size: 3,379 Bytes
cd8bd0a | 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 | /**
* API utility functions for making HTTP requests
*/
const DEFAULT_HEADERS: Record<string, string> = {
"Content-Type": "application/json",
};
interface ApiOptions extends RequestInit {
headers?: Record<string, string>;
}
export async function get(url: string, options: ApiOptions = {}) {
const response = await fetch(url, {
...options,
method: "GET",
headers: { ...DEFAULT_HEADERS, ...options.headers },
});
return handleResponse(response);
}
export async function post(url: string, data: unknown, options: ApiOptions = {}) {
const response = await fetch(url, {
...options,
method: "POST",
headers: { ...DEFAULT_HEADERS, ...options.headers },
body: JSON.stringify(data),
});
return handleResponse(response);
}
export async function put(url: string, data: unknown, options: ApiOptions = {}) {
const response = await fetch(url, {
...options,
method: "PUT",
headers: { ...DEFAULT_HEADERS, ...options.headers },
body: JSON.stringify(data),
});
return handleResponse(response);
}
export async function del(url: string, options: ApiOptions = {}) {
const response = await fetch(url, {
...options,
method: "DELETE",
headers: { ...DEFAULT_HEADERS, ...options.headers },
});
return handleResponse(response);
}
/**
* Safely read a fetch `Response` body. Returns the parsed JSON when the body is
* JSON, the raw text when it is not, or `null` when empty. Never throws on a
* non-JSON body — a plain-text `500 Internal Server Error` no longer surfaces to
* the caller as `Unexpected token 'I'…`. (#1318) The body is read exactly once,
* so pass the returned value to {@link getErrorMessage} rather than re-reading
* the response.
*/
export async function parseResponseBody(response: Response): Promise<unknown> {
const text = await response.text();
if (!text) return null;
try {
return JSON.parse(text);
} catch {
return text;
}
}
/**
* Extract a human-readable error message from an already-parsed response body
* (see {@link parseResponseBody}), regardless of whether it is a JSON object
* (`{ error }` / `{ error: { message } }` / `{ message }`) or plain text. Falls
* back to a status-qualified default. (#1318)
*/
export function getErrorMessage(
body: unknown,
status?: number,
fallback = "Request failed"
): string {
if (body && typeof body === "object") {
const rec = body as Record<string, unknown>;
const err = rec.error;
if (typeof err === "string" && err.trim()) return err;
if (err && typeof err === "object") {
const message = (err as Record<string, unknown>).message;
if (typeof message === "string" && message.trim()) return message;
return JSON.stringify(err);
}
const msg = rec.message ?? rec.detail;
if (typeof msg === "string" && msg.trim()) return msg;
}
if (typeof body === "string" && body.trim()) {
return body.length > 300 ? `${body.slice(0, 300)}…` : body;
}
return status != null ? `${fallback} (HTTP ${status})` : fallback;
}
async function handleResponse(response: Response) {
const data = await response.json();
if (!response.ok) {
const error: any = new Error(data.error || "An error occurred");
error.status = response.status;
error.data = data;
throw error;
}
return data;
}
const api = { get, post, put, del };
export default api;
|