From eefa6afd187aceb2da86a8bf7ebd3ed87fea0b65 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:15:15 -0800 Subject: [PATCH 1/7] feat: Open Utility for Merging Headers --- src/gaxios.ts | 43 +++++++++++++++++++++++-------------------- test/test.getch.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 51ef2f1..513b276 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -115,10 +115,10 @@ export class Gaxios implements FetchCompliance { // 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 @@ -362,23 +362,6 @@ export class Gaxios implements FetchCompliance { 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. * @@ -390,7 +373,7 @@ export class Gaxios implements FetchCompliance { ): Promise { // 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); @@ -664,4 +647,24 @@ export class Gaxios implements FetchCompliance { return this.#fetch; } + + /** + * Merges headers. + * + * @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); + append = append instanceof Headers ? append : new Headers(append); + + 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; + } } diff --git a/test/test.getch.ts b/test/test.getch.ts index debef58..f27473b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1511,3 +1511,41 @@ 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: HeadersInit[] = [ + {...base}, + Object.entries(base), + new Headers(base), + ]; + + const matrixAppend: HeadersInit[] = [ + {...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 set-cookie headers', () => { + const base = {'set-cookie': 'a=a'}; + const append = {'set-cookie': 'b=b'}; + const expected = new Headers({'set-cookie': 'a=a, b=b'}); + + const headers = Gaxios.mergeHeaders(base, append); + + assert.deepStrictEqual(headers, expected); + }); +}); From 515aa0892b8fcf2f06060e09d531529055800fee Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:27:07 -0800 Subject: [PATCH 2/7] fix: standard call --- test/test.getch.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test.getch.ts b/test/test.getch.ts index f27473b..40ce2ba 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1539,13 +1539,16 @@ describe('merge headers', () => { } }); - it('should merge set-cookie headers', () => { + it.only('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, b=b'}); + const expected = new Headers([ + ['set-cookie', 'a=a'], + ['set-cookie', 'b=b'], + ]); const headers = Gaxios.mergeHeaders(base, append); - assert.deepStrictEqual(headers, expected); + assert.deepStrictEqual(headers.getSetCookie(), expected.getSetCookie()); }); }); From 807ddbb5d86b5897db20b0678cfac541fa02c7fb Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:29:31 -0800 Subject: [PATCH 3/7] feat: Support `HeadersInit` for `GaxiosRequest#headers` --- src/common.ts | 18 ------------------ test/test.getch.ts | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/common.ts b/src/common.ts index 57aad5f..cf0d064 100644 --- a/src/common.ts +++ b/src/common.ts @@ -170,24 +170,6 @@ export interface GaxiosOptions extends RequestInit { defaultAdapter: (options: GaxiosOptionsPrepared) => GaxiosPromise, ) => GaxiosPromise; url?: string | URL; - /** - * Headers to add to the request. - * - * @remarks - * - * Using the proper Headers type has the following benefits: - * - creates consistency throughout the libraries; no need to check properties for different casing - * - see {@link https://github.com/googleapis/gaxios/issues/262 #262} - * - Alignment with {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/headers `Request#headers`} - * - Can easily append an existing header or create it ('upsert') if it does exist, like so: - * ```ts - * const headers = new Headers(); - * - * // creates, if not exist, or appends to an existing header - * headers.append('x-goog-api-client', 'abc'); - * ``` - */ - headers?: Headers; baseURL?: string | URL; /** * The data to send in the {@link RequestInit.body} of the request. Objects will be diff --git a/test/test.getch.ts b/test/test.getch.ts index f27473b..baa2339 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -169,7 +169,7 @@ describe('🥁 configuration options', () => { const inst = new Gaxios({headers: new Headers({apple: 'juice'})}); const res = await inst.request({ url, - headers: new Headers({figgy: 'pudding'}), + headers: {figgy: 'pudding'}, }); scope.done(); assert.strictEqual(res.config.headers.get('apple'), 'juice'); From a806b9a984a388deb1398d3a2ed11f83d867c14a Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 16:30:51 -0800 Subject: [PATCH 4/7] fix: only --- test/test.getch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.getch.ts b/test/test.getch.ts index 40ce2ba..8c3d74b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1539,7 +1539,7 @@ describe('merge headers', () => { } }); - it.only('should merge set-cookie headers', () => { + it('should merge set-cookie headers', () => { const base = {'set-cookie': 'a=a'}; const append = {'set-cookie': 'b=b'}; const expected = new Headers([ From dbd95702b3863a960de45c28cc59617add913a78 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 17:06:55 -0800 Subject: [PATCH 5/7] chore: HeadersInit global does not exist in TS by default --- src/gaxios.ts | 2 ++ test/test.getch.ts | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 513b276..639ab3f 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -668,3 +668,5 @@ export class Gaxios implements FetchCompliance { return base; } } + +type HeadersInit = ConstructorParameters[0]; diff --git a/test/test.getch.ts b/test/test.getch.ts index 8c3d74b..28eecfb 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1518,13 +1518,8 @@ describe('merge headers', () => { const append = {b: 'b'}; const expected = new Headers({...base, ...append}); - const matrixBase: HeadersInit[] = [ - {...base}, - Object.entries(base), - new Headers(base), - ]; - - const matrixAppend: HeadersInit[] = [ + const matrixBase = [{...base}, Object.entries(base), new Headers(base)]; + const matrixAppend = [ {...append}, Object.entries(append), new Headers(append), From 613cff41a9b741209662a5020cbab53de8e92281 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Fri, 7 Feb 2025 23:23:38 -0800 Subject: [PATCH 6/7] feat: Support merging multiple headers in one call --- src/gaxios.ts | 17 ++++++++++------- test/test.getch.ts | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 639ab3f..86e4b54 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -655,15 +655,18 @@ export class Gaxios implements FetchCompliance { * @param append headers to append/overwrite with * @returns the base headers instance with merged `Headers` */ - static mergeHeaders(base: HeadersInit, append?: HeadersInit): Headers { + static mergeHeaders(base: HeadersInit, ...append: HeadersInit[]): Headers { base = base instanceof Headers ? base : new Headers(base); - append = append instanceof Headers ? append : new Headers(append); - 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); - }); + 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; } diff --git a/test/test.getch.ts b/test/test.getch.ts index 28eecfb..d02102b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -1513,7 +1513,7 @@ describe('fetch-compatible API', () => { }); describe('merge headers', () => { - it('should merge headers', () => { + it('should merge Headers', () => { const base = {a: 'a'}; const append = {b: 'b'}; const expected = new Headers({...base, ...append}); @@ -1534,7 +1534,18 @@ describe('merge headers', () => { } }); - it('should merge set-cookie headers', () => { + 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([ From fa3061084ed446161af7c5a68bd8cd80851985cf Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Sat, 8 Feb 2025 01:24:13 -0800 Subject: [PATCH 7/7] docs: additional docs --- src/gaxios.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gaxios.ts b/src/gaxios.ts index 86e4b54..8b41892 100644 --- a/src/gaxios.ts +++ b/src/gaxios.ts @@ -650,12 +650,20 @@ export class Gaxios implements FetchCompliance { /** * 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 + * - if they exist in another form, they will be used as a new Headers object + * - if they do not exist * * @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 { + static mergeHeaders(base?: HeadersInit, ...append: HeadersInit[]): Headers { base = base instanceof Headers ? base : new Headers(base); for (const headers of append) {