import { AnyRoute, RouterConfig } from './Router.js' import { HttpMethod } from '../API/Request.js' /** * Represents a collection of routes that can be added and resolved. */ class Routes { /** * Represents the current route. * @type {Route | undefined} */ private current?: AnyRoute /** * Optional private variable that represents a collection of routes. * @type {Routes | undefined} */ private variable?: Routes /** * An object that maps string keys to Routes. * @private * @readonly * @type {Object.} */ private readonly next: { [k: string]: Routes } /** * Initializes a new instance of the class. * @constructor */ constructor() { this.next = {} } /** * Adds a route to the routing system. * @param {Route} route - The route to add. * @param {string[]} parts - The parts of the route path. * @returns None * @throws {Error} If a duplicate route is detected. */ public addRoute(route: AnyRoute, parts: string[]): void { const first = parts.shift() if (first) { if (first.startsWith(':')) { this.variable = this.variable || new Routes() this.variable.addRoute(route, parts) } else { this.next[first] = this.next[first] || new Routes() this.next[first].addRoute(route, parts) } } else { if (this.current) throw new Error(`Duplicate route: ${route.method}: ${route.path}`) this.current = route } } /** * Resolves a route based on the given parts. * @param {string[]} parts - An array of route parts. * @returns {Route} The resolved route. */ public resolveRoute(parts: string[]): AnyRoute | undefined { const first = parts.shift() if (first) { if (first in this.next) { return this.next[first].resolveRoute(parts) } return this.variable?.resolveRoute(parts) } return this.current } } /** * A class that resolves routes based on the provided configuration. * @class RouteResolver */ export default class RouteResolver { /** * An object that maps HTTP methods to their corresponding routes. * @property {object} routes - The routes object. * @property {Routes} routes.method - The routes for the specified HTTP method. */ private routes: { [method in HttpMethod]?: Routes } /** * Constructs a new instance of the Router class with the given configuration. * @param {RouterConfig} config - The configuration object for the router. * @returns None */ constructor(config: RouterConfig) { this.routes = {} this.buildRoutes(config) } /** * Resolves a route based on the given HTTP method and path. * @param {string} method - The HTTP method of the request. * @param {string} path - The path of the request. * @returns {Route | undefined} - The resolved route or undefined if no route is found. */ public resolveRoute(method: HttpMethod, path: string): AnyRoute | undefined { const parts = path.split('/').filter(p => p.length) return this.routes[method]?.resolveRoute(parts) } /** * Builds the routes for the router based on the given configuration. * @param {RouterConfig} config - The router configuration object. * @returns None */ private buildRoutes(config: RouterConfig): void { const addRoute = (path: string, route: AnyRoute) => { const parts = path.split('/').filter(p => p.length) this.routes[route.method] = this.routes[route.method] || new Routes() this.routes[route.method]!.addRoute(route, parts) } for (const route of config.routes) { if (Array.isArray(route.path)) { for (const path of route.path) addRoute(path, route) } else addRoute(route.path, route) } } }