@tunframework/tun-rest-router
Version:
rest router for tun
164 lines (163 loc) • 6.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchRoute = exports.pathToRegexp = exports.assertLegalPathPartName = exports.parseControllers2Routes = void 0;
const tun_1 = require("@tunframework/tun");
function parseControllers2Routes(controllers) {
const _routes = [];
for (const controller of controllers) {
const pathAndMethodList = Object.getOwnPropertyNames(controller).filter((item) => typeof controller[item] === 'function');
for (const pathAndMethod of pathAndMethodList) {
let [method, pathname] = pathAndMethod.split(' ');
if (!pathname) {
pathname = method;
method = 'GET';
console.warn(`"${pathname}" implict method not recommanded, curently resolved as "GET"`);
}
if (!pathname) {
throw new Error(`"${pathAndMethod}" pathname required!`);
}
method = method.toUpperCase();
let methods = method.split('|');
let methodUnsupported = methods.find((o) => !tun_1.HttpMethod[o]);
if (methodUnsupported) {
throw new Error(`unsupported method "${methodUnsupported}"`);
}
if (!pathname.startsWith('/')) {
console.warn(`"${pathname}" prefix "/" required`);
continue;
}
let handler = controller[pathAndMethod];
_routes.push({
...pathToRegexp(pathname),
methods,
slugValues: [],
handler
});
}
}
// @ts-ignore
return _routes;
}
exports.parseControllers2Routes = parseControllers2Routes;
function assertLegalPathPartName(name) {
if (!name || name.match(/^[^-0-9a-zA-Z]+$/)) {
throw new Error(`Expect "${name}" to be legal part of path.`);
}
}
exports.assertLegalPathPartName = assertLegalPathPartName;
function pathToRegexp(pathname) {
/**@type {Route} */
let route = {
methods: ['GET'],
pathname,
re: new RegExp('^/$'),
// /a/b/c
// /a/b/:c([A-Za-z0-9]+)
// /a/b/{c:[A-Za-z0-9]+}
// /p?ttern
slugNames: [],
slugValues: [],
slugPositions: [],
handler: (ctx, next) => next()
};
if (!pathname.startsWith('/')) {
throw new Error(`Expect path "${pathname}" startsWith "/"`);
}
let regexp;
if (pathname === '/') {
regexp = '^/$';
}
else {
regexp =
'^/' +
pathname
.substring(1)
.split('/')
.map((item, itemIndex) => {
if (item.startsWith(':')) {
// "/custom/:id([0-9a-zA-Z]{36})"
let name = item.substring(1);
let lParInd = name.indexOf('(');
if (lParInd > -1 && name.endsWith(')')) {
let customRegexp = name.substring(lParInd + 1, name.length - 1);
name = name.substring(0, lParInd);
assertLegalPathPartName(name);
route.slugNames.push(name);
route.slugPositions.push(itemIndex);
return `(${customRegexp})`;
}
else {
assertLegalPathPartName(name);
route.slugNames.push(name);
route.slugPositions.push(itemIndex);
return '(.+)';
}
}
else if (item.startsWith('{') && item.endsWith('}')) {
// "/custom/{id:[0-9a-zA-Z]{36}}"
let kv = item.substring(1, item.length - 1);
let [name, customRegexp] = kv.split(':', 2);
customRegexp = customRegexp || '.+';
assertLegalPathPartName(name);
route.slugNames.push(name);
route.slugPositions.push(itemIndex);
return `(${customRegexp})`;
}
else {
// try match glob like "/custom/**/*.js"
// let name = item.replace(/([.^$])/g, '\\$1').replace(/\*\*/g, '.*[^/]+').replace(/\*/g, '[^/]+')
let name = item
.replace(/([.^$])/g, '\\$1')
.replace(/(\*+)/, ($, $1) => {
if ($1.length === 2) {
return '.*[^/]+';
}
else if ($1.length === 1) {
return '[^/]+';
}
else {
throw new Error(`Expect "*" or "**" found ${$1}`);
}
});
// assertLegalPathPartName(name);
return name;
}
})
.join('/') +
'([?].*)?([#].*)?$';
}
route.re = new RegExp(regexp);
return route;
}
exports.pathToRegexp = pathToRegexp;
function matchRoute(method, pathname, routes) {
// Check full-match route at first
const fullMatched = routes.find((/**@type {Route} */ item) => item.methods.some((m) => m === method) &&
item.pathname === pathname &&
item.slugNames.length === 0);
if (fullMatched)
return fullMatched;
let slugValuesMap = {};
return routes
.filter((/**@type {Route} */ item) => {
if (item.methods.every((m) => m !== method)) {
return false;
}
let matched = item.re.exec(pathname);
if (matched) {
slugValuesMap[item.pathname] = matched.slice(1, matched.length - 2);
return true;
}
})
.reduce((pre, next) => {
// compare len
if (!pre || pre.pathname.length <= next.pathname.length) {
return {
...next,
slugValues: slugValuesMap[next.pathname]
};
}
return pre;
}, null);
}
exports.matchRoute = matchRoute;