Skip to content

Commit 761f55f

Browse files
committed
POC for custom route patterns
1 parent ac65adb commit 761f55f

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

library/helpers/buildRouteFromURL.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,15 @@ t.test("it detects numeric comma separated arrays", async (t) => {
198198
t.same(buildRouteFromURL("/users/1,2,3_"), "/users/1,2,3_");
199199
t.same(buildRouteFromURL("/users/1,2,3a"), "/users/1,2,3a");
200200
});
201+
202+
t.test("it supports custom patterns", async () => {
203+
t.same(
204+
buildRouteFromURL("/prefix-103799/api/dashboard", ["prefix-{digits}"]),
205+
"/:custom/api/dashboard"
206+
);
207+
208+
t.same(
209+
buildRouteFromURL("/blog/01-31513/slug", ["{digits}-{digits}"]),
210+
"/blog/:custom/slug"
211+
);
212+
});

library/helpers/buildRouteFromURL.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { safeCreateRegExp } from "../agent/safeCreateRegExp";
2+
import { escapeStringRegexp } from "./escapeStringRegexp";
13
import { looksLikeASecret } from "./looksLikeASecret";
24
import { safeDecodeURIComponent } from "./safeDecodeURIComponent";
35
import { tryParseURLPath } from "./tryParseURLPath";
@@ -15,7 +17,7 @@ const HASH = /^(?:[a-f0-9]{32}|[a-f0-9]{40}|[a-f0-9]{64}|[a-f0-9]{128})$/i;
1517
const HASH_LENGTHS = [32, 40, 64, 128];
1618
const NUMBER_ARRAY = /^\d+(?:,\d+)*$/;
1719

18-
export function buildRouteFromURL(url: string) {
20+
export function buildRouteFromURL(url: string, custom: string[] = []) {
1921
let path = tryParseURLPath(url);
2022

2123
if (!path) {
@@ -29,7 +31,10 @@ export function buildRouteFromURL(url: string) {
2931
}
3032
}
3133

32-
const route = path.split("/").map(replaceURLSegmentWithParam).join("/");
34+
const route = path
35+
.split("/")
36+
.map(replaceURLSegmentWithCustomParam(custom))
37+
.join("/");
3338

3439
if (route === "/") {
3540
return "/";
@@ -42,6 +47,46 @@ export function buildRouteFromURL(url: string) {
4247
return route;
4348
}
4449

50+
function compileCustom(pattern: string) {
51+
if (!pattern.includes("{") || !pattern.includes("}")) {
52+
return undefined;
53+
}
54+
55+
const supported: Record<string, string> = {
56+
"{digits}": `\\d+`,
57+
"{alpha}": "[a-zA-Z]+",
58+
};
59+
60+
// Split the pattern into tokens (placeholders and literals)
61+
const placeholderRegex = /(\{[a-zA-Z]+})/g;
62+
const parts = pattern.split(placeholderRegex);
63+
const regexParts = parts.map((part) => {
64+
if (supported[part]) {
65+
return supported[part];
66+
}
67+
68+
return escapeStringRegexp(part);
69+
});
70+
71+
return safeCreateRegExp(`^${regexParts.join("")}$`, "");
72+
}
73+
74+
function replaceURLSegmentWithCustomParam(custom: string[]) {
75+
const customPatterns = custom
76+
.map(compileCustom)
77+
.filter((p) => p !== undefined);
78+
79+
return (segment: string) => {
80+
for (const pattern of customPatterns) {
81+
if (pattern && pattern.test(segment)) {
82+
return `:custom`;
83+
}
84+
}
85+
86+
return replaceURLSegmentWithParam(segment);
87+
};
88+
}
89+
4590
function replaceURLSegmentWithParam(segment: string) {
4691
const charCode = segment.charCodeAt(0);
4792
const startsWithNumber = charCode >= 48 && charCode <= 57; // ASCII codes for '0' to '9'

0 commit comments

Comments
 (0)