Skip to content

Commit

Permalink
feat: add form data handler in the sdk middleware module
Browse files Browse the repository at this point in the history
  • Loading branch information
bartoszherba committed Nov 22, 2024
1 parent f02f7cf commit 4f6b4fc
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 1 deletion.
20 changes: 20 additions & 0 deletions .changeset/long-panthers-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@vue-storefront/sdk": minor
---

**[ADDED]** Add support for multipart/form-data requests in SDK

- Added handling for multipart/form-data content type in the default HTTP client
- Automatically handles File and Blob objects in request parameters

```typescript
// Upload a file using multipart/form-data
await sdk.commerce.uploadFile(
{ file: new File(["content"], "test.txt", { type: "text/plain" }) },
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);
```
3 changes: 3 additions & 0 deletions packages/sdk/src/__tests__/__mocks__/apiClient/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const { createApiClient } = apiClientFactory({
unauthorized: async (_context, _params) => {
throw { statusCode: 401, message: "Unauthorized" };
},
uploadFile: async (_context, params) => {
return { file: params.file };
},
logout: async (_context) => {},
},
});
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/src/__tests__/__mocks__/apiClient/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ export type Endpoints = {
* For testing void responses.
*/
logout: () => Promise<void>;
/**
* Upload a file.
*/
uploadFile: (params: {
file: { name: string; content: string };
}) => Promise<{ file: { name: string; content: string } }>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -867,4 +867,56 @@ describe("middlewareModule", () => {

expect(logSpy).not.toHaveBeenCalled();
});

it("should create FormData when Content-Type is multipart/form-data", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(middlewareModule<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
cdnCacheBustingId: "commit-hash",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.uploadFile(
{ file: { name: "test.txt", content: "test" } },
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);

const [url, params, config] = customHttpClient.mock.calls[0];
expect(url).toBe("http://localhost:8181/commerce/uploadFile");
expect(params[0]).toEqual({ file: { name: "test.txt", content: "test" } });
expect(config.headers["Content-Type"]).toBe("multipart/form-data");
});

it("should maintain correct endpoint URL for multipart requests", async () => {
const customHttpClient = jest.fn();
const sdkConfig = {
commerce: buildModule(middlewareModule<Endpoints>, {
apiUrl: "http://localhost:8181/commerce",
httpClient: customHttpClient,
}),
};
const sdk = initSDK(sdkConfig);

await sdk.commerce.uploadFile(
{ file: { name: "test.txt", content: "test" } },
prepareConfig({
headers: {
"Content-Type": "multipart/form-data",
},
})
);

expect(customHttpClient).toHaveBeenCalledWith(
"http://localhost:8181/commerce/uploadFile",
expect.any(Array),
expect.any(Object)
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,34 @@ export const getRequestSender = (options: Options): RequestSender => {
};

const defaultHTTPClient: HTTPClient = async (url, params, config) => {
const isMultipart = config?.headers?.["Content-Type"]?.includes(
"multipart/form-data"
);

let body;
if (config?.method === "GET") {
body = undefined;
} else if (isMultipart) {
const formData = new FormData();
Object.entries(params).forEach(([key, value]) => {
if (value instanceof Blob || value instanceof File) {
formData.append(key, value);
} else {
formData.append(key, JSON.stringify(value));
}
});
body = formData;

if (config?.headers) {
delete config.headers["Content-Type"];
}
} else {
body = JSON.stringify(params);
}

const response = await fetch(url, {
...config,
body: config?.method === "GET" ? undefined : JSON.stringify(params),
body,
credentials: "include",
});

Expand Down

0 comments on commit 4f6b4fc

Please sign in to comment.