From a8a38152d6f824e962e79ce51f7f4e0aea5c3df7 Mon Sep 17 00:00:00 2001 From: Gourav Kumar Dhiman Date: Tue, 20 Oct 2026 19:54:43 +0530 Subject: [PATCH] fix: include the server error message in failed API requests Failures only showed axios' generic "Request failed with status code 400". Append the message from the tRPC error body when present. Co-Authored-By: Claude Sonnet 5.5 --- src/client.ts | 14 +++++++++++++- tests/client.test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index dc31d14..1d14705 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,15 +70,27 @@ export function saveAuthConfig(url: string, token: string): void { fs.writeFileSync(configPath, JSON.stringify({ url, token }, null, 2)); } +export function extractApiErrorMessage(data: unknown): string | undefined { + const d = data as any; + const message = d?.error?.json?.message ?? d?.error?.message ?? d?.message; + return typeof message === "string" && message ? message : undefined; +} + export function createClient(): AxiosInstance { const auth = readAuthConfig(); - return axios.create({ + const client = axios.create({ baseURL: `${auth.url}/api`, headers: { "x-api-key": auth.token, "Content-Type": "application/json", }, }); + client.interceptors.response.use(undefined, (error) => { + const detail = extractApiErrorMessage(error?.response?.data); + if (detail) error.message = `${error.message}: ${detail}`; + return Promise.reject(error); + }); + return client; } export async function apiPost( diff --git a/tests/client.test.ts b/tests/client.test.ts index 849213f..736f709 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -54,3 +54,38 @@ describe("saveAuthConfig", () => { expect(typeof saveAuthConfig).toBe("function"); }); }); + +describe("extractApiErrorMessage", () => { + it("should read tRPC and plain error bodies", async () => { + const { extractApiErrorMessage } = await import("../src/client.js"); + + expect( + extractApiErrorMessage({ error: { json: { message: "Not found" } } }), + ).toBe("Not found"); + expect(extractApiErrorMessage({ message: "Unauthorized" })).toBe( + "Unauthorized", + ); + expect(extractApiErrorMessage("")).toBeUndefined(); + expect(extractApiErrorMessage(undefined)).toBeUndefined(); + }); + + it("should append the server message to request errors", async () => { + process.env.DOKPLOY_URL = "http://127.0.0.1:9"; + process.env.DOKPLOY_API_KEY = "k"; + const { createClient } = await import("../src/client.js"); + const client = createClient(); + client.defaults.adapter = async (config) => { + const error: any = new Error("Request failed with status code 400"); + error.config = config; + error.response = { + status: 400, + data: { error: { json: { message: "Invalid input" } } }, + }; + throw error; + }; + + await expect(client.get("/x")).rejects.toThrow( + "Request failed with status code 400: Invalid input", + ); + }); +});