diff --git a/packages/plugin-router-hapi/src/attach.ts b/packages/plugin-router-hapi/src/attach.ts index 5924868ab..657596a46 100644 --- a/packages/plugin-router-hapi/src/attach.ts +++ b/packages/plugin-router-hapi/src/attach.ts @@ -39,7 +39,8 @@ export default function attachRouter(service: Microfleet, config: RouterHapiPlug name: 'microfleetRouter', version: '1.0.0', async register(server: Server) { - for (const [actionName, handler] of service.router.routes.get(ActionTransport.http).entries()) { + const routes = service.router.routes.get<{ TransportOptions: { hapi: ServerRoute } }>(ActionTransport.http) + for (const [actionName, handler] of routes.entries()) { const path = fromNameToPath(actionName, config.prefix) const serverRoute: ServerRoute = { path, diff --git a/packages/plugin-router/__tests__/artifacts/utils.ts b/packages/plugin-router/__tests__/artifacts/utils.ts index 355febb57..638c7a499 100644 --- a/packages/plugin-router/__tests__/artifacts/utils.ts +++ b/packages/plugin-router/__tests__/artifacts/utils.ts @@ -59,7 +59,7 @@ const reqPromise = async (reqUrl: URL, requestOptions: any) => { return res } -export function getHTTPRequest(_options: RequestInit & {url: string }): (action: string, params?: any, opts?: any) => Bluebird { +export function getHTTPRequest(_options: RequestInit & { url: string }): (action: string, params?: any, opts?: any) => Bluebird { return (action: string, params?: any, opts: any = {}): Bluebird => { const { url, ...options } = _options const requestOptions = { @@ -70,6 +70,10 @@ export function getHTTPRequest(_options: RequestInit & {url: string }): ...options, } + if (opts.method) { + requestOptions.method = opts.method + } + const reqUrl = new URL(`${url}${action}`) requestOptions.body = (params || opts.json) ? JSON.stringify(opts.json || params) : null diff --git a/packages/plugin-router/__tests__/suites/01.router.spec.ts b/packages/plugin-router/__tests__/suites/01.router.spec.ts index 72628b3d8..4322e3089 100644 --- a/packages/plugin-router/__tests__/suites/01.router.spec.ts +++ b/packages/plugin-router/__tests__/suites/01.router.spec.ts @@ -249,15 +249,13 @@ describe('@microfleet/plugin-router', async () => { } await service.connect() - await Promise.all([ - rget({ sample: 1, bool: true }), - rget({ sample: 'crap', bool: true }, false), - rget({ sample: 13, bool: 'invalid' }, false), - rget({ sample: 13, bool: '0' }), - rget({ sample: 13, bool: '0', oops: 'q' }, false), - rget({ sample: 13.4, bool: '0' }, false), - rget(null, false, { json: { sample: 13.4, bool: '0' }, method: 'post' }), - ]) + await rget({ sample: 1, bool: true }) + await rget({ sample: 'crap', bool: true }, false) + await rget({ sample: 13, bool: 'invalid' }, false) + await rget({ sample: 13, bool: '0' }) + await rget({ sample: 13, bool: '0', oops: 'q' }, false) + await rget({ sample: 13.4, bool: '0' }, false) + await rget(null, false, { json: { sample: 13.4, bool: '0' }, method: 'post' }) }) it('should be able to set schema and responseSchema from action name', async () => { diff --git a/packages/plugin-router/src/extensions/audit/log.ts b/packages/plugin-router/src/extensions/audit/log.ts index 875c70855..66a3f451a 100644 --- a/packages/plugin-router/src/extensions/audit/log.ts +++ b/packages/plugin-router/src/extensions/audit/log.ts @@ -1,5 +1,5 @@ import { Microfleet } from '@microfleet/core-types' -import type { ServiceRequest } from '../../types/router' +import type { ReplyGenericInterface, RequestGenericInterface, ServiceRequest } from '../../types/router' import { Lifecycle, LifecycleExtensions } from '../../lifecycle/index' import { initTimingExtension } from './timing' @@ -13,15 +13,18 @@ export type AuditLogExtensionParams = { getErrorLevel?: (this: Microfleet, error: any) => ErrorLevel | undefined } -export type MetaLog = { - headers: Record +export type MetaLog< + DefaultRequest extends RequestGenericInterface = RequestGenericInterface, + DefaultReply extends ReplyGenericInterface = ReplyGenericInterface +> = { + headers: DefaultRequest["Headers"] latency: number | null method: string - params: any - query: any + params: DefaultRequest["Params"] + query: DefaultRequest["Querystring"] route: string transport: string - response?: any + response?: DefaultReply["Reply"] err?: Error } diff --git a/packages/plugin-router/src/extensions/validate/query-string-parser.ts b/packages/plugin-router/src/extensions/validate/query-string-parser.ts index 29f640b64..eb8efd726 100644 --- a/packages/plugin-router/src/extensions/validate/query-string-parser.ts +++ b/packages/plugin-router/src/extensions/validate/query-string-parser.ts @@ -1,11 +1,11 @@ -import { parse } from 'qs' +import { parse, type ParsedQs } from 'qs' import { Lifecycle } from '../../lifecycle/index' import { ServiceRequest } from '../../types/router' type QSParserAugmentedAction = ServiceRequest & { action: ServiceRequest['action'] & { - transformQuery?: (...args: any[]) => any; - transformOpts?: any; + transformQuery?: (input: Record) => ParsedQs; + transformOpts?: Parameters[1]; }; } @@ -19,7 +19,8 @@ async function preValidate(request: QSParserAugmentedAction): Promise { const { action } = request const { transformQuery = identity, transformOpts } = action - request.query = transformQuery(parse(query, { + // module actually handles all variations of input + request.query = transformQuery(parse(query as any, { depth: 1, parameterLimit: 10, parseArrays: false, diff --git a/packages/plugin-router/src/index.ts b/packages/plugin-router/src/index.ts index 3e6b63caa..a2f4988bb 100644 --- a/packages/plugin-router/src/index.ts +++ b/packages/plugin-router/src/index.ts @@ -18,7 +18,8 @@ export type { ServiceMiddleware, ServiceActionHandler, ServiceActionAuthGetName, - TransportOptions, + RequestGenericInterface, + ReplyGenericInterface } from './types/router' export type { AuthInfo } from './lifecycle/handlers/auth' diff --git a/packages/plugin-router/src/routes.ts b/packages/plugin-router/src/routes.ts index 96689a7d6..542ee6767 100644 --- a/packages/plugin-router/src/routes.ts +++ b/packages/plugin-router/src/routes.ts @@ -1,15 +1,21 @@ -import { ServiceAction } from './types/router' +import type { ReplyGenericInterface, RequestGenericInterface, ServiceAction } from './types/router' export type RouteName = string export type TransportName = string -export type RoutesCollection = Map +export type RoutesCollection< + DefaultRequest extends RequestGenericInterface = RequestGenericInterface, + DefaultResponse extends ReplyGenericInterface = ReplyGenericInterface +> = Map> export type RoutesGroupedByTransport = Map export default class Routes { protected groupedByTransport: RoutesGroupedByTransport = new Map() - get(transport: TransportName): RoutesCollection { + get< + DefaultRequest extends RequestGenericInterface = RequestGenericInterface, + DefaultResponse extends ReplyGenericInterface = ReplyGenericInterface + >(transport: TransportName): RoutesCollection { const { groupedByTransport } = this - const routes: RoutesCollection = groupedByTransport.get(transport) || new Map() + const routes: RoutesCollection = groupedByTransport.get(transport) || new Map() groupedByTransport.set(transport, routes) diff --git a/packages/plugin-router/src/types/router.ts b/packages/plugin-router/src/types/router.ts index 60162b49e..fffa83b08 100644 --- a/packages/plugin-router/src/types/router.ts +++ b/packages/plugin-router/src/types/router.ts @@ -7,34 +7,50 @@ export type ServiceMiddleware = (this: Microfleet, request: ServiceRequest) => P export type ServiceActionAuthGetName = (request: ServiceRequest) => string export type ServiceActionHandler = ServiceAction['handler'] -export interface ServiceAction { - handler(this: Microfleet, request: ServiceRequest, ...params: any[]): Promise + +export interface RequestGenericInterface { + Params?: unknown; + Headers?: unknown; + Querystring?: unknown; + Locals?: unknown; + TransportOptions?: unknown; +} + +export interface ReplyGenericInterface { + Reply?: unknown; +} +export interface ServiceAction< + DefaultRequest extends RequestGenericInterface = RequestGenericInterface, + DefaultReply extends ReplyGenericInterface = ReplyGenericInterface, +> { + handler(this: Microfleet, request: ServiceRequest, ...params: any[]): Promise actionName: string transports: ServiceRequest['transport'][] - transportOptions?: TransportOptions + transportOptions?: DefaultRequest["TransportOptions"] validateResponse: boolean schema?: string | null | boolean responseSchema?: string; readonly?: boolean } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TransportOptions {} - -export interface ServiceRequest { +export interface ServiceRequest< + DefaultRequest extends RequestGenericInterface = RequestGenericInterface, + DefaultReply extends ReplyGenericInterface = ReplyGenericInterface, + ErrorArg extends Error & { name: string, code?: string } = any +> { route: string - action: ServiceAction - params: any - headers: any - query: any + action: ServiceAction + params: DefaultRequest["Params"] + headers: DefaultRequest["Headers"] + query: DefaultRequest["Querystring"] method: keyof typeof RequestDataKey transport: typeof ActionTransport[keyof typeof ActionTransport] transportRequest: any | ClientRequest - locals: any + locals: DefaultRequest["Locals"] parentSpan: null span: null log: Logger - response?: unknown - error?: any + response?: DefaultReply["Reply"] + error?: ErrorArg reformatError: boolean }