UNPKG

route-path-match

Version:

compile route path template, match route path params

264 lines (254 loc) 6.41 kB
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 };