Skip to content

Commit 66f3088

Browse files
committed
fix: reject server function calls on 5xx responses without X-Error
1 parent 27c2877 commit 66f3088

3 files changed

Lines changed: 68 additions & 2 deletions

File tree

.changeset/tidy-donkeys-reject.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solidjs/start": patch
3+
---
4+
5+
Reject server function calls when the response is a 5xx without an X-Error header, instead of resolving with the parsed error body
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, it, vi, beforeEach } from "vitest";
2+
3+
vi.mock("../shared/dev-toolbar/functions/tracker.ts", () => ({
4+
pushRequest: vi.fn(),
5+
pushResponse: vi.fn(),
6+
}));
7+
8+
vi.mock("./serialization.ts", () => ({
9+
serializeToJSONString: vi.fn(async () => "[]"),
10+
}));
11+
12+
vi.mock("./shared.ts", async importOriginal => {
13+
const actual = await importOriginal<typeof import("./shared.ts")>();
14+
return { ...actual, extractBody: vi.fn(async () => undefined) };
15+
});
16+
17+
const { cloneServerReference } = await import("./client.ts");
18+
19+
const respondWith = (status: number, headers: Record<string, string> = {}) => {
20+
vi.stubGlobal(
21+
"fetch",
22+
vi.fn(async () => new Response(null, { status, headers })),
23+
);
24+
};
25+
26+
const callServerFunction = () =>
27+
(cloneServerReference("test-fn") as unknown as () => Promise<unknown>)();
28+
29+
const rejectionOf = async (call: Promise<unknown>) => {
30+
try {
31+
await call;
32+
} catch (error) {
33+
return error;
34+
}
35+
throw new Error("expected the server function call to reject");
36+
};
37+
38+
describe("fetchServerFunction", () => {
39+
beforeEach(() => {
40+
vi.stubEnv("BASE_URL", "http://localhost/");
41+
});
42+
43+
it("rejects when the response is a 5xx without an X-Error header", async () => {
44+
respondWith(500);
45+
const rejection = await rejectionOf(callServerFunction());
46+
expect(rejection).toBeInstanceOf(Error);
47+
expect(rejection).toHaveProperty("message", "Server function call failed with status 500");
48+
});
49+
50+
it("rejects with an error when an X-Error response carries no body", async () => {
51+
respondWith(403, { "X-Error": "true" });
52+
const rejection = await rejectionOf(callServerFunction());
53+
expect(rejection).toBeInstanceOf(Error);
54+
expect(rejection).toHaveProperty("message", "Server function call failed with status 403");
55+
});
56+
57+
it("resolves normally for a successful response", async () => {
58+
respondWith(200);
59+
await expect(callServerFunction()).resolves.toBeUndefined();
60+
});
61+
});

packages/start/src/fns/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ async function fetchServerFunction(
9494
}
9595

9696
const result = await extractBody(instance, true, response.clone());
97-
if (response.headers.has("X-Error")) {
98-
throw result;
97+
if (response.headers.has("X-Error") || response.status >= 500) {
98+
throw result ?? new Error(`Server function call failed with status ${response.status}`);
9999
}
100100
return result;
101101
}

0 commit comments

Comments
 (0)