route-path-match
Version:
compile route path template, match route path params
270 lines (260 loc) • 6.35 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const routePathMatch = require('route-path-match');
const taskset = require('taskset');
/**
* @export RouteMatcher
* @export ExpressRouter
*/
/**
* @require route-path-match:{ matchByRule, parseToRule }
*/
// 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 !== '*') {
newRoute.path = routePathMatch.parseToRule(newRoute.path);
}
else {
newRoute.path = {
raw: '*',
flag: '*',
value: '*',
names: []
};
}
{
newRoute.handler.forEach((handler, i, handlers) => {
if (typeof handler === 'object') {
handlers[i] = this.toResponseHandler(handler);
}
});
}
}
}
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[0] === '*' || route.method.includes(method)) {
if (route.path.flag === '*' || (params = routePathMatch.matchByRule(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 {
flow = null;
handle = null;
onRoute = null;
constructor(routes, options) {
super(routes, options);
const routesHandler = this.matchHandlers.bind(this);
const { onRoute, ...flowOptions } = options;
if (onRoute) {
this.onRoute = onRoute;
}
/**
* @require taskset:{ AsyncTasksFlow }
*/
const flow = new taskset.AsyncTasksFlow([ routesHandler ], {
isNextable: (req, res) => !res.headersSent,
...flowOptions
});
this.flow = flow;
this.handle = flow.run.bind(flow);
}
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);
}
}
matchHandlers(req, res) {
const route = this.matchRoute(req.path, req.method);
if (route) {
if (route.params && req.params) {
Object.assign(req.params, route.params);
}
if (this.onRoute) {
this.onRoute(route, req, res);
}
return route.handler;
}
}
}
// 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 [ '*' ];
}
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';
}
}
};
exports.ExpressRouter = ExpressRouter;
exports.RouteMatcher = RouteMatcher;