Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-donkeys-reject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/start": patch
---

Reject server function calls when the response is a 5xx without an X-Error header, instead of resolving with the parsed error body
61 changes: 61 additions & 0 deletions packages/start/src/fns/client.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, expect, it, vi, beforeEach } from "vitest";

vi.mock("../shared/dev-toolbar/functions/tracker.ts", () => ({
pushRequest: vi.fn(),
pushResponse: vi.fn(),
}));

vi.mock("./serialization.ts", () => ({
serializeToJSONString: vi.fn(async () => "[]"),
}));

vi.mock("./shared.ts", async importOriginal => {
const actual = await importOriginal<typeof import("./shared.ts")>();
return { ...actual, extractBody: vi.fn(async () => undefined) };
});

const { cloneServerReference } = await import("./client.ts");

const respondWith = (status: number, headers: Record<string, string> = {}) => {
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response(null, { status, headers })),
);
};

const callServerFunction = () =>
(cloneServerReference("test-fn") as unknown as () => Promise<unknown>)();

const rejectionOf = async (call: Promise<unknown>) => {
try {
await call;
} catch (error) {
return error;
}
throw new Error("expected the server function call to reject");
};

describe("fetchServerFunction", () => {
beforeEach(() => {
vi.stubEnv("BASE_URL", "http://localhost/");
});

it("rejects when the response is a 5xx without an X-Error header", async () => {
respondWith(500);
const rejection = await rejectionOf(callServerFunction());
expect(rejection).toBeInstanceOf(Error);
expect(rejection).toHaveProperty("message", "Server function call failed with status 500");
});

it("rejects with an error when an X-Error response carries no body", async () => {
respondWith(403, { "X-Error": "true" });
const rejection = await rejectionOf(callServerFunction());
expect(rejection).toBeInstanceOf(Error);
expect(rejection).toHaveProperty("message", "Server function call failed with status 403");
});

it("resolves normally for a successful response", async () => {
respondWith(200);
await expect(callServerFunction()).resolves.toBeUndefined();
});
});
4 changes: 2 additions & 2 deletions packages/start/src/fns/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ async function fetchServerFunction(
}

const result = await extractBody(instance, true, response.clone());
if (response.headers.has("X-Error")) {
throw result;
if (response.headers.has("X-Error") || response.status >= 500) {
throw result ?? new Error(`Server function call failed with status ${response.status}`);
Comment thread
birkskyum marked this conversation as resolved.
}
return result;
}
Expand Down
Loading