import { Express, static as _serveStatic } from "express"; import { Logger } from "winston"; import { CommonConfig } from "./config-type"; import { DependsOnMethod } from "./depends-on-method"; import { AbstractEndpoint } from "./endpoint"; import { RoutingError } from "./errors"; import { AuxMethod, Method } from "./method"; import { ServeStatic, StaticHandler } from "./serve-static"; import { getStartupLogo } from "./startup-logo"; export interface Routing { [SEGMENT: string]: Routing | DependsOnMethod | AbstractEndpoint | ServeStatic; } export interface RoutingCycleParams { routing: Routing; endpointCb: ( endpoint: AbstractEndpoint, path: string, method: Method | AuxMethod ) => void; staticCb?: (path: string, handler: StaticHandler) => void; parentPath?: string; cors?: boolean; } export const routingCycle = ({ routing, endpointCb, staticCb, parentPath, cors, }: RoutingCycleParams) => { Object.entries(routing).forEach(([segment, element]) => { segment = segment.trim(); if (segment.match(/\//)) { throw new RoutingError( "Routing elements should not contain '/' character.\n" + `The error caused by ${ parentPath ? `'${parentPath}' route that has a '${segment}'` : `'${segment}'` } entry.` ); } const path = `${parentPath || ""}${segment ? `/${segment}` : ""}`; if (element instanceof AbstractEndpoint) { const methods: (Method | AuxMethod)[] = element.getMethods().slice(); if (cors) { methods.push("options"); } methods.forEach((method) => { endpointCb(element, path, method); }); } else if (element instanceof ServeStatic) { if (staticCb) { staticCb(path, _serveStatic(...element.params)); } } else if (element instanceof DependsOnMethod) { Object.entries<AbstractEndpoint>(element.methods).forEach( ([method, endpoint]) => { endpointCb(endpoint, path, method as Method); } ); if (cors && Object.keys(element.methods).length > 0) { const firstEndpoint = Object.values( element.methods )[0] as AbstractEndpoint; endpointCb(firstEndpoint, path, "options"); } } else { routingCycle({ routing: element, endpointCb, staticCb, cors, parentPath: path, }); } }); }; export const initRouting = ({ app, logger, config, routing, }: { app: Express; logger: Logger; config: CommonConfig; routing: Routing; }) => { if (config.startupLogo !== false) { console.log(getStartupLogo()); } routingCycle({ routing, cors: config.cors, endpointCb: (endpoint, path, method) => { app[method](path, async (request, response) => { logger.info(`${request.method}: ${path}`); await endpoint.execute({ request, response, logger, config }); }); }, staticCb: (path, handler) => { app.use(path, handler); }, }); };