From 06009bd2d5ae7ffcb1c183ac44dca9330571ba66 Mon Sep 17 00:00:00 2001 From: "T. Narantuya" Date: Fri, 19 Sep 2025 12:57:39 +0900 Subject: [PATCH] Enhance ApiClient with error handling and flexible HTTP methods. Introduce ApiError class for better error management, normalize base URL resolution from environment variables, and update createClient function to support custom error handling and authorization middleware. --- packages/api-client/src/runtime/client.ts | 69 +++++++++++++++++------ 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/api-client/src/runtime/client.ts b/packages/api-client/src/runtime/client.ts index 21f33a1c..5f4fbeb0 100644 --- a/packages/api-client/src/runtime/client.ts +++ b/packages/api-client/src/runtime/client.ts @@ -1,46 +1,83 @@ import createOpenApiClient from "openapi-fetch"; import type { paths } from "../__generated__/types"; +export class ApiError extends Error { + constructor( + message: string, + public readonly response: Response, + public readonly body?: unknown + ) { + super(message); + this.name = "ApiError"; + } +} + export type ApiClient = ReturnType>; export type AuthHeaderResolver = () => string | undefined; export interface CreateClientOptions { getAuthHeader?: AuthHeaderResolver; + handleError?: (response: Response) => void | Promise; +} + +async function defaultHandleError(response: Response) { + if (response.ok) return; + + let body: unknown; + let message = response.statusText || `Request failed with status ${response.status}`; + + try { + const cloned = response.clone(); + const contentType = cloned.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = await cloned.json(); + if (body && typeof body === "object" && "message" in body && typeof (body as any).message === "string") { + message = (body as any).message; + } + } else { + const text = await cloned.text(); + if (text) { + body = text; + message = text; + } + } + } catch { + // Ignore body parse errors; fall back to status text + } + + throw new ApiError(message, response, body); } export function createClient( baseUrl: string, options: CreateClientOptions = {} ): ApiClient { - const client = createOpenApiClient({ - baseUrl, - throwOnError: true, - }); + const client = createOpenApiClient({ baseUrl }); + const handleError = options.handleError ?? defaultHandleError; - if (typeof client.use === "function" && options.getAuthHeader) { + if (typeof client.use === "function") { const resolveAuthHeader = options.getAuthHeader; client.use({ - onRequest({ request }: { request: Request }) { - if (!request || typeof request.headers?.has !== "function") { - return; - } - - if (request.headers.has("Authorization")) { - return; - } + onRequest({ request }) { + if (!resolveAuthHeader) return; + if (!request || typeof request.headers?.has !== "function") return; + if (request.headers.has("Authorization")) return; const headerValue = resolveAuthHeader(); - if (!headerValue) { - return; - } + if (!headerValue) return; request.headers.set("Authorization", headerValue); }, + async onResponse({ response }) { + await handleError(response); + }, } as never); } return client; } + +export type { paths };