Skip to content

Commit 6038f8f

Browse files
authored
Allow falsy values (except undefined) as a valid body (#1825)
* Allow falsy values (except undefined) as a valid body * Add change-set
1 parent 246f3b7 commit 6038f8f

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

.changeset/rich-crews-repeat.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
Allow falsy values (except undefined) as a valid body

packages/openapi-fetch/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export default function createClient(clientOptions) {
8484
...init,
8585
headers: mergeHeaders(baseHeaders, headers, params.header),
8686
};
87-
if (requestInit.body) {
87+
if (requestInit.body !== undefined) {
8888
requestInit.body = bodySerializer(requestInit.body);
8989
// remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression
9090
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 MethodResponse,
57
type Middleware,
68
type MiddlewareCallbackParams,
@@ -1335,6 +1337,186 @@ describe("client", () => {
13351337
});
13361338
});
13371339

1340+
describe("body serialization", () => {
1341+
const BODY_ACCEPTING_METHODS = [["PUT"], ["POST"], ["DELETE"], ["OPTIONS"], ["PATCH"]] as const;
1342+
const ALL_METHODS = [...BODY_ACCEPTING_METHODS, ["GET"], ["HEAD"]] as const;
1343+
1344+
const fireRequestAndGetBodyInformation = async (options: {
1345+
bodySerializer?: BodySerializer<unknown>;
1346+
method: (typeof ALL_METHODS)[number][number];
1347+
fetchOptions: FetchOptions<any>;
1348+
}) => {
1349+
const client = createClient<any>({ baseUrl, bodySerializer: options.bodySerializer });
1350+
const { getRequest } = useMockRequestHandler({
1351+
baseUrl,
1352+
method: "all",
1353+
path: "/blogposts-optional",
1354+
status: 200,
1355+
});
1356+
await client[options.method]("/blogposts-optional", options.fetchOptions as any);
1357+
1358+
const request = getRequest();
1359+
const bodyText = await request.text();
1360+
1361+
return { bodyUsed: request.bodyUsed, bodyText };
1362+
};
1363+
1364+
it.each(ALL_METHODS)("missing body (with body serializer) - %s", async (method) => {
1365+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1366+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1367+
bodySerializer,
1368+
method,
1369+
fetchOptions: {},
1370+
});
1371+
1372+
expect(bodyUsed).toBe(false);
1373+
expect(bodyText).toBe("");
1374+
expect(bodySerializer).not.toBeCalled();
1375+
});
1376+
1377+
it.each(ALL_METHODS)("missing body (without body serializer) - %s", async (method) => {
1378+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ method, fetchOptions: {} });
1379+
1380+
expect(bodyUsed).toBe(false);
1381+
expect(bodyText).toBe("");
1382+
});
1383+
1384+
it.each(ALL_METHODS)("`undefined` body (with body serializer) - %s", async (method) => {
1385+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1386+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1387+
bodySerializer,
1388+
method,
1389+
fetchOptions: {
1390+
body: undefined,
1391+
},
1392+
});
1393+
1394+
expect(bodyUsed).toBe(false);
1395+
expect(bodyText).toBe("");
1396+
expect(bodySerializer).not.toBeCalled();
1397+
});
1398+
1399+
it.each(ALL_METHODS)("`undefined` body (without body serializer) - %s", async (method) => {
1400+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1401+
method,
1402+
fetchOptions: {
1403+
body: undefined,
1404+
},
1405+
});
1406+
1407+
expect(bodyUsed).toBe(false);
1408+
expect(bodyText).toBe("");
1409+
});
1410+
1411+
it.each(BODY_ACCEPTING_METHODS)("`null` body (with body serializer) - %s", async (method) => {
1412+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1413+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1414+
bodySerializer,
1415+
method,
1416+
fetchOptions: {
1417+
body: null,
1418+
},
1419+
});
1420+
1421+
expect(bodyUsed).toBe(true);
1422+
expect(bodyText).toBe("Serialized: null");
1423+
expect(bodySerializer).toBeCalled();
1424+
});
1425+
1426+
it.each(BODY_ACCEPTING_METHODS)("`null` body (without body serializer) - %s", async (method) => {
1427+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1428+
method,
1429+
fetchOptions: {
1430+
body: null,
1431+
},
1432+
});
1433+
1434+
expect(bodyUsed).toBe(true);
1435+
expect(bodyText).toBe("null");
1436+
});
1437+
1438+
it.each(BODY_ACCEPTING_METHODS)("`false` body (with body serializer) - %s", async (method) => {
1439+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1440+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1441+
bodySerializer,
1442+
method,
1443+
fetchOptions: {
1444+
body: false,
1445+
},
1446+
});
1447+
1448+
expect(bodyUsed).toBe(true);
1449+
expect(bodyText).toBe("Serialized: false");
1450+
expect(bodySerializer).toBeCalled();
1451+
});
1452+
1453+
it.each(BODY_ACCEPTING_METHODS)("`false` body (without body serializer) - %s", async (method) => {
1454+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1455+
method,
1456+
fetchOptions: {
1457+
body: false,
1458+
},
1459+
});
1460+
1461+
expect(bodyUsed).toBe(true);
1462+
expect(bodyText).toBe("false");
1463+
});
1464+
1465+
it.each(BODY_ACCEPTING_METHODS)("`''` body (with body serializer) - %s", async (method) => {
1466+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1467+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1468+
bodySerializer,
1469+
method,
1470+
fetchOptions: {
1471+
body: "",
1472+
},
1473+
});
1474+
1475+
expect(bodyUsed).toBe(true);
1476+
expect(bodyText).toBe('Serialized: ""');
1477+
expect(bodySerializer).toBeCalled();
1478+
});
1479+
1480+
it.each(BODY_ACCEPTING_METHODS)("`''` body (without body serializer) - %s", async (method) => {
1481+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1482+
method,
1483+
fetchOptions: {
1484+
body: "",
1485+
},
1486+
});
1487+
1488+
expect(bodyUsed).toBe(true);
1489+
expect(bodyText).toBe('""');
1490+
});
1491+
1492+
it.each(BODY_ACCEPTING_METHODS)("`0` body (with body serializer) - %s", async (method) => {
1493+
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
1494+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1495+
bodySerializer,
1496+
method,
1497+
fetchOptions: {
1498+
body: 0,
1499+
},
1500+
});
1501+
1502+
expect(bodyUsed).toBe(true);
1503+
expect(bodyText).toBe("Serialized: 0");
1504+
expect(bodySerializer).toBeCalled();
1505+
});
1506+
1507+
it.each(BODY_ACCEPTING_METHODS)("`0` body (without body serializer) - %s", async (method) => {
1508+
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
1509+
method,
1510+
fetchOptions: {
1511+
body: 0,
1512+
},
1513+
});
1514+
1515+
expect(bodyUsed).toBe(true);
1516+
expect(bodyText).toBe("0");
1517+
});
1518+
});
1519+
13381520
describe("requests", () => {
13391521
it("multipart/form-data", async () => {
13401522
const client = createClient<paths>({ baseUrl });

0 commit comments

Comments
 (0)