Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Open Utility for Merging Headers #685

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 37 additions & 20 deletions src/gaxios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@

// prepare headers
if (input && typeof input === 'object' && 'headers' in input) {
this.#mergeHeaders(headers, input.headers);
Gaxios.mergeHeaders(headers, input.headers);
}
if (init) {
this.#mergeHeaders(headers, new Headers(init.headers));
Gaxios.mergeHeaders(headers, new Headers(init.headers));
}

// prepare request
Expand All @@ -135,7 +135,7 @@
* Perform an HTTP request with the given options.
* @param opts Set of HTTP options that will be used for this HTTP request.
*/
async request<T = any>(opts: GaxiosOptions = {}): GaxiosPromise<T> {

Check warning on line 138 in src/gaxios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
let prepared = await this.#prepareRequest(opts);
prepared = await this.#applyRequestInterceptors(prepared);
return this.#applyResponseInterceptors(this._request(prepared));
Expand Down Expand Up @@ -177,7 +177,7 @@
* Internal, retryable version of the `request` method.
* @param opts Set of HTTP options that will be used for this HTTP request.
*/
protected async _request<T = any>(

Check warning on line 180 in src/gaxios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
opts: GaxiosOptionsPrepared,
): GaxiosPromise<T> {
try {
Expand Down Expand Up @@ -236,7 +236,7 @@
private async getResponseData(
opts: GaxiosOptionsPrepared,
res: Response,
): Promise<any> {

Check warning on line 239 in src/gaxios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
if (
opts.maxContentLength &&
res.headers.has('content-length') &&
Expand Down Expand Up @@ -362,23 +362,6 @@
return promiseChain;
}

/**
* Merges headers.
*
* @param base headers to append/overwrite to
* @param append headers to append/overwrite with
* @returns the base headers instance with merged `Headers`
*/
#mergeHeaders(base: Headers, append?: Headers) {
append?.forEach((value, key) => {
// set-cookie is the only header that would repeat.
// A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie
key === 'set-cookie' ? base.append(key, value) : base.set(key, value);
});

return base;
}

/**
* Validates the options, merges them with defaults, and prepare request.
*
Expand All @@ -390,7 +373,7 @@
): Promise<GaxiosOptionsPrepared> {
// Prepare Headers - copy in order to not mutate the original objects
const preparedHeaders = new Headers(this.defaults.headers);
this.#mergeHeaders(preparedHeaders, options.headers);
Gaxios.mergeHeaders(preparedHeaders, options.headers);

// Merge options
const opts = extend(true, {}, this.defaults, options);
Expand Down Expand Up @@ -577,7 +560,7 @@
*/
private async getResponseDataFromContentType(
response: Response,
): Promise<any> {

Check warning on line 563 in src/gaxios.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
let contentType = response.headers.get('Content-Type');
if (contentType === null) {
// Maintain existing functionality by calling text()
Expand Down Expand Up @@ -664,4 +647,38 @@

return this.#fetch;
}

/**
* Merges headers.
* If the base headers do not exist a new `Headers` object will be returned.
*
* @remarks
*
* Using this utility can be helpful when the headers are not known to exist:
* - if they exist as `Headers`, that instance will be used
* - it improves performance and allows users to use their existing references to their `Headers`
* - if they exist in another form (`HeadersInit`), they will be used to create a new `Headers` object
* - if the base headers do not exist a new `Headers` object will be created
*
* @param base headers to append/overwrite to
* @param append headers to append/overwrite with
* @returns the base headers instance with merged `Headers`
*/
static mergeHeaders(base?: HeadersInit, ...append: HeadersInit[]): Headers {
base = base instanceof Headers ? base : new Headers(base);

for (const headers of append) {
const add = headers instanceof Headers ? headers : new Headers(headers);

add.forEach((value, key) => {
// set-cookie is the only header that would repeat.
// A bit of background: https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie
key === 'set-cookie' ? base.append(key, value) : base.set(key, value);
});
}

return base;
}
}

type HeadersInit = ConstructorParameters<typeof Headers>[0];
47 changes: 47 additions & 0 deletions test/test.getch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,3 +1511,50 @@ describe('fetch-compatible API', () => {
assert.deepStrictEqual(res.data, {});
});
});

describe('merge headers', () => {
it('should merge Headers', () => {
const base = {a: 'a'};
const append = {b: 'b'};
const expected = new Headers({...base, ...append});

const matrixBase = [{...base}, Object.entries(base), new Headers(base)];
const matrixAppend = [
{...append},
Object.entries(append),
new Headers(append),
];

for (const base of matrixBase) {
for (const append of matrixAppend) {
const headers = Gaxios.mergeHeaders(base, append);

assert.deepStrictEqual(headers, expected);
}
}
});

it('should merge multiple Headers', () => {
const base = {a: 'a'};
const append = {b: 'b'};
const appendMore = {c: 'c'};
const expected = new Headers({...base, ...append, ...appendMore});

const headers = Gaxios.mergeHeaders(base, append, appendMore);

assert.deepStrictEqual(headers, expected);
});

it('should merge Set-Cookie Headers', () => {
const base = {'set-cookie': 'a=a'};
const append = {'set-cookie': 'b=b'};
const expected = new Headers([
['set-cookie', 'a=a'],
['set-cookie', 'b=b'],
]);

const headers = Gaxios.mergeHeaders(base, append);

assert.deepStrictEqual(headers.getSetCookie(), expected.getSetCookie());
});
});