diff --git a/server/main.ts b/server/main.ts index d307a6f..47bdfea 100644 --- a/server/main.ts +++ b/server/main.ts @@ -49,6 +49,20 @@ router ); }); +router + .get("/user/:id/:name/*", (c) => { + return c.html( + `id = ${c.params.id}, name = ${c.params.name}, rest = ${c.params.restOfThePath}`, + ); + }); + +router + .get("/user/:idButDifferent", (c) => { + return c.html( + `idButDifferent = ${c.params.idButDifferent}`, + ); + }); + export default { async fetch(req, connInfo) { return await router.handleRequest(req, connInfo); diff --git a/server/src/lib/router.ts b/server/src/lib/router.ts index fdb4864..4af49f4 100644 --- a/server/src/lib/router.ts +++ b/server/src/lib/router.ts @@ -41,7 +41,6 @@ class HttpRouter { method: string, handler: RequestHandler, ): HttpRouter; - add( path: string | string[], method: string, @@ -103,7 +102,7 @@ class HttpRouter { ? this.pathPreprocessor(c.path) : c.path; - let params: Params = {}; + let params: string[] = []; const handler = this.routerTree .find(path) @@ -152,6 +151,10 @@ class HttpRouter { return c; } + + private setParams(path: string, params: string[]): Params { + path.split("/").filter((segmet) => segmet.startsWith(":")); + } } export type ExtractRouteParams = T extends string diff --git a/server/src/lib/routerTree.ts b/server/src/lib/routerTree.ts index f1bd958..b1021a1 100644 --- a/server/src/lib/routerTree.ts +++ b/server/src/lib/routerTree.ts @@ -6,6 +6,7 @@ const DEFAULT_PATH_SEPARATOR = "/"; interface Node { handler: Option; + paramNames: string[]; addChild( segment: string, wildcardSymbol: string, @@ -22,6 +23,7 @@ class StaticNode implements Node { protected dynamicChild: Option> = none; protected wildcardChild: Option> = none; public handler: Option = none; + public paramNames: string[] = []; constructor(handler?: T) { this.handler = fromNullableVal(handler); @@ -33,8 +35,8 @@ class StaticNode implements Node { return child; } - setDynamicChild(paramName: string, handler?: T): DynamicNode { - const child = new DynamicNode(paramName, handler); + setDynamicChild(handler?: T): DynamicNode { + const child = new DynamicNode(handler); this.dynamicChild = some(child); return child; } @@ -55,8 +57,7 @@ class StaticNode implements Node { return this.setWildcardNode(handler); } if (segment.startsWith(paramPrefixSymbol)) { - const paramName = segment.slice(paramPrefixSymbol.length); - return this.setDynamicChild(paramName, handler); + return this.setDynamicChild(handler); } return this.addStaticChild(segment, handler); } @@ -91,7 +92,6 @@ class StaticNode implements Node { // TODO: get rid of fixed param name class DynamicNode extends StaticNode implements Node { constructor( - public readonly paramName: string, handler?: T, ) { super(handler); @@ -104,6 +104,7 @@ class DynamicNode extends StaticNode implements Node { class WildcardNode implements Node { public handler: Option; + public paramNames: string[] = []; constructor(handler?: T) { this.handler = fromNullableVal(handler); @@ -144,6 +145,7 @@ export class RouterTree { public add(path: string, handler: T): void { const segments = this.splitPath(path); + const paramNames: string[] = this.extractParams(segments); let current: TreeNode = this.root; for (const segment of segments) { @@ -157,15 +159,21 @@ export class RouterTree { ) ); - if (current.isWildcardNode()) break; + if (current.isWildcardNode()) { + current.paramNames = paramNames; + current.paramNames.push("restOfThePath"); + current.handler = some(handler); + return; + } } + current.paramNames = paramNames; current.handler = some(handler); } public find(path: string): Option> { const segments = this.splitPath(path); - const params: Record = {}; + const paramValues: string[] = []; let current: TreeNode = this.root; let i = 0; @@ -175,7 +183,7 @@ export class RouterTree { const nextNode = current.getChild(segment).ifSome((child) => { if (child.isDynamicNode()) { - params[child.paramName] = segment; + paramValues.push(segment); } current = child; }); @@ -186,10 +194,16 @@ export class RouterTree { if (current.isWildcardNode()) { const rest = segments.slice(i - 1); if (rest.length > 0) { - params["restOfThePath"] = rest.join(this.pathSeparator); + paramValues.push(rest.join(this.pathSeparator)); } } + const params: Params = {}; + + for (let i = 0; i < paramValues.length; i++) { + params[current.paramNames[i]] = paramValues[i]; + } + return current.handler.map((value) => ({ value, params })); } @@ -214,6 +228,16 @@ export class RouterTree { const trimmed = path.trim().replace(/^\/+/, "").replace(/\/+$/, ""); return trimmed ? trimmed.split(this.pathSeparator) : []; } + + public extractParams(segments: string[]): string[] { + return segments.filter((segment) => + segment.startsWith(this.paramPrefixSymbol) + ).map((segment) => this.stripParamPrefix(segment)); + } + + public stripParamPrefix(segment: string): string { + return segment.slice(this.paramPrefixSymbol.length); + } } export type Params = Record;