|
- export class ApiError extends Error {
- readonly status: number;
- readonly code: string;
- readonly traceId?: string;
-
- constructor(status: number, code: string, message: string, traceId?: string) {
- super(message);
- this.name = "ApiError";
- this.status = status;
- this.code = code;
- this.traceId = traceId;
- }
- }
-
- type RequestOptions = {
- method: string;
- path: string;
- query?: Record<string, string | number | boolean | undefined>;
- body?: unknown;
- expectedStatus: number;
- };
-
- function buildUrl(path: string, query?: RequestOptions["query"]): string {
- const url = new URL(path, window.location.origin);
- if (query) {
- for (const [key, value] of Object.entries(query)) {
- if (value !== undefined && value !== "") {
- url.searchParams.set(key, String(value));
- }
- }
- }
- return `${url.pathname}${url.search}`;
- }
-
- export async function request<T>(options: RequestOptions): Promise<T> {
- const headers: Record<string, string> = { Accept: "application/json" };
- if (options.body !== undefined) {
- headers["Content-Type"] = "application/json";
- }
-
- const response = await fetch(buildUrl(options.path, options.query), {
- method: options.method,
- headers,
- body: options.body === undefined ? undefined : JSON.stringify(options.body),
- });
-
- if (response.status === 204 || options.expectedStatus === 204) {
- if (!response.ok && response.status !== options.expectedStatus) {
- throw await toApiError(response);
- }
- return undefined as T;
- }
-
- if (response.status !== options.expectedStatus) {
- throw await toApiError(response);
- }
-
- if (response.headers.get("content-type")?.includes("application/json")) {
- return (await response.json()) as T;
- }
- return undefined as T;
- }
-
- async function toApiError(response: Response): Promise<ApiError> {
- const traceId = response.headers.get("x-request-id") ?? undefined;
- try {
- const payload = (await response.json()) as {
- error?: { code?: string; message?: string; trace_id?: string };
- };
- return new ApiError(
- response.status,
- payload.error?.code ?? "http_error",
- payload.error?.message ?? response.statusText,
- payload.error?.trace_id ?? traceId,
- );
- } catch {
- return new ApiError(response.status, "http_error", response.statusText, traceId);
- }
- }
|