route-path-match
Version:
compile route path template, match route path params
264 lines (254 loc) • 6.41 kB
JavaScript
import { parsePathTpl, matchPathParams } from 'route-path-match';
import { runAsyncTasksFlow } from 'taskset';
/**
* @export RouteMatcher
* @export ExpressRouter
* @export ROUTE_PROPERTY_CHECKERS
*/
/**
* @require route-path-match:{ matchPathParams, parsePathTpl }
*/
// RouteMatcher
class RouteMatcher {
routes = [];
constructor(routes, defaultRoute) {
this.routes = this.parseRoutes(routes, defaultRoute || {});
}
// toResponseHandler(response)
// return response
parseRoutes(routes, parentRoute, level = 0) {
const newRoutes = [];
for (let __i = 0; __i < routes.length; __i++) {
let route = routes[__i];
const { children } = route;
if (children) {
const newRoute = this.parseRoute(route, parentRoute, false);
newRoutes.push(...this.parseRoutes(children, newRoute, level + 1));
}
else {
const newRoute = this.parseRoute(route, parentRoute);
newRoutes.push(newRoute);
}
}
// if level === 1 && !parentRoute.notSort
// newRoutes.sort((a, b) => (b.path.length || b.path.raw.length) - (a.path.length || a.path.raw.length))
return newRoutes;
}
parseRoute(route, parentRoute, pureRouteMode = true) {
const newRoute = pureRouteMode ? {}: Object.create(parentRoute);
try {
for (let __a = Object.keys(ROUTE_PROPERTY_PARSERS), __i = 0; __i < __a.length; __i++) {
let key = __a[__i], parser = ROUTE_PROPERTY_PARSERS[key];
newRoute[key] = parser(route[key], parentRoute[key], route);
}
if (pureRouteMode) {
for (let __a = Object.keys(ROUTE_PROPERTY_CHECKERS), __i = 0; __i < __a.length; __i++) {
let key = __a[__i], checker = ROUTE_PROPERTY_CHECKERS[key];
checker(newRoute[key], newRoute);
}
if (newRoute.path !== '*') {
const newPath = parsePathTpl(newRoute.path);
if (newPath !== newRoute.path) {
newPath.raw = newRoute.path;
}
newRoute.path = newPath;
}
{
newRoute.handler.forEach((handler, i, handlers) => {
if (typeof handler === 'object') {
if (!this.toResponseHandler) {
throw 'NOT_SUPPORT_STATIC_RESPONSE';
}
handlers[i] = this.toResponseHandler(handler);
}
else if (typeof handler !== 'function') {
throw 'NOT_SUPPORT_HANDLER_TYPE';
}
});
}
}
}
catch (err) {
console.error('parse route error:', route);
throw err;
}
return newRoute;
}
matchRoute(path, method) {
let params;
for (let __a = this.routes, __i = 0; __i < __a.length; __i++) {
let route = __a[__i];
if (route.method === '*' || route.method.includes(method)) {
if (route.path === '*' || (params = matchPathParams(route.path, path))) {
const matchedRoute = Object.create(route);
if (route.params) {
params = {
...route.params,
...params
};
}
matchedRoute.params = params;
return matchedRoute;
}
}
}
}
}
// ExpressRouter
class ExpressRouter extends RouteMatcher {
toResponseHandler(staticResponse) {
return (req, res) => {
const { code, headers, body } = staticResponse;
if (code) {
res.status(code);
}
if (headers) {
for (let __a = Object.keys(headers), __i = 0; __i < __a.length; __i++) {
let key = __a[__i], value = headers[key];
res.setHeader(key, value);
}
}
res.end(body);
}
}
isHeadersSent(req, res) {
return res.headersSent;
}
async handle(hookTasks, ...args) {
try {
await hookTasks.onStart ?. (...args);
const req = args[0];
const route = this.matchRoute(req.path, req.method);
if (!route) {
throw 'NOT_MATCH_ROUTE';
}
if (route.params && req.params) {
Object.assign(req.params, route.params);
}
await hookTasks.onRoute ?. (route, ...args);
const handlers = route.handler;
/**
* @require taskset:{ runAsyncTasksFlow }
*/
await runAsyncTasksFlow(handlers, this.isHeadersSent, ...args);
await hookTasks.onFinish ?. (...args);
}
catch (err) {
await hookTasks.onError ?. (err, ...args);
}
await hookTasks.onEnd ?. (...args);
}
}
// ROUTE_PROPERTY_PARSERS
const ALLOW_METHODS = [ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD' ];
const ROUTE_PROPERTY_PARSERS = {
path(path, parentPath) {
if (!path) {
path = '';
}
if (typeof path === 'string') {
if (typeof parentPath === 'string' && parentPath) {
if (parentPath.startsWith('^')) {
throw 'ERROR_PARENT_PATH';
}
if (path === '*') {
path = `^${parentPath}`;
}
else if (path.startsWith('^')) {
path = `^${parentPath}${path.slice(1)}`;
}
else {
path = `${parentPath}${path}`;
}
}
return path;
}
},
method(method, parentMethod) {
if (typeof method === 'string') {
if (method === '*') {
return method;
}
const methods = [];
for (let __a = method.toUpperCase().split(','), __i = 0; __i < __a.length; __i++) {
let tmpMethod = __a[__i];
if (ALLOW_METHODS.includes(tmpMethod)) {
methods.push(tmpMethod);
}
}
if (!methods.length) {
throw 'NOT_SUPPORT_METHOD';
}
return methods;
}
return parentMethod;
},
params(params, parentParams) {
if (params || parentParams) {
params = {
...parentParams,
...params
};
return params;
}
},
handler(handler, parentHandler, route) {
if (handler == null) {
if (route.response != null) {
handler = parseStaticResponse(route.response);
}
}
if (handler || parentHandler) {
const handlers = [].concat(parentHandler, handler).filter(Boolean);
if (handlers.some(handler => !handler)) {
throw 'NOT_SUPPORT_HANDLER';
}
return handlers;
}
}
};
function parseStaticResponse(response) {
if (typeof response === 'string') {
response = { body: response };
}
let { body = '', code, headers, redirect } = response;
if (redirect) {
if (!code) {
code = 302;
}
headers = {
...headers,
location: redirect
};
}
if (body) {
if ([ Object, Array, undefined ].includes(body.constructor)) {
body = JSON.stringify(body);
}
}
const newResponse = {
code,
body,
headers
};
return newResponse;
}
// ROUTE_PROPERTY_CHECKERS
const ROUTE_PROPERTY_CHECKERS = {
path(value) {
if (!value) {
throw 'ERRPR_ROUTE_PATH';
}
},
method(value) {
if (!value) {
throw 'ERRPR_ROUTE_METHOD';
}
},
handler(value) {
if (!value) {
throw 'ERROR_ROUTE_HANDLER';
}
}
};
export { ExpressRouter, ROUTE_PROPERTY_CHECKERS, RouteMatcher };