UNPKG

@chubbyts/chubbyts-framework-router-path-to-regexp

Version:

Path-to-regex routing implementation for chubbyts-framework.

97 lines (96 loc) 4.17 kB
import { compile, match } from 'path-to-regexp'; import { createBadRequest, createMethodNotAllowed, createNotFound, } from '@chubbyts/chubbyts-http-error/dist/http-error'; const decodePathname = (pathname) => { try { return decodeURI(pathname); } catch { throw createBadRequest({ detail: `The path "${pathname}" contains invalid percent-encoding.`, }); } }; /** * ```ts * import type { Match } from '@chubbyts/chubbyts-framework/dist/router/route-matcher'; * import type { RoutesByName } from '@chubbyts/chubbyts-framework/dist/router/routes-by-name'; * import { createPathToRegexpRouteMatcher } from '@chubbyts/chubbyts-framework-router-path-to-regexp/dist/path-to-regexp-router'; * * const routesByName: RoutesByName = ...; * * const pathToRegexpRouteMatcher: Match = createPathToRegexpRouteMatcher(routesByName); * ``` */ export const createPathToRegexpRouteMatcher = (routesByName) => { const matchersByName = new Map(Array.from(routesByName.entries()).map(([name, route]) => [name, match(route.path)])); return (request) => { const url = new URL(request.url); const pathname = decodePathname(url.pathname); const matchWithMethods = []; for (const [name, route] of routesByName.entries()) { const matcherByName = matchersByName.get(name); const matchedPathname = matcherByName(pathname); if (!matchedPathname) { continue; } if (route.method === request.method) { return { ...route, attributes: matchedPathname.params }; } // oxlint-disable-next-line functional/immutable-data matchWithMethods.push(route.method); } if (matchWithMethods.length > 0) { throw createMethodNotAllowed({ detail: `Method "${request.method}" at path "${url.pathname}" is not allowed. Must be one of: "${matchWithMethods.join('", "')}".`, }); } throw createNotFound({ detail: `The path "${url.pathname}" you are looking for could not be found.`, }); }; }; /** * ```ts * import type { GeneratePath } from '@chubbyts/chubbyts-framework/dist/router/url-generator'; * import type { RoutesByName } from '@chubbyts/chubbyts-framework/dist/router/routes-by-name'; * import { createPathToRegexpPathGenerator } from '@chubbyts/chubbyts-framework-router-path-to-regexp/dist/path-to-regexp-router'; * * const routesByName: RoutesByName = ...; * * const pathToRegexpPathGenerator: GeneratePath = createPathToRegexpPathGenerator(routesByName); * ``` */ export const createPathToRegexpPathGenerator = (routesByName) => { const compilesByName = new Map(Array.from(routesByName.entries()).map(([name, route]) => [name, compile(route.path)])); return (name, attributes, query) => { const route = routesByName.get(name); if (undefined === route) { throw new Error(`Missing route: "${name}"`); } const compileByName = compilesByName.get(name); return compileByName(attributes) + (undefined !== query ? '?' + query : ''); }; }; /** * ```ts * import type { GeneratePath, GenerateUrl } from '@chubbyts/chubbyts-framework/dist/router/url-generator'; * import type { RoutesByName } from '@chubbyts/chubbyts-framework/dist/router/routes-by-name'; * import { createPathToRegexpUrlGenerator } from '@chubbyts/chubbyts-framework-router-path-to-regexp/dist/path-to-regexp-router'; * * const generatePath: GeneratePath = ...; * * const pathToRegexpUrlGenerator: GenerateUrl = createPathToRegexpUrlGenerator(generatePath); * ``` */ export const createPathToRegexpUrlGenerator = (generatePath) => { return (serverRequest, name, attributes, query) => { const { protocol, username, password, hostname, port } = new URL(serverRequest.url); const path = generatePath(name, attributes, query); return (protocol + '//' + (username && password ? `${username}:${password}@` : '') + hostname + (port ? ':' + port : '') + path); }; };