Skip to content

Commit

Permalink
fix: smater header defaults and handling
Browse files Browse the repository at this point in the history
- stop setting Content-Type header for multipart requests
- support HeaderInit in request objects
- support default headers

BREAKING CHANGE:
multipart/form-data headers are not set by oazapfts anymore as these are usually automatically set by the browser/node
when this causes problems on your end you can set Content-Type=multipart/form-data manually via RequestOpts

oazapfts runtime now works with `Header` instead of plain Objects `{}`. This might affect you when you use a custom
fetch implementation and manipulate headers there

fix #512
ref #509
  • Loading branch information
Xiphe committed Dec 14, 2023
1 parent b18de77 commit c9b62c1
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 109 deletions.
7 changes: 3 additions & 4 deletions demo/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,12 @@ describe("multipart", () => {
"http://localhost:8000/v2/pet/5/uploadImage",
expect.objectContaining({
body: expect.any(FormData),
headers: {
Accept: "application/json",
"Content-Type": "multipart/form-data",
},
method: "POST",
}),
);
expect(Object.fromEntries(customFetch.mock.calls[0][1].headers)).toEqual({
accept: "application/json",
});
const formData = customFetch.mock.calls[0][1].body as FormData;
expect(formData.getAll("files").length).toBe(10);
const meta = formData.getAll("imageMeta") as Blob[];
Expand Down
10 changes: 4 additions & 6 deletions demo/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,9 @@ export function deletePet(
return oazapfts.fetchText(`/pet/${encodeURIComponent(petId)}`, {
...opts,
method: "DELETE",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
api_key: apiKey,
},
}),
});
}
/**
Expand Down Expand Up @@ -321,10 +320,9 @@ export function customizePet(
{
...opts,
method: "POST",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-color-options": xColorOptions,
},
}),
},
);
}
Expand Down
10 changes: 4 additions & 6 deletions demo/enumApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,9 @@ export function deletePet(
return oazapfts.fetchText(`/pet/${encodeURIComponent(petId)}`, {
...opts,
method: "DELETE",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
api_key: apiKey,
},
}),
});
}
/**
Expand Down Expand Up @@ -320,10 +319,9 @@ export function customizePet(
{
...opts,
method: "POST",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-color-options": xColorOptions,
},
}),
},
);
}
Expand Down
10 changes: 4 additions & 6 deletions demo/mergedReadWriteApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,9 @@ export function deletePet(
return oazapfts.fetchText(`/pet/${encodeURIComponent(petId)}`, {
...opts,
method: "DELETE",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
api_key: apiKey,
},
}),
});
}
/**
Expand Down Expand Up @@ -294,10 +293,9 @@ export function customizePet(
{
...opts,
method: "POST",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-color-options": xColorOptions,
},
}),
},
);
}
Expand Down
10 changes: 4 additions & 6 deletions demo/optimisticApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,9 @@ export function deletePet(
oazapfts.fetchText(`/pet/${encodeURIComponent(petId)}`, {
...opts,
method: "DELETE",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
api_key: apiKey,
},
}),
}),
);
}
Expand Down Expand Up @@ -338,10 +337,9 @@ export function customizePet(
{
...opts,
method: "POST",
headers: {
...(opts && opts.headers),
headers: oazapfts.mergeHeaders(opts?.headers, {
"x-color-options": xColorOptions,
},
}),
},
),
);
Expand Down
34 changes: 16 additions & 18 deletions src/codegen/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1312,26 +1312,24 @@ export default class ApiGenerator {
init.push(
factory.createPropertyAssignment(
"headers",
factory.createObjectLiteralExpression(
[
factory.createSpreadAssignment(
factory.createLogicalAnd(
factory.createIdentifier("opts"),
factory.createPropertyAccessExpression(
factory.createIdentifier("opts"),
"headers",
callOazapftsFunction("mergeHeaders", [
factory.createPropertyAccessChain(
factory.createIdentifier("opts"),
factory.createToken(ts.SyntaxKind.QuestionDotToken),
"headers",
),
factory.createObjectLiteralExpression(
[
...header.map((param) =>
cg.createPropertyAssignment(
param.name,
factory.createIdentifier(getArgName(param)),
),
),
),
...header.map((param) =>
cg.createPropertyAssignment(
param.name,
factory.createIdentifier(getArgName(param)),
),
),
],
true,
),
],
true,
),
]),
),
);
}
Expand Down
37 changes: 37 additions & 0 deletions src/runtime/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export type CustomHeaders = Record<
string,
string | null | boolean | number | undefined
>;

export function mergeHeaders(
base: HeadersInit | CustomHeaders | undefined,
overwrite?: HeadersInit | CustomHeaders,
) {
const baseHeaders = normalizeHeaders(base);
const overwriteHeaders = normalizeHeaders(overwrite);

overwriteHeaders.forEach((value, key) => {
baseHeaders.set(key, value);
});

return baseHeaders;
}

export function normalizeHeaders(
headers: HeadersInit | CustomHeaders | undefined,
) {
// This might be custom header config containing null | boolean | number | undefined
// By default Headers constructor will convert them to string but we don't want that
// for nullish values.
if (headers && !(headers instanceof Headers) && !Array.isArray(headers)) {
return new Headers(
Object.fromEntries(
Object.entries(headers)
.filter(([, v]) => v != null)
.map(([k, v]) => [k, String(v)]),
),
);
}

return new Headers(headers);
}
56 changes: 51 additions & 5 deletions src/runtime/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,67 @@ describe("request", () => {
expect(err?.headers?.get("x-request-id")).toBe("1234");
});

it("should allow 'Content-Type' header to be customized", async () => {
it("allows 'Content-Type' header to be customized", () => {
const jsonUTF8ContentType = "application/json; charset=UTF-8";
const formUTF8ContentType =
"application/x-www-form-urlencoded; charset=UTF-8";
const customContentType = "application/acme-custom; charset=UTF-8";

const jsonResponse = oazapfts.json({
const jsonRequest = oazapfts.json({
body: { value: "body value" },
headers: { "Content-Type": jsonUTF8ContentType },
});
const formResponse = oazapfts.form({
const formRequest = oazapfts.form({
body: { value: "body value" },
headers: { "Content-Type": formUTF8ContentType },
});
const multipartRequest = oazapfts.multipart({
body: { value: "body value" },
headers: { "Content-Type": customContentType },
});

expect(jsonRequest.headers.get("Content-Type")).toBe(jsonUTF8ContentType);
expect(formRequest.headers.get("Content-Type")).toBe(formUTF8ContentType);
expect(multipartRequest.headers.get("Content-Type")).toBe(
customContentType,
);
});

it("sets default Content-Type headers to json and form requests", () => {
expect(
oazapfts
.json({
body: { value: "body value" },
})
.headers.get("Content-Type"),
).toBe("application/json");

expect(
oazapfts
.form({
body: { value: "body value" },
})
.headers.get("Content-Type"),
).toBe("application/x-www-form-urlencoded");
});

// https://github.com/oazapfts/oazapfts/issues/512
it("does not set default Content-Type headers for multipart requests", () => {
expect(
oazapfts
.multipart({
body: { value: "body value" },
})
.headers.has("Content-Type"),
).toBe(false);
});

it("allows multiple headers with the same name", () => {
const headers = new Headers();
headers.append("x-header", "value1");
headers.append("x-header", "value2");
const request = oazapfts.json({ headers });

expect(jsonResponse.headers["Content-Type"]).toEqual(jsonUTF8ContentType);
expect(formResponse.headers["Content-Type"]).toEqual(formUTF8ContentType);
expect(request.headers.get("x-header")).toBe("value1, value2");
});
});

0 comments on commit c9b62c1

Please sign in to comment.