File size: 1,616 Bytes
fc9bd9f | 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 | export type Fetcher = (
url: string,
options?: RequestInit
) => Promise<Response>;
export class ApiError extends Error {
status: number;
detail: string | null;
constructor(message: string, status: number, detail: string | null) {
super(message);
this.name = "ApiError";
this.status = status;
this.detail = detail;
}
}
export interface ApiRequestOptions {
method?: string;
body?: unknown;
signal?: AbortSignal;
/** Human-readable label for the error message, e.g. "Start training". */
action?: string;
}
/**
* Performs a request against the lelab backend and parses the JSON response.
* Throws ApiError with FastAPI's `detail` field on non-2xx, or on JSON parse
* failure. Use this in place of ad-hoc `r.ok` / `r.json()` branching.
*/
export async function apiRequest<T = unknown>(
baseUrl: string,
fetcher: Fetcher,
path: string,
{ method = "GET", body, signal, action }: ApiRequestOptions = {}
): Promise<T> {
const init: RequestInit = { method, signal };
if (body !== undefined) init.body = JSON.stringify(body);
const url = `${baseUrl}${path}`;
const r = await fetcher(url, init);
if (!r.ok) {
let detail: string | null = null;
try {
const errBody = await r.json();
detail = errBody?.detail ?? errBody?.message ?? null;
} catch {
// body wasn't JSON
}
const label = action || `${method} ${path}`;
throw new ApiError(
`${label} failed: ${detail ?? r.status}`,
r.status,
detail
);
}
// 204 No Content
if (r.status === 204) return undefined as T;
return r.json() as Promise<T>;
}
|