diff --git a/src/gaxios.ts b/src/gaxios.ts index 51ef2f1..b60e8c2 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,38 @@ export class Gaxios implements FetchCompliance { 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[0]; diff --git a/test/test.getch.ts b/test/test.getch.ts index debef58..d02102b 100644 --- a/test/test.getch.ts +++ b/test/test.getch.ts @@ -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()); + }); +});