Skip to content

Commit

Permalink
Allow falsy values (except undefined) as a valid body (#1825)
Browse files Browse the repository at this point in the history
* Allow falsy values (except undefined) as a valid body

* Add change-set
  • Loading branch information
goce-cz authored Aug 30, 2024
1 parent 246f3b7 commit 6038f8f
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/rich-crews-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Allow falsy values (except undefined) as a valid body
2 changes: 1 addition & 1 deletion packages/openapi-fetch/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function createClient(clientOptions) {
...init,
headers: mergeHeaders(baseHeaders, headers, params.header),
};
if (requestInit.body) {
if (requestInit.body !== undefined) {
requestInit.body = bodySerializer(requestInit.body);
// remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression
if (requestInit.body instanceof FormData) {
Expand Down
182 changes: 182 additions & 0 deletions packages/openapi-fetch/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { HttpResponse, type StrictResponse } from "msw";
import { afterAll, beforeAll, describe, expect, expectTypeOf, it } from "vitest";
import createClient, {
type BodySerializer,
type FetchOptions,
type MethodResponse,
type Middleware,
type MiddlewareCallbackParams,
Expand Down Expand Up @@ -1335,6 +1337,186 @@ describe("client", () => {
});
});

describe("body serialization", () => {
const BODY_ACCEPTING_METHODS = [["PUT"], ["POST"], ["DELETE"], ["OPTIONS"], ["PATCH"]] as const;
const ALL_METHODS = [...BODY_ACCEPTING_METHODS, ["GET"], ["HEAD"]] as const;

const fireRequestAndGetBodyInformation = async (options: {
bodySerializer?: BodySerializer<unknown>;
method: (typeof ALL_METHODS)[number][number];
fetchOptions: FetchOptions<any>;
}) => {
const client = createClient<any>({ baseUrl, bodySerializer: options.bodySerializer });
const { getRequest } = useMockRequestHandler({
baseUrl,
method: "all",
path: "/blogposts-optional",
status: 200,
});
await client[options.method]("/blogposts-optional", options.fetchOptions as any);

const request = getRequest();
const bodyText = await request.text();

return { bodyUsed: request.bodyUsed, bodyText };
};

it.each(ALL_METHODS)("missing body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {},
});

expect(bodyUsed).toBe(false);
expect(bodyText).toBe("");
expect(bodySerializer).not.toBeCalled();
});

it.each(ALL_METHODS)("missing body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({ method, fetchOptions: {} });

expect(bodyUsed).toBe(false);
expect(bodyText).toBe("");
});

it.each(ALL_METHODS)("`undefined` body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {
body: undefined,
},
});

expect(bodyUsed).toBe(false);
expect(bodyText).toBe("");
expect(bodySerializer).not.toBeCalled();
});

it.each(ALL_METHODS)("`undefined` body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
method,
fetchOptions: {
body: undefined,
},
});

expect(bodyUsed).toBe(false);
expect(bodyText).toBe("");
});

it.each(BODY_ACCEPTING_METHODS)("`null` body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {
body: null,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("Serialized: null");
expect(bodySerializer).toBeCalled();
});

it.each(BODY_ACCEPTING_METHODS)("`null` body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
method,
fetchOptions: {
body: null,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("null");
});

it.each(BODY_ACCEPTING_METHODS)("`false` body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {
body: false,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("Serialized: false");
expect(bodySerializer).toBeCalled();
});

it.each(BODY_ACCEPTING_METHODS)("`false` body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
method,
fetchOptions: {
body: false,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("false");
});

it.each(BODY_ACCEPTING_METHODS)("`''` body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {
body: "",
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe('Serialized: ""');
expect(bodySerializer).toBeCalled();
});

it.each(BODY_ACCEPTING_METHODS)("`''` body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
method,
fetchOptions: {
body: "",
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe('""');
});

it.each(BODY_ACCEPTING_METHODS)("`0` body (with body serializer) - %s", async (method) => {
const bodySerializer = vi.fn((body) => `Serialized: ${JSON.stringify(body)}`);
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
bodySerializer,
method,
fetchOptions: {
body: 0,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("Serialized: 0");
expect(bodySerializer).toBeCalled();
});

it.each(BODY_ACCEPTING_METHODS)("`0` body (without body serializer) - %s", async (method) => {
const { bodyUsed, bodyText } = await fireRequestAndGetBodyInformation({
method,
fetchOptions: {
body: 0,
},
});

expect(bodyUsed).toBe(true);
expect(bodyText).toBe("0");
});
});

describe("requests", () => {
it("multipart/form-data", async () => {
const client = createClient<paths>({ baseUrl });
Expand Down

0 comments on commit 6038f8f

Please sign in to comment.