import { APIGatewayProxyEvent, Context } from 'aws-lambda' import { pathToRegexp } from 'path-to-regexp' import Request from '../../API/Request.js' import Response, { ResponseErrorType } from '../../API/Response.js' import Transaction, { StringMap } from '../../BaseEvent/Transaction.js' import Validator from '../../Validation/Validator.js' import { RouterConfig } from '../Router.js' import RouteResolver from '../RouteResolver.js' /** * Represents a server that handles serverless events and routes them to appropriate handlers. */ export default class Server { /** * The configuration object for the router. * @readonly * @type {RouterConfig} */ protected readonly config: RouterConfig /** * A protected property that holds a RouteResolver object. * The RouteResolver is responsible for resolving routes and returning the appropriate response. * @type {RouteResolver} */ protected readonly routeResolver: RouteResolver /** * Constructs a new instance of the Router class. * @param {RouterConfig} config - The configuration object for the router. * @returns None */ constructor(config: RouterConfig) { this.config = config this.routeResolver = new RouteResolver(config) } /** * Returns a callable function that is bound to the `handleServerlessEvent` method of the current object. * @returns {CallableFunction} - A callable function that is bound to the `handleServerlessEvent` method. */ public getExport(): CallableFunction { return this.handleServerlessEvent.bind(this) } /** * Handles a serverless event by executing a transaction and resolving the route based on the event. * @param {APIGatewayProxyEvent} event - The serverless event object. * @param {Context} context - The serverless context object. * @returns None */ public async handleServerlessEvent(event: APIGatewayProxyEvent, context: Context) { // init transaction await new Transaction( event, context, this.config ).execute(async transaction => { const request = transaction.request const route = this.routeResolver.resolveRoute(request.getMethod(), request.getPath()) if (route) { transaction.logger.log('Router accepted route:', route.path) // Validate input if (route.inputSchema) { const validationResp = Validator.validateSchema(request.getBody(), route.inputSchema) if (validationResp && validationResp instanceof Response) return validationResp } // Validate query if (route.querySchema) { const validationResp = Validator.validateSchema( request.getQueryParams(), route.querySchema ) if (validationResp && validationResp instanceof Response) return validationResp } // parse before validating for (const path of Array.isArray(route.path) ? route.path : [route.path]) { this.parsePathParams(request, path) } // Validate path if (route.pathSchema) { const validationResp = Validator.validateSchema(request.getPathParams(), route.pathSchema) if (validationResp && validationResp instanceof Response) return validationResp } // Continue to route handler return await route.handler(transaction) } //No route found :/ return new Response(404, { err: 'Route not found!' }) }) } /** * Parses the path parameters from the request URL based on the given route path. * @param {Request} req - The request object. * @param {string} routePath - The route path pattern to match against. * @returns None */ private parsePathParams(req: Request, routePath: string) { const path = req.getPath() const regex = pathToRegexp(routePath) const result = regex.regexp.exec(path) if (result) req.setFixedPathParams(regex.keys, result) } }