UNPKG

route-path-match

Version:

compile route path template, match route path params

265 lines (257 loc) 6.2 kB
import { parseToRule, matchByRule } from 'route-path-match'; import { AsyncTasksFlow } from '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 = 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 = 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 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'; } } }; export { ExpressRouter, RouteMatcher };