reworked dynamic parameters to be positional rather than fixed

This commit is contained in:
2025-02-03 17:13:04 +03:00
parent 19013984f3
commit 62d9f5a631
3 changed files with 52 additions and 11 deletions

View File

@ -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 { export default {
async fetch(req, connInfo) { async fetch(req, connInfo) {
return await router.handleRequest(req, connInfo); return await router.handleRequest(req, connInfo);

View File

@ -41,7 +41,6 @@ class HttpRouter {
method: string, method: string,
handler: RequestHandler<string>, handler: RequestHandler<string>,
): HttpRouter; ): HttpRouter;
add( add(
path: string | string[], path: string | string[],
method: string, method: string,
@ -103,7 +102,7 @@ class HttpRouter {
? this.pathPreprocessor(c.path) ? this.pathPreprocessor(c.path)
: c.path; : c.path;
let params: Params<string> = {}; let params: string[] = [];
const handler = this.routerTree const handler = this.routerTree
.find(path) .find(path)
@ -152,6 +151,10 @@ class HttpRouter {
return c; return c;
} }
private setParams(path: string, params: string[]): Params<string> {
path.split("/").filter((segmet) => segmet.startsWith(":"));
}
} }
export type ExtractRouteParams<T extends string> = T extends string export type ExtractRouteParams<T extends string> = T extends string

View File

@ -6,6 +6,7 @@ const DEFAULT_PATH_SEPARATOR = "/";
interface Node<T> { interface Node<T> {
handler: Option<T>; handler: Option<T>;
paramNames: string[];
addChild( addChild(
segment: string, segment: string,
wildcardSymbol: string, wildcardSymbol: string,
@ -22,6 +23,7 @@ class StaticNode<T> implements Node<T> {
protected dynamicChild: Option<DynamicNode<T>> = none; protected dynamicChild: Option<DynamicNode<T>> = none;
protected wildcardChild: Option<WildcardNode<T>> = none; protected wildcardChild: Option<WildcardNode<T>> = none;
public handler: Option<T> = none; public handler: Option<T> = none;
public paramNames: string[] = [];
constructor(handler?: T) { constructor(handler?: T) {
this.handler = fromNullableVal(handler); this.handler = fromNullableVal(handler);
@ -33,8 +35,8 @@ class StaticNode<T> implements Node<T> {
return child; return child;
} }
setDynamicChild(paramName: string, handler?: T): DynamicNode<T> { setDynamicChild(handler?: T): DynamicNode<T> {
const child = new DynamicNode(paramName, handler); const child = new DynamicNode(handler);
this.dynamicChild = some(child); this.dynamicChild = some(child);
return child; return child;
} }
@ -55,8 +57,7 @@ class StaticNode<T> implements Node<T> {
return this.setWildcardNode(handler); return this.setWildcardNode(handler);
} }
if (segment.startsWith(paramPrefixSymbol)) { if (segment.startsWith(paramPrefixSymbol)) {
const paramName = segment.slice(paramPrefixSymbol.length); return this.setDynamicChild(handler);
return this.setDynamicChild(paramName, handler);
} }
return this.addStaticChild(segment, handler); return this.addStaticChild(segment, handler);
} }
@ -91,7 +92,6 @@ class StaticNode<T> implements Node<T> {
// TODO: get rid of fixed param name // TODO: get rid of fixed param name
class DynamicNode<T> extends StaticNode<T> implements Node<T> { class DynamicNode<T> extends StaticNode<T> implements Node<T> {
constructor( constructor(
public readonly paramName: string,
handler?: T, handler?: T,
) { ) {
super(handler); super(handler);
@ -104,6 +104,7 @@ class DynamicNode<T> extends StaticNode<T> implements Node<T> {
class WildcardNode<T> implements Node<T> { class WildcardNode<T> implements Node<T> {
public handler: Option<T>; public handler: Option<T>;
public paramNames: string[] = [];
constructor(handler?: T) { constructor(handler?: T) {
this.handler = fromNullableVal(handler); this.handler = fromNullableVal(handler);
@ -144,6 +145,7 @@ export class RouterTree<T> {
public add(path: string, handler: T): void { public add(path: string, handler: T): void {
const segments = this.splitPath(path); const segments = this.splitPath(path);
const paramNames: string[] = this.extractParams(segments);
let current: TreeNode<T> = this.root; let current: TreeNode<T> = this.root;
for (const segment of segments) { for (const segment of segments) {
@ -157,15 +159,21 @@ export class RouterTree<T> {
) )
); );
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); current.handler = some(handler);
} }
public find(path: string): Option<RouteMatch<T>> { public find(path: string): Option<RouteMatch<T>> {
const segments = this.splitPath(path); const segments = this.splitPath(path);
const params: Record<string, string> = {}; const paramValues: string[] = [];
let current: TreeNode<T> = this.root; let current: TreeNode<T> = this.root;
let i = 0; let i = 0;
@ -175,7 +183,7 @@ export class RouterTree<T> {
const nextNode = current.getChild(segment).ifSome((child) => { const nextNode = current.getChild(segment).ifSome((child) => {
if (child.isDynamicNode()) { if (child.isDynamicNode()) {
params[child.paramName] = segment; paramValues.push(segment);
} }
current = child; current = child;
}); });
@ -186,10 +194,16 @@ export class RouterTree<T> {
if (current.isWildcardNode()) { if (current.isWildcardNode()) {
const rest = segments.slice(i - 1); const rest = segments.slice(i - 1);
if (rest.length > 0) { 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 })); return current.handler.map((value) => ({ value, params }));
} }
@ -214,6 +228,16 @@ export class RouterTree<T> {
const trimmed = path.trim().replace(/^\/+/, "").replace(/\/+$/, ""); const trimmed = path.trim().replace(/^\/+/, "").replace(/\/+$/, "");
return trimmed ? trimmed.split(this.pathSeparator) : []; 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<string, string>; export type Params = Record<string, string>;