Skip to content

Commit ab86c89

Browse files
authored
fix: Unexpected end of JSON input in Webhook Form (#4214)
## Description closes #4050 This PR modifies the response handling logic to attempt parsing all resource data as JSON. If an error occurs during parsing, an error response is only returned if the Accept header is explicitly set to application/json. 204 responses are skipped too. ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 3d98a69 commit ab86c89

File tree

2 files changed

+93
-10
lines changed

2 files changed

+93
-10
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, expect, test, beforeEach, jest } from "@jest/globals";
2+
3+
import { loadResource } from "./resource-loader";
4+
import type { ResourceRequest } from "./schema/resources";
5+
6+
// Mock the fetch function
7+
8+
describe("loadResource", () => {
9+
let mockFetch: jest.Mock<typeof fetch>;
10+
11+
beforeEach(() => {
12+
mockFetch = jest.fn();
13+
jest.clearAllMocks();
14+
});
15+
16+
test("should successfully fetch a resource and return a JSON response", async () => {
17+
const mockResponse = new Response(JSON.stringify({ key: "value" }), {
18+
status: 200,
19+
});
20+
mockFetch.mockResolvedValue(mockResponse);
21+
22+
const resourceRequest: ResourceRequest = {
23+
id: "1",
24+
name: "resource",
25+
url: "https://example.com/resource",
26+
method: "get",
27+
headers: [],
28+
body: undefined,
29+
};
30+
31+
const result = await loadResource(mockFetch, resourceRequest);
32+
33+
expect(mockFetch).toHaveBeenCalledWith("https://example.com/resource", {
34+
method: "get",
35+
headers: new Headers(),
36+
});
37+
38+
expect(result).toEqual({
39+
data: {
40+
key: "value",
41+
},
42+
ok: true,
43+
status: 200,
44+
statusText: "",
45+
});
46+
});
47+
48+
test("should fetch resource successfully with non-JSON response", async () => {
49+
const mockResponse = new Response("nonjson", {
50+
status: 200,
51+
});
52+
mockFetch.mockResolvedValue(mockResponse);
53+
54+
const resourceRequest: ResourceRequest = {
55+
id: "1",
56+
name: "resource",
57+
url: "https://example.com/resource",
58+
method: "get",
59+
headers: [],
60+
body: undefined,
61+
};
62+
63+
const result = await loadResource(mockFetch, resourceRequest);
64+
65+
expect(mockFetch).toHaveBeenCalledWith("https://example.com/resource", {
66+
method: "get",
67+
headers: new Headers(),
68+
});
69+
70+
expect(result).toEqual({
71+
data: "nonjson",
72+
ok: true,
73+
status: 200,
74+
statusText: "",
75+
});
76+
});
77+
});

packages/sdk/src/resource-loader.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,30 @@ export const loadResource = async (
2424
// cloudflare workers fail when fetching url contains spaces
2525
// even though new URL suppose to trim them on parsing by spec
2626
const response = await customFetch(url.trim(), requestInit);
27-
let data;
28-
if (
29-
response.ok &&
30-
// accept json by default and when specified explicitly
31-
(response.headers.has("content-type") === false ||
32-
response.headers.get("content-type")?.includes("application/json"))
33-
) {
34-
data = await response.json();
35-
} else {
36-
data = await response.text();
27+
28+
let data = await response.text();
29+
30+
try {
31+
// If it looks like JSON and quacks like JSON, then it probably is JSON.
32+
data = JSON.parse(data);
33+
} catch {
34+
// ignore, leave data as text
3735
}
36+
37+
if (!response.ok) {
38+
console.error(
39+
`Failed to load resource: ${url} - ${response.status}: ${JSON.stringify(data).slice(0, 300)}`
40+
);
41+
}
42+
3843
return {
3944
ok: response.ok,
4045
data,
4146
status: response.status,
4247
statusText: response.statusText,
4348
};
4449
} catch (error) {
50+
console.error(error);
4551
const message = (error as unknown as Error).message;
4652
return {
4753
ok: false,

0 commit comments

Comments
 (0)