diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a6abd..4f838e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [0.10.0] - 2024-09-08 + +### Added + +- support for `headers` in `ControllerMethodArg` as a shortcut to parse & + retrieve request headers + +### Changed + +- upgraded dependencies (`@oak/oak@17.0.0`, `@std/path@1.0.4` + `@std/assert@1.0.4`, `@std/testing@1.0.2`, `@std/io@0.224.7`) + +- minor TypeScript syntax updates to better support Deno 2 + ## [0.9.0] - 2024-07-16 ### Changed diff --git a/deps.ts b/deps.ts index 17dd3df..43b7b8d 100644 --- a/deps.ts +++ b/deps.ts @@ -1,13 +1,13 @@ -export { join } from "jsr:@std/path@^0.225.2"; +export { join } from "jsr:@std/path@^1.0.4"; -export { Router } from "jsr:@oak/oak@^16.1.0"; +export { Router } from "jsr:@oak/oak@^17.0.0"; export type { Application, Context, Next, RouteContext, -} from "jsr:@oak/oak@^16.1.0"; +} from "jsr:@oak/oak@^17.0.0"; import { extendZodWithOpenApi, diff --git a/dev_deps.ts b/dev_deps.ts index 13a3404..b9a4025 100644 --- a/dev_deps.ts +++ b/dev_deps.ts @@ -5,16 +5,16 @@ export { assertObjectMatch, assertStringIncludes, assertThrows, -} from "jsr:@std/assert@^1.0.0"; +} from "jsr:@std/assert@^1.0.4"; export { type BodyType, type Middleware, Request, testing as oakTesting, -} from "jsr:@oak/oak@^16.1.0"; +} from "jsr:@oak/oak@^17.0.0"; -export { Body } from "jsr:@oak/oak@^16.1.0/body"; +export { Body } from "jsr:@oak/oak@^17.0.0/body"; export { assertSpyCall, @@ -24,17 +24,17 @@ export { spy, type Stub, stub, -} from "jsr:@std/testing@^0.225.3/mock"; +} from "jsr:@std/testing@^1.0.2/mock"; -export { assertSnapshot } from "jsr:@std/testing@^0.225.3/snapshot"; +export { assertSnapshot } from "jsr:@std/testing@^1.0.2/snapshot"; -export { Buffer } from "jsr:@std/io@^0.224.3"; +export { Buffer } from "jsr:@std/io@^0.224.7"; export { afterEach, beforeEach, describe, it, -} from "jsr:@std/testing@^0.225.3/bdd"; +} from "jsr:@std/testing@^1.0.2/bdd"; export { ZodObject } from "npm:zod@^3.23.8"; diff --git a/jsr.json b/jsr.json index 030f042..55531ba 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@dklab/oak-routing-ctrl", - "version": "0.9.0", + "version": "0.10.0", "exports": { ".": "./mod.ts", "./mod": "./mod.ts" diff --git a/src/ControllerMethodArgs.ts b/src/ControllerMethodArgs.ts index 160899c..15bcd05 100644 --- a/src/ControllerMethodArgs.ts +++ b/src/ControllerMethodArgs.ts @@ -17,7 +17,8 @@ import { ERR_UNSUPPORTED_CLASS_METHOD_DECORATOR_RUNTIME_BEHAVIOR } from "./Const export type ControllerMethodArg = | "param" | "body" - | "query"; + | "query" + | "headers"; // an enhanced version of a (decorated) method which // is declared in the (decorated) Controller Class @@ -127,22 +128,26 @@ function getEnhancedHandler( try { parsedReqBody = await _internal.parseOakReqBody(ctx); } catch (e) { - return ctx.throw(400, `Unable to parse request body: ${e.message}`, { - stack: e.stack, - }); + return ctx.throw( + 400, + `Unable to parse request body: ${(e as Error).message}`, + { + stack: (e as Error).stack, + }, + ); } const parsedReqSearchParams: Record = {}; try { - ctx.request.url.searchParams.forEach((value, key) => + ctx.request.url.searchParams.forEach((value: string, key: string) => parsedReqSearchParams[key] = value ); } catch (e) { return ctx.throw( 400, - `Unable to parse request search params: ${e.message}`, + `Unable to parse request search params: ${(e as Error).message}`, { - stack: e.stack, + stack: (e as Error).stack, }, ); } @@ -164,6 +169,13 @@ function getEnhancedHandler( // search query a.k.a URLSearchParams decoratedArgs.push(parsedReqSearchParams); break; + case p === "headers": { + // request headers + const headers: Record = {}; + ctx.request.headers.forEach((v: string, k: string) => headers[k] = v); + decoratedArgs.push(headers); + break; + } case ["ctx", "context"].includes(p): // `ctx` or `context` is supported by default (as the last argument) // but can also be declared explicitly diff --git a/src/ControllerMethodArgs_test.ts b/src/ControllerMethodArgs_test.ts index 681da37..d290f23 100644 --- a/src/ControllerMethodArgs_test.ts +++ b/src/ControllerMethodArgs_test.ts @@ -108,7 +108,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring body, } as ClassMethodDecoratorContext, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(typeof enhancedHandler, "function"); @@ -120,12 +120,59 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring body, enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "i.am": "deep" }); }); +Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring body, param, query, headers", async () => { + let enhancedHandler; + let enhancedHandlerRetVal; + try { + enhancedHandler = ControllerMethodArgs("body", "param", "query", "headers")( + function testHandler( + _body: unknown, + _param: unknown, + _query: unknown, + headers: unknown, + ) { + // @TODO consider mocking `body`, `param`, and `query` and assert for + // their parsed values + return { + "i.am.also": "deep", + "my.headers.include": headers, + }; + }, + { + name: "testHandler", + } as ClassMethodDecoratorContext, + ); + } catch (e) { + throw new Error(`test case should not have thrown ${(e as Error).message}`); + } + assertEquals(typeof enhancedHandler, "function"); + + try { + const ctx = createMockContext(); + Object.defineProperty(ctx.request, "body", { + get: () => createMockRequestBody("json"), + }); + Object.defineProperty(ctx.request, "headers", { + value: new Map([["x-foo", "bar"]]), + }); + + enhancedHandlerRetVal = await enhancedHandler(ctx); + } catch (e) { + throw new Error(`test case should not have thrown ${(e as Error).message}`); + } + + assertEquals(enhancedHandlerRetVal, { + "i.am.also": "deep", + "my.headers.include": { "x-foo": "bar" }, + }); +}); + Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring param, body", async () => { let enhancedHandler; let enhancedHandlerRetVal; @@ -141,7 +188,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring param, } as ClassMethodDecoratorContext, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(typeof enhancedHandler, "function"); @@ -153,7 +200,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring param, enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "param, body" }); @@ -174,7 +221,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring param, } as ClassMethodDecoratorContext, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(typeof enhancedHandler, "function"); @@ -186,7 +233,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring param, enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "param, query" }); @@ -207,7 +254,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring body", } as ClassMethodDecoratorContext, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(typeof enhancedHandler, "function"); @@ -219,7 +266,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring body", enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "body" }); @@ -238,7 +285,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring nothin } as ClassMethodDecoratorContext, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(typeof enhancedHandler, "function"); @@ -250,7 +297,7 @@ Deno.test("ControllerMethodArgs Decorator - Standard strategy - declaring nothin enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "nothing" }); @@ -286,7 +333,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin methodDescriptor, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(decoratorRetVal, undefined); @@ -302,12 +349,65 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin }); enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "i.am": "deep.too" }); }); +Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declaring body, param, query, headers", async () => { + const methodDescriptor = { + value: function testHandler( + _body: unknown, + _param: unknown, + _query: unknown, + headers: unknown, + ) { + return { + "i.am.also": "deep.ya", + "my.headers.include": headers, + }; + }, + writable: true, + enumerable: false, + configurable: true, + }; + let decoratorRetVal; + try { + decoratorRetVal = ControllerMethodArgs("body", "param", "query", "headers")( + {}, + "testHandler", + methodDescriptor, + ); + } catch (e) { + throw new Error(`test case should not have thrown ${(e as Error).message}`); + } + assertEquals(decoratorRetVal, undefined); + + // by this point, `methodDescriptor.value` has been written with a new value + // which is the enhanced `testHandler` + let enhancedHandlerRetVal; + try { + // deno-lint-ignore ban-types + const enhancedHandler: Function = methodDescriptor.value; + const ctx = createMockContext(); + Object.defineProperty(ctx.request, "body", { + get: () => createMockRequestBody("json"), + }); + Object.defineProperty(ctx.request, "headers", { + value: new Map([["x-foo", "bar"]]), + }); + enhancedHandlerRetVal = await enhancedHandler(ctx); + } catch (e) { + throw new Error(`test case should not have thrown ${(e as Error).message}`); + } + + assertEquals(enhancedHandlerRetVal, { + "i.am.also": "deep.ya", + "my.headers.include": { "x-foo": "bar" }, + }); +}); + Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declaring param, body", async () => { const methodDescriptor = { value: function testHandler() { @@ -325,7 +425,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin methodDescriptor, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(decoratorRetVal, undefined); @@ -341,7 +441,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin }); enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "param, body" }); @@ -364,7 +464,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin methodDescriptor, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(decoratorRetVal, undefined); @@ -380,7 +480,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin }); enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "param, query" }); @@ -403,7 +503,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin methodDescriptor, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(decoratorRetVal, undefined); @@ -419,7 +519,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin }); enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "body" }); @@ -442,7 +542,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin methodDescriptor, ); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(decoratorRetVal, undefined); @@ -458,7 +558,7 @@ Deno.test("ControllerMethodArgs Decorator - CloudflareWorker strategy - declarin }); enhancedHandlerRetVal = await enhancedHandler(ctx); } catch (e) { - throw new Error(`test case should not have thrown ${e.message}`); + throw new Error(`test case should not have thrown ${(e as Error).message}`); } assertEquals(enhancedHandlerRetVal, { "declaring": "nothing" }); diff --git a/src/useOas.ts b/src/useOas.ts index cbc39c1..60dc67d 100644 --- a/src/useOas.ts +++ b/src/useOas.ts @@ -96,7 +96,10 @@ export const useOas = ( try { _useOas(app, cfg); } catch (e) { - debug("unable to complete OpenApiSpec initialization:", e.message); + debug( + "unable to complete OpenApiSpec initialization:", + (e as Error).message, + ); } };