@chubbyts/chubbyts-framework-router-path-to-regexp
Version:
Path-to-regex routing implementation for chubbyts-framework.
97 lines (96 loc) • 4.17 kB
JavaScript
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);
};
};