Skip to content

Commit 181c4de

Browse files
authored
Allow unsetting default headers (#1314)
1 parent 43742fc commit 181c4de

File tree

4 files changed

+29
-9
lines changed

4 files changed

+29
-9
lines changed

Diff for: .changeset/gentle-socks-wink.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": patch
3+
---
4+
5+
Make headers typing friendlier

Diff for: .changeset/weak-games-exercise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-fetch": patch
3+
---
4+
5+
Allow unsetting headers

Diff for: packages/openapi-fetch/src/index.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,16 @@ describe("client", () => {
337337
);
338338
});
339339

340+
it("allows unsetting headers", async () => {
341+
const client = createClient<paths>({ headers: { "Content-Type": null } });
342+
mockFetchOnce({ status: 200, body: JSON.stringify({ email: "[email protected]" }) });
343+
await client.GET("/self", { params: {} });
344+
345+
// assert default headers were passed
346+
const options = fetchMocker.mock.calls[0][1];
347+
expect(options?.headers).toEqual(new Headers());
348+
});
349+
340350
it("accepts a custom fetch function", async () => {
341351
const data = { works: true };
342352
const customFetch = {

Diff for: packages/openapi-fetch/src/index.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const TRAILING_SLASH_RE = /\/*$/;
1111
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */
1212

1313
/** options for each client instance */
14-
interface ClientOptions extends RequestInit {
14+
interface ClientOptions extends Omit<RequestInit, "headers"> {
1515
/** set the common root URL for all API requests */
1616
baseUrl?: string;
1717
/** custom fetch (defaults to globalThis.fetch) */
@@ -20,7 +20,10 @@ interface ClientOptions extends RequestInit {
2020
querySerializer?: QuerySerializer<unknown>;
2121
/** global bodySerializer */
2222
bodySerializer?: BodySerializer<unknown>;
23+
// headers override to make typing friendlier
24+
headers?: HeadersOptions;
2325
}
26+
export type HeadersOptions = HeadersInit | Record<string, string | number | boolean | null | undefined>;
2427
export type QuerySerializer<T> = (query: T extends { parameters: any } ? NonNullable<T["parameters"]["query"]> : Record<string, unknown>) => string;
2528
export type BodySerializer<T> = (body: OperationRequestBodyContent<T>) => any;
2629
export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream";
@@ -43,17 +46,12 @@ export type RequestOptions<T> = ParamsOption<T> &
4346
export default function createClient<Paths extends {}>(clientOptions: ClientOptions = {}) {
4447
const { fetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, ...options } = clientOptions;
4548

46-
const defaultHeaders = new Headers({
47-
...DEFAULT_HEADERS,
48-
...(options.headers ?? {}),
49-
});
50-
5149
async function coreFetch<P extends keyof Paths, M extends HttpMethod>(url: P, fetchOptions: FetchOptions<M extends keyof Paths[P] ? Paths[P][M] : never>): Promise<FetchResponse<M extends keyof Paths[P] ? Paths[P][M] : unknown>> {
5250
const { headers, body: requestBody, params = {}, parseAs = "json", querySerializer = globalQuerySerializer ?? defaultQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, ...init } = fetchOptions || {};
5351

5452
// URL
5553
const finalURL = createFinalURL(url as string, { baseUrl: options.baseUrl, params, querySerializer });
56-
const finalHeaders = mergeHeaders(defaultHeaders as any, headers as any, (params as any).header);
54+
const finalHeaders = mergeHeaders(DEFAULT_HEADERS, clientOptions?.headers, headers, (params as any).header);
5755

5856
// fetch!
5957
const requestInit: RequestInit = { redirect: "follow", ...options, ...init, headers: finalHeaders };
@@ -157,13 +155,15 @@ export function createFinalURL<O>(url: string, options: { baseUrl?: string; para
157155
}
158156

159157
/** merge headers a and b, with b taking priority */
160-
export function mergeHeaders(...allHeaders: (Record<string, unknown> | Headers)[]): Headers {
158+
export function mergeHeaders(...allHeaders: (HeadersOptions | undefined)[]): Headers {
161159
const headers = new Headers();
162160
for (const headerSet of allHeaders) {
163161
if (!headerSet || typeof headerSet !== "object") continue;
164162
const iterator = headerSet instanceof Headers ? headerSet.entries() : Object.entries(headerSet);
165163
for (const [k, v] of iterator) {
166-
if (v !== undefined && v !== null) {
164+
if (v === null) {
165+
headers.delete(k);
166+
} else if (v !== undefined) {
167167
headers.set(k, v as any);
168168
}
169169
}

0 commit comments

Comments
 (0)