UNPKG

@iopa/router

Version:

Lightweight and fast router for IOPA applications

175 lines (154 loc) 4.65 kB
/* * Internet Open Protocol Abstraction (IOPA) * Copyright (c) 2016 - 2022 Internet Open Protocols Alliance * Portions Copyright (c) 2021 Yusuke Wada * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { IContextIopa, IRouterResult, IRouter, IRouterApp, Next, AppHandler } from '@iopa/types' import { TraceEvent, util } from 'iopa' import { compose } from './compose' import { METHODS, METHOD_NAME_ALL_LOWERCASE } from './constants' import { TrieRouter } from './trie-router' interface IRoute { path: string method: string handler: NamedAppHandler } type NamedAppHandler = AppHandler & { id: string; options?: unknown } const { url: { mergePath, getPathFromURL } } = util export default class RouterMiddleware { public readonly router: IRouter<NamedAppHandler> = new TrieRouter() public readonly strict: boolean = true // strict routing - default is true protected _basePath: string = '' public routes: IRoute[] = [] public constructor( app: IRouterApp, options: { router?: IRouter<NamedAppHandler> } = {} ) { const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE] allMethods.forEach((method) => { app[method] = <Path extends string = ''>( args1: Path, args2: NamedAppHandler, args3: unknown ) => { let _path = '/' if (typeof args1 === 'string') { _path = args1 } else { throw new Error('first argument must be a string path') } if (typeof args2 !== 'string') { this._addRoute(method, _path, args2, args3) } else { throw new Error('second argument must be an app func') } return app } }) Object.assign(this, options) } public async invoke(context: IContextIopa<any>, next: Next): Promise<void> { const result = await this._matchRoute(context) if (!result) { return next() } this._preprocessRoute(result, context) try { await this._validateRoute(result, context) await this._handleRoute(result, context, next) } catch (ex) { console.log(ex) context.respondWith(ex) return } } protected _addRoute( method: string, path: string, handler: NamedAppHandler, options?: unknown ): IRoute { method = method.toUpperCase() if (this._basePath) { path = mergePath(this._basePath, path) } this.router.add(method, path, handler) handler.id = handler.id || `${method} ${path} Handler #${ this.routes.filter((r) => r.handler.id.startsWith(`${method} ${path} Handler`) ).length + 1 }` handler.options = options const r: IRoute = { path: path, method: method, handler: handler } this.routes.push(r) console.log('ADDED', r) return r } protected async _matchRoute( context: IContextIopa ): Promise<IRouterResult<NamedAppHandler | null>> { const path = getPathFromURL(context.get('iopa.OriginalUrl'), { strict: this.strict }) const method = context.get('iopa.Method') return this.router.match(method, path) } protected _preprocessRoute( result: IRouterResult<NamedAppHandler | null>, context: IContextIopa ): void { const params = result ? result.params : {} context.set('iopa.Params', params) } protected async _validateRoute( result: IRouterResult<NamedAppHandler | null>, context: IContextIopa ): Promise<void> { /** Must inherit */ } protected async _handleRoute( result: IRouterResult<NamedAppHandler | null>, context: IContextIopa, next: Next ): Promise<void> { const handlers = result ? result.handlers : [] const composed = compose(handlers) context.dispatchEvent( new TraceEvent('trace-next', { label: context.get('server.CurrentMiddleware') }) ) await composed(context) context.dispatchEvent( new TraceEvent('trace-next-resume', { label: context.get('server.CurrentMiddleware') }) ) if (!context.response.get('iopa.IsFinalized')) { return next() } } }