diff --git a/deno.json b/deno.json index 83c2ac7..fe1cfd2 100644 --- a/deno.json +++ b/deno.json @@ -8,8 +8,8 @@ }, "imports": { "@deno/dnt": "jsr:@deno/dnt@^0.41.3", - "@std/expect": "jsr:@std/expect@^1.0.6", - "@std/testing": "jsr:@std/testing@^1.0.3", + "@std/expect": "jsr:@std/expect@^1.0.9", + "@std/testing": "jsr:@std/testing@^1.0.6", "#fetch": "./src/fetch/mod.ts", "#response": "./src/response/mod.ts" }, diff --git a/deno.lock b/deno.lock index 1576f10..61c9ae9 100644 --- a/deno.lock +++ b/deno.lock @@ -7,26 +7,27 @@ "jsr:@deno/graph@~0.73.1": "0.73.1", "jsr:@std/assert@0.223": "0.223.0", "jsr:@std/assert@0.226": "0.226.0", - "jsr:@std/assert@^1.0.6": "1.0.6", - "jsr:@std/async@^1.0.5": "1.0.7", + "jsr:@std/assert@^1.0.9": "1.0.9", "jsr:@std/bytes@0.223": "0.223.0", "jsr:@std/data-structures@^1.0.4": "1.0.4", - "jsr:@std/expect@^1.0.6": "1.0.6", + "jsr:@std/expect@^1.0.9": "1.0.9", "jsr:@std/fmt@0.223": "0.223.0", "jsr:@std/fmt@1": "1.0.3", "jsr:@std/fs@0.223": "0.223.0", - "jsr:@std/fs@1": "1.0.5", + "jsr:@std/fs@1": "1.0.6", "jsr:@std/fs@^1.0.4": "1.0.5", + "jsr:@std/fs@^1.0.6": "1.0.6", "jsr:@std/fs@~0.229.3": "0.229.3", - "jsr:@std/internal@^1.0.4": "1.0.4", + "jsr:@std/internal@^1.0.5": "1.0.5", "jsr:@std/io@0.223": "0.223.0", "jsr:@std/path@0.223": "0.223.0", - "jsr:@std/path@1": "1.0.7", + "jsr:@std/path@1": "1.0.8", "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", "jsr:@std/path@^1.0.6": "1.0.7", - "jsr:@std/path@^1.0.7": "1.0.7", + "jsr:@std/path@^1.0.7": "1.0.8", + "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/path@~0.225.2": "0.225.2", - "jsr:@std/testing@^1.0.3": "1.0.3", + "jsr:@std/testing@^1.0.6": "1.0.6", "jsr:@ts-morph/bootstrap@0.24": "0.24.0", "jsr:@ts-morph/common@0.24": "0.24.0" }, @@ -64,25 +65,22 @@ "@std/assert@0.226.0": { "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" }, - "@std/assert@1.0.6": { - "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", + "@std/assert@1.0.9": { + "integrity": "a9f0c611a869cc791b26f523eec54c7e187aab7932c2c8e8bea0622d13680dcd", "dependencies": [ "jsr:@std/internal" ] }, - "@std/async@1.0.7": { - "integrity": "f4fadc0124432e37cba11e8b3880164661a664de00a65118d976848f32f96290" - }, "@std/bytes@0.223.0": { "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" }, "@std/data-structures@1.0.4": { "integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0" }, - "@std/expect@1.0.6": { - "integrity": "dffba969ca5ea6d7c39338d4985c9af2bfa6e080c710cc2b77756dd7657c369c", + "@std/expect@1.0.9": { + "integrity": "108bb428f17492ac40439479e1dc55fbaae581530e905a8603f97305842a5a01", "dependencies": [ - "jsr:@std/assert@^1.0.6", + "jsr:@std/assert@^1.0.9", "jsr:@std/internal" ] }, @@ -107,8 +105,14 @@ "jsr:@std/path@^1.0.7" ] }, - "@std/internal@1.0.4": { - "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" + "@std/fs@1.0.6": { + "integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2", + "dependencies": [ + "jsr:@std/path@^1.0.8" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" }, "@std/io@0.223.0": { "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", @@ -135,15 +139,17 @@ "@std/path@1.0.7": { "integrity": "76a689e07f0e15dcc6002ec39d0866797e7156629212b28f27179b8a5c3b33a1" }, - "@std/testing@1.0.3": { - "integrity": "f98c2bee53860a5916727d7e7d3abe920dd6f9edace022e2d059f00d05c2cf42", + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, + "@std/testing@1.0.6": { + "integrity": "9192ded2d34065f4c959fdc1921fe836770abb9194410c2cc8a0fff4eff5c893", "dependencies": [ - "jsr:@std/assert@^1.0.6", - "jsr:@std/async", + "jsr:@std/assert@^1.0.9", "jsr:@std/data-structures", - "jsr:@std/fs@^1.0.4", + "jsr:@std/fs@^1.0.6", "jsr:@std/internal", - "jsr:@std/path@^1.0.6" + "jsr:@std/path@^1.0.8" ] }, "@ts-morph/bootstrap@0.24.0": { @@ -163,8 +169,8 @@ "workspace": { "dependencies": [ "jsr:@deno/dnt@~0.41.3", - "jsr:@std/expect@^1.0.6", - "jsr:@std/testing@^1.0.3" + "jsr:@std/expect@^1.0.9", + "jsr:@std/testing@^1.0.6" ] } } diff --git a/src/fetch/__test__/applyMiddleware.test.ts b/src/fetch/__test__/applyMiddleware.test.ts index a5bde49..32749cf 100644 --- a/src/fetch/__test__/applyMiddleware.test.ts +++ b/src/fetch/__test__/applyMiddleware.test.ts @@ -1,9 +1,9 @@ -import { it } from '@std/testing/bdd'; +import { test } from '@std/testing/bdd'; import { expect } from '@std/expect'; import { applyMiddleware } from '../apply-middleware.ts'; import type { Middleware } from '../types.ts'; -it('should apply middleware in the order in which they are passed', async () => { +test('should apply middleware in the order in which they are passed', async () => { const log: string[] = []; const foo: Middleware = async (request, next) => { diff --git a/src/middleware/__test__/default-headers.test.ts b/src/middleware/__test__/default-headers.test.ts new file mode 100644 index 0000000..0ed6243 --- /dev/null +++ b/src/middleware/__test__/default-headers.test.ts @@ -0,0 +1,119 @@ +import { test } from '@std/testing/bdd'; +import { expect } from '@std/expect'; +import { defaultHeaders } from '../default-headers.ts'; + +test('strategy "set", should be default', async () => { + const middleware = defaultHeaders({ + 'content-type': 'application/json', + }); + + const request = new Request('https://foo.bar', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }); + + expect(request.headers.get('Content-Type')).toBe('text/plain;charset=UTF-8'); + + const next = (req: Request) => { + state.request = req; + return new Response('ok'); + }; + + const state: { request: Request | null } = { + request: null, + }; + + await middleware(request, next); + + expect(state.request?.headers.get('Content-Type')).toBe('application/json'); +}); + +test('strategy "append"', async () => { + const middleware = defaultHeaders({ + 'content-type': 'application/json', + }, { + strategy: 'append', + }); + + const request = new Request('https://foo.bar', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }); + + expect(request.headers.get('Content-Type')).toBe('text/plain;charset=UTF-8'); + + const next = (req: Request) => { + state.request = req; + return new Response('ok'); + }; + + const state: { request: Request | null } = { + request: null, + }; + + await middleware(request, next); + + expect(state.request?.headers.get('Content-Type')).toBe( + 'text/plain;charset=UTF-8, application/json', + ); +}); + +test('strategy "defaults-set"', async () => { + const middleware = defaultHeaders({ + 'content-type': 'application/json', + }, { + strategy: 'defaults-set', + }); + + const request = new Request('https://foo.bar', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }); + + expect(request.headers.get('Content-Type')).toBe('text/plain;charset=UTF-8'); + + const next = (req: Request) => { + state.request = req; + return new Response('ok'); + }; + + const state: { request: Request | null } = { + request: null, + }; + + await middleware(request, next); + + expect(state.request?.headers.get('Content-Type')).toBe( + 'text/plain;charset=UTF-8', + ); +}); + +test('strategy "defaults-set"', async () => { + const middleware = defaultHeaders({ + 'content-type': 'application/json', + }, { + strategy: 'defaults-append', + }); + + const request = new Request('https://foo.bar', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }), + }); + + expect(request.headers.get('Content-Type')).toBe('text/plain;charset=UTF-8'); + + const next = (req: Request) => { + state.request = req; + return new Response('ok'); + }; + + const state: { request: Request | null } = { + request: null, + }; + + await middleware(request, next); + + expect(state.request?.headers.get('Content-Type')).toBe( + 'application/json, text/plain;charset=UTF-8', + ); +}); diff --git a/src/middleware/__test__/retry.test.ts b/src/middleware/__test__/retry.test.ts index d733c2a..46a0914 100644 --- a/src/middleware/__test__/retry.test.ts +++ b/src/middleware/__test__/retry.test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, it } from '@std/testing/bdd'; +import { afterAll, beforeAll, test } from '@std/testing/bdd'; import { expect } from '@std/expect'; import { applyMiddleware, configureFetch } from '#fetch'; import { retry } from '../retry.ts'; @@ -22,7 +22,7 @@ afterAll(async () => { await server.shutdown(); }); -it('Should fetch resource until it returns success response)', async () => { +test('Should fetch resource until it returns success response)', async () => { const fetch = configureFetch(globalThis.fetch, applyMiddleware(retry({ count: 10 }))); expect(serverState.counter).toBe(3); @@ -37,7 +37,7 @@ it('Should fetch resource until it returns success response)', async () => { await dump(response); }); -it('Should break fetch loop when attempt limit is reached', async () => { +test('Should break fetch loop when attempt limit is reached', async () => { const fetch = configureFetch(globalThis.fetch, applyMiddleware(retry({ count: 2 }))); serverState.counter = 5; diff --git a/src/middleware/default-headers.ts b/src/middleware/default-headers.ts index 8f82f91..79fba0e 100644 --- a/src/middleware/default-headers.ts +++ b/src/middleware/default-headers.ts @@ -3,11 +3,13 @@ import type { Middleware } from '#fetch'; /** Options of default headers middleware. */ export interface DefaultHeadersOptions { /** - * How to add header to request headers. - * - "set" - headers will be added using "set" method - * - "append" - headers will be added using "append" method + * How to add headers to request headers. + * - "set" - `defaults` will be added to `request.headers` using "set" method + * - "append" - `defaults` will be added to `request.headers` using "append" method + * - "defaults-set" - `request.headers` will be added to `defaults` using "set" method + * - "defaults-append" - `request.headers` will be added to `defaults` using "append" method */ - strategy?: 'set' | 'append'; + strategy?: 'set' | 'append' | 'defaults-set' | 'defaults-append'; } /** @@ -17,24 +19,52 @@ export interface DefaultHeadersOptions { */ export function defaultHeaders( defaults: HeadersInit, - { strategy = 'append' }: DefaultHeadersOptions = {}, + { strategy = 'set' }: DefaultHeadersOptions = {}, ): Middleware { return (request, next) => { + /** + * Previously, there was a different approach here: + * headers were created based on "defaults" argument, + * then headers from the request were added to them. + * + * This was done so that the "default headers" were + * truly default and were overridden by what was set by + * the developer in the request itself. + * + * But it didn't work well because browser always + * had the "Content-Type" header set by default, + * which always overridden the option that was in the + * middleware factory arguments. + * + * To fix this, `strategy` option is introduced. + */ + // IMPORTANT: for avoid mutate request, just create new Headers and Request here - const headers = new Headers(request.headers); + const headers = new Headers(strategy.startsWith('defaults-') ? defaults : request.headers); - /* - Previously, there was a different approach here: headers were created based on "defaults" argument, then headers from the request were added to them. + if (strategy === 'defaults-set') { + request.headers.forEach((value, key) => { + headers.set(key, value); + }); + } - This was done so that the "default headers" were truly default and were overridden by what was set by the developer in the request itself. + if (strategy === 'defaults-append') { + request.headers.forEach((value, key) => { + headers.append(key, value); + }); + } - But it didn't work well because browser always had the "Content-Type" header set by default, which always overridden the option that was in the middleware factory arguments. + if (strategy === 'set') { + new Headers(defaults).forEach((value, key) => { + headers.set(key, value); + }); + } - To fix this, default headers are now added to the request headers - */ - new Headers(defaults).forEach((value, key) => { - headers[strategy](key, value); - }); + if (strategy === 'append') { + new Headers(defaults).forEach((value, key) => { + headers.append(key, value); + }); + } return next(new Request(request, { headers })); }; diff --git a/src/url/__test__/url.test.ts b/src/url/__test__/url.test.ts index dbe3d83..35a342b 100644 --- a/src/url/__test__/url.test.ts +++ b/src/url/__test__/url.test.ts @@ -1,8 +1,8 @@ -import { it } from '@std/testing/bdd'; +import { test } from '@std/testing/bdd'; import { expect } from '@std/expect'; import { resetParams, setParams, withoutParams, withParams } from '../url.ts'; -it('withParams', () => { +test('withParams', () => { const input = new URL('http://hello.com?foo=1&bar=2'); expect(input.searchParams.get('foo')).toBe('1'); @@ -15,7 +15,7 @@ it('withParams', () => { expect(output.searchParams.get('baz')).toBe('4'); }); -it('withoutParams', () => { +test('withoutParams', () => { const input = new URL('http://hello.com?foo=1&bar=2'); expect(input.searchParams.get('foo')).toBe('1'); @@ -28,7 +28,7 @@ it('withoutParams', () => { expect(output.searchParams.get('baz')).toBe(null); }); -it('setParams', () => { +test('setParams', () => { const input = new URL('http://hello.com?foo=1&bar=2'); expect(input.searchParams.get('foo')).toBe('1'); @@ -42,7 +42,7 @@ it('setParams', () => { expect(output.searchParams.get('baz')).toBe('4'); }); -it('resetParams', () => { +test('resetParams', () => { const input = new URL('http://hello.com?foo=1&bar=2'); expect(input.searchParams.get('foo')).toBe('1');