diff --git a/src/router.ts b/src/router.ts index 6284777..0134514 100644 --- a/src/router.ts +++ b/src/router.ts @@ -65,7 +65,12 @@ function lookup(ctx: RadixRouterContext, path: string): MatchedRoute { if (node === null) { break; } else { - params[node.paramName] = section; + if (node.type === NODE_TYPES.MIXED && node.paramMatcher) { + const matches = section.match(node.paramMatcher); + Object.assign(params, matches.groups); + } else { + params[node.paramName] = section; + } paramsFound = true; } } else { @@ -116,8 +121,22 @@ function insert(ctx: RadixRouterContext, path: string, data: any) { node.children.set(section, childNode); if (type === NODE_TYPES.PLACEHOLDER) { - childNode.paramName = - section === "*" ? `_${_unnamedPlaceholderCtr++}` : section.slice(1); + if (section === "*") { + childNode.paramName = `_${_unnamedPlaceholderCtr++}`; + } else { + const PARAMS_RE = /:\w+|[^:]+/g; + const params = [...section.matchAll(PARAMS_RE)].map((i) => i[0]); + if (params.length === 1) { + childNode.paramName = params[0].slice(1); + } else { + childNode.type = NODE_TYPES.MIXED; + const sectionRegexString = section.replace( + /:(\w+)/g, + (_, id) => `(?<${id}>\\w+)`, + ); + childNode.paramMatcher = new RegExp(`^${sectionRegexString}$`); + } + } node.placeholderChildNode = childNode; isStaticRoute = false; } else if (type === NODE_TYPES.WILDCARD) { @@ -185,7 +204,7 @@ function getNodeType(str: string) { if (str.startsWith("**")) { return NODE_TYPES.WILDCARD; } - if (str[0] === ":" || str === "*") { + if (str.includes(":") || str === "*") { return NODE_TYPES.PLACEHOLDER; } return NODE_TYPES.NORMAL; diff --git a/src/types.ts b/src/types.ts index 8394537..17d06ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,7 @@ export const NODE_TYPES = { NORMAL: 0 as const, WILDCARD: 1 as const, PLACEHOLDER: 2 as const, + MIXED: 3 as const, }; type _NODE_TYPES = typeof NODE_TYPES; @@ -22,6 +23,7 @@ export interface RadixNode { children: Map>; data: RadixNodeData | null; paramName: string | null; + paramMatcher?: string | RegExp; wildcardChildNode: RadixNode | null; placeholderChildNode: RadixNode | null; } diff --git a/tests/router.test.ts b/tests/router.test.ts index 156bac0..ee826ae 100644 --- a/tests/router.test.ts +++ b/tests/router.test.ts @@ -93,6 +93,16 @@ describe("Router lookup", function () { }); }); + describe("mixed params in same segemnt", function () { + const mixedPath = "/files/:category/:id,name=:name.txt"; + testRouter([mixedPath], { + "/files/test/123,name=foobar.txt": { + path: mixedPath, + params: { category: "test", id: "123", name: "foobar" }, + }, + }); + }); + describe("should be able to match routes with trailing slash", function () { testRouter(["route/without/trailing/slash", "route/with/trailing/slash/"], { "route/without/trailing/slash": { path: "route/without/trailing/slash" },