Skip to content

Commit 449d7e8

Browse files
committed
Allow falsy values (except undefined) as a valid body
1 parent 3694a3e commit 449d7e8

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

packages/openapi-fetch/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default function createClient(clientOptions) {
8282
...init,
8383
headers: mergeHeaders(baseHeaders, headers, params.header),
8484
};
85-
if (requestInit.body) {
85+
if (requestInit.body !== undefined) {
8686
requestInit.body = bodySerializer(requestInit.body);
8787
// remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression
8888
if (requestInit.body instanceof FormData) {

packages/openapi-fetch/test/index.test.ts

+182
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { HttpResponse, type StrictResponse } from "msw";
22
import { afterAll, beforeAll, describe, expect, expectTypeOf, it } from "vitest";
33
import createClient, {
4+
type BodySerializer,
5+
type FetchOptions,
46
type Middleware,
57
type MiddlewareCallbackParams,
68
type QuerySerializerOptions,
@@ -1275,6 +1277,186 @@ describe("client", () => {
12751277
});
12761278
});
12771279

1280+
describe("body serialization", () => {
1281+
const BODY_ACCEPTING_METHODS = [["PUT"], ["POST"], ["DELETE"], ["OPTIONS"], ["PATCH"]] as const;
1282+
const ALL_METHODS = [...BODY_ACCEPTING_METHODS, ["GET"], ["HEAD"]] as const;
1283+
1284+
const fireRequestAndGetBodyInformation = async (options: {
1285+
bodySerializer?: BodySerializer<unknown>;
1286+
method: (typeof ALL_METHODS)[number][number];
1287+
fetchOptions: FetchOptions<any>;
1288+
}) => {
1289+
const client = createClient<any>({ baseUrl, bodySerializer: options.bodySerializer });
1290+
const { getRequest } = useMockRequestHandler({
1291+
baseUrl,
1292+
method: "all",
1293+
path: "/blogposts-optional",
1294+
status: 200,
1295+
});
1296+
await client[options.method]("/blogposts-optional", options.fetchOptions as any);
1297+
1298+
const request = getRequest();
1299+
const bodyText = await request.text();
1300+
1301+
return { bodyUsed: request.bodyUsed, bodyText };
1302+
};
1303+
1304+
it.each(ALL_METHODS)("missing body (with body serializer) - %s", async (method) => {
1305+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1306+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1307+
bodySerializer,
1308+
method,
1309+
fetchOptions: {},
1310+
});
1311+
1312+
expect(bodyUsed).toBe(false);
1313+
expect(bodyText).toBe("");
1314+
expect(bodySerializer).not.toBeCalled();
1315+
});
1316+
1317+
it.each(ALL_METHODS)("missing body (without body serializer) - %s", async (method) => {
1318+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ method, fetchOptions: {} });
1319+
1320+
expect(bodyUsed).toBe(false);
1321+
expect(bodyText).toBe("");
1322+
});
1323+
1324+
it.each(ALL_METHODS)("`undefined` body (with body serializer) - %s", async (method) => {
1325+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1326+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1327+
bodySerializer,
1328+
method,
1329+
fetchOptions: {
1330+
body: undefined,
1331+
},
1332+
});
1333+
1334+
expect(bodyUsed).toBe(false);
1335+
expect(bodyText).toBe("");
1336+
expect(bodySerializer).not.toBeCalled();
1337+
});
1338+
1339+
it.each(ALL_METHODS)("`undefined` body (without body serializer) - %s", async (method) => {
1340+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1341+
method,
1342+
fetchOptions: {
1343+
body: undefined,
1344+
},
1345+
});
1346+
1347+
expect(bodyUsed).toBe(false);
1348+
expect(bodyText).toBe("");
1349+
});
1350+
1351+
it.each(BODY_ACCEPTING_METHODS)("`null` body (with body serializer) - %s", async (method) => {
1352+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1353+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1354+
bodySerializer,
1355+
method,
1356+
fetchOptions: {
1357+
body: null,
1358+
},
1359+
});
1360+
1361+
expect(bodyUsed).toBe(true);
1362+
expect(bodyText).toBe("Serialized: null");
1363+
expect(bodySerializer).toBeCalled();
1364+
});
1365+
1366+
it.each(BODY_ACCEPTING_METHODS)("`null` body (without body serializer) - %s", async (method) => {
1367+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1368+
method,
1369+
fetchOptions: {
1370+
body: null,
1371+
},
1372+
});
1373+
1374+
expect(bodyUsed).toBe(true);
1375+
expect(bodyText).toBe("null");
1376+
});
1377+
1378+
it.each(BODY_ACCEPTING_METHODS)("`false` body (with body serializer) - %s", async (method) => {
1379+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1380+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1381+
bodySerializer,
1382+
method,
1383+
fetchOptions: {
1384+
body: false,
1385+
},
1386+
});
1387+
1388+
expect(bodyUsed).toBe(true);
1389+
expect(bodyText).toBe("Serialized: false");
1390+
expect(bodySerializer).toBeCalled();
1391+
});
1392+
1393+
it.each(BODY_ACCEPTING_METHODS)("`false` body (without body serializer) - %s", async (method) => {
1394+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1395+
method,
1396+
fetchOptions: {
1397+
body: false,
1398+
},
1399+
});
1400+
1401+
expect(bodyUsed).toBe(true);
1402+
expect(bodyText).toBe("false");
1403+
});
1404+
1405+
it.each(BODY_ACCEPTING_METHODS)("`''` body (with body serializer) - %s", async (method) => {
1406+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1407+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1408+
bodySerializer,
1409+
method,
1410+
fetchOptions: {
1411+
body: "",
1412+
},
1413+
});
1414+
1415+
expect(bodyUsed).toBe(true);
1416+
expect(bodyText).toBe('Serialized: ""');
1417+
expect(bodySerializer).toBeCalled();
1418+
});
1419+
1420+
it.each(BODY_ACCEPTING_METHODS)("`''` body (without body serializer) - %s", async (method) => {
1421+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1422+
method,
1423+
fetchOptions: {
1424+
body: "",
1425+
},
1426+
});
1427+
1428+
expect(bodyUsed).toBe(true);
1429+
expect(bodyText).toBe('""');
1430+
});
1431+
1432+
it.each(BODY_ACCEPTING_METHODS)("`0` body (with body serializer) - %s", async (method) => {
1433+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1434+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1435+
bodySerializer,
1436+
method,
1437+
fetchOptions: {
1438+
body: 0,
1439+
},
1440+
});
1441+
1442+
expect(bodyUsed).toBe(true);
1443+
expect(bodyText).toBe("Serialized: 0");
1444+
expect(bodySerializer).toBeCalled();
1445+
});
1446+
1447+
it.each(BODY_ACCEPTING_METHODS)("`0` body (without body serializer) - %s", async (method) => {
1448+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1449+
method,
1450+
fetchOptions: {
1451+
body: 0,
1452+
},
1453+
});
1454+
1455+
expect(bodyUsed).toBe(true);
1456+
expect(bodyText).toBe("0");
1457+
});
1458+
});
1459+
12781460
describe("requests", () => {
12791461
it("multipart/form-data", async () => {
12801462
const client = createClient<paths>({ baseUrl });

0 commit comments

Comments
 (0)