From 888d058b900b29fa39a2d77a6aa5e8946f2ee5e7 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Wed, 7 Feb 2024 00:42:36 +0100 Subject: [PATCH] feat(http): also read path parameters in HttpRequestParser This allows to access in http listeners all parameters that come from the url. ```typescript @http.GET('team/:id') async team(id: string) {} onAuth.listen((event, parser: HttpRequestParser<{id: string}>) => { const data = await parser(); console.log(data.teamId); }); ``` These parameters must be present in the method/function definition, otherwise they are not included (the definition alone in the path string is not enough) --- packages/http/src/model.ts | 23 ++++++++++++++++++++--- packages/http/src/request-parser.ts | 2 +- packages/http/tests/router.spec.ts | 6 ++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/http/src/model.ts b/packages/http/src/model.ts index b71a08641..4577081ba 100644 --- a/packages/http/src/model.ts +++ b/packages/http/src/model.ts @@ -135,15 +135,16 @@ export type HttpBody = T & TypeAnnotation<'httpBody'>; export type HttpBodyValidation = ValidatedBody & TypeAnnotation<'httpBodyValidation'>; export interface HttpRequestParserOptions { + withPath?: boolean; withBody?: boolean; withQuery?: boolean; withHeader?: boolean; } /** - * Delays the parsing of the body/query/header to the very last moment, when the parameter is actually used. + * Delays the parsing of the path/body/query/header to the very last moment, when the parameter is actually used. * - * If no options are provided, the parser will receive data from header, body, and query, in this order. + * If no options are provided, the parser will receive data from path, header, body, and query, in this order. * This basically allows to fetch data from all possible HTTP sources in one go. * * You can disable various sources by providing the options, e.g. `{withBody: false}` to disable body parsing. @@ -158,7 +159,23 @@ export interface HttpRequestParserOptions { * } * ``` * - * This is necessary in event listeners, since they are instantiated synchronously, + * Note that the parsers is based on all defined parameters (e.g. `userId: HttpQuery` => {userId: string}), + * and then starts from there applying header, body, and then query values. + * This means you also get access to defined path parameters, like: + * + * ```typescript + * @http.GET('teams/:teamId') + * async route(teamId: string) { + * //teamId is string + * } + * + * httpWorkflow.onController.listen((event, parser: HttpRequestParser<{teamId: string}>) => { + * const data = await parser(); + * console.log(data.teamId); + * }); + * ``` + * + * HttpRequestParser is necessary in event listeners, since they are instantiated synchronously, * but body is parsed asynchronously. So use in event listeners HttpRequestParser instead of HttpBody. */ export type HttpRequestParser = ((options?: HttpRequestParserOptions) => Promise) & TypeAnnotation<'httpRequestParser', T>; diff --git a/packages/http/src/request-parser.ts b/packages/http/src/request-parser.ts index 168e1a990..71371d7c4 100644 --- a/packages/http/src/request-parser.ts +++ b/packages/http/src/request-parser.ts @@ -246,7 +246,7 @@ export function getRequestParserCodeForParameters( const parseOptionsVar = compiler.reserveVariable('parseOptions', parseOptions); const parseBodyVar = compiler.reserveVariable('parseBody', parseBody); setParameters.push(`parameters.${parameter.parameter.name} = async (options = {}) => { - let res = {}; + let res = options.withPath !== false ? Object.assign({}, parameters) : {}; if (options.withHeader !== false) { Object.assign(res, _headers); } diff --git a/packages/http/tests/router.spec.ts b/packages/http/tests/router.spec.ts index 209eb6f80..882589295 100644 --- a/packages/http/tests/router.spec.ts +++ b/packages/http/tests/router.spec.ts @@ -1316,6 +1316,11 @@ test('body and queries in listener', async () => { const data = await parser(); return [data.auth, data.userId]; } + @http.GET('/5/:userId') + async handle5(userId: number, parser: HttpRequestParser) { + const data = await parser(); + return [userId, data.auth, data.userId]; + } } type AuthData = { @@ -1345,6 +1350,7 @@ test('body and queries in listener', async () => { expect((await httpKernel.request(HttpRequest.POST('/3?auth=secretToken1').json({userId: '24'}))).json).toEqual([24, 24, 'secretToken1']); expect((await httpKernel.request(HttpRequest.GET('/4?auth=secretToken1&userId=1'))).json).toEqual(['secretToken1', '1']); expect((await httpKernel.request(HttpRequest.GET('/4?userId=1').header('auth', 'secretToken1'))).json).toEqual(['secretToken1', '1']); + expect((await httpKernel.request(HttpRequest.GET('/5/1?auth=secretToken1'))).json).toEqual([1, 'secretToken1', '1']); }); test('stream', async () => {