Skip to content

Commit 5f10cfd

Browse files
committed
Add addRouteParam function
1 parent 535ee83 commit 5f10cfd

15 files changed

+191
-84
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as t from "tap";
2+
import { wrap } from "../helpers/wrap";
3+
import { addRouteParam, getRegisteredRouteParams } from "./addRouteParam";
4+
5+
let logs: string[] = [];
6+
wrap(console, "warn", function warn() {
7+
return function warn(message: string) {
8+
logs.push(message);
9+
};
10+
});
11+
12+
t.beforeEach(() => {
13+
logs = [];
14+
});
15+
16+
t.test("it warns if slash is included", async (t) => {
17+
addRouteParam("prefix/{digits}");
18+
t.same(logs[0], "addRouteParam(...) expects a pattern without slashes.");
19+
});
20+
21+
t.test("it warns if no curly braces are included", async (t) => {
22+
addRouteParam("prefix-digits");
23+
t.same(
24+
logs[0],
25+
"addRouteParam(...) expects a pattern that includes {digits} or {alpha}."
26+
);
27+
});
28+
29+
t.test("addRouteParam adds valid patterns", async (t) => {
30+
addRouteParam("prefix-{digits}");
31+
t.same(getRegisteredRouteParams().length, 1);
32+
t.same(getRegisteredRouteParams()[0].test("prefix-12345"), true);
33+
34+
addRouteParam("prefix-{digits}");
35+
t.same(getRegisteredRouteParams().length, 1);
36+
37+
addRouteParam("prefix-{alpha}");
38+
t.same(getRegisteredRouteParams().length, 2);
39+
t.same(getRegisteredRouteParams()[1].test("prefix-abcde"), true);
40+
41+
addRouteParam("prefix-{alpha}");
42+
t.same(getRegisteredRouteParams().length, 2);
43+
});

library/agent/addRouteParam.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// oxlint-disable no-console
2+
import { compileCustomPattern } from "../helpers/buildRouteFromURL";
3+
4+
const registeredPatterns: RegExp[] = [];
5+
const registeredPatternsSet: Set<string> = new Set();
6+
7+
export function addRouteParam(pattern: string) {
8+
if (!pattern.includes("{") || !pattern.includes("}")) {
9+
console.warn(
10+
"addRouteParam(...) expects a pattern that includes {digits} or {alpha}."
11+
);
12+
return;
13+
}
14+
15+
if (pattern.includes("/")) {
16+
console.warn("addRouteParam(...) expects a pattern without slashes.");
17+
return;
18+
}
19+
20+
const regex = compileCustomPattern(pattern);
21+
if (!regex) {
22+
console.warn(
23+
"addRouteParam(...) could not compile the provided pattern into a valid regular expression."
24+
);
25+
return;
26+
}
27+
28+
if (registeredPatternsSet.has(pattern)) {
29+
return;
30+
}
31+
32+
registeredPatternsSet.add(pattern);
33+
registeredPatterns.push(regex);
34+
}
35+
36+
export function getRegisteredRouteParams(): RegExp[] {
37+
return registeredPatterns;
38+
}
Lines changed: 75 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,162 @@
11
import * as t from "tap";
2-
import { buildRouteFromURL } from "./buildRouteFromURL";
2+
import { buildRouteFromURL, compileCustomPattern } from "./buildRouteFromURL";
33
import * as ObjectID from "bson-objectid";
44
import { createHash } from "crypto";
55

66
t.test("it returns undefined for invalid URLs", async () => {
7-
t.same(buildRouteFromURL(""), undefined);
8-
t.same(buildRouteFromURL("http"), undefined);
7+
t.same(buildRouteFromURL("", []), undefined);
8+
t.same(buildRouteFromURL("http", []), undefined);
99
});
1010

1111
t.test("it returns / for root URLs", async () => {
12-
t.same(buildRouteFromURL("/"), "/");
13-
t.same(buildRouteFromURL("http://localhost/"), "/");
12+
t.same(buildRouteFromURL("/", []), "/");
13+
t.same(buildRouteFromURL("http://localhost/", []), "/");
1414
});
1515

1616
t.test("it replaces numbers", async () => {
17-
t.same(buildRouteFromURL("/posts/3"), "/posts/:number");
18-
t.same(buildRouteFromURL("http://localhost/posts/3"), "/posts/:number");
19-
t.same(buildRouteFromURL("http://localhost/posts/3/"), "/posts/:number");
17+
t.same(buildRouteFromURL("/posts/3", []), "/posts/:number");
18+
t.same(buildRouteFromURL("http://localhost/posts/3", []), "/posts/:number");
19+
t.same(buildRouteFromURL("http://localhost/posts/3/", []), "/posts/:number");
2020
t.same(
21-
buildRouteFromURL("http://localhost/posts/3/comments/10"),
21+
buildRouteFromURL("http://localhost/posts/3/comments/10", []),
2222
"/posts/:number/comments/:number"
2323
);
2424
t.same(
25-
buildRouteFromURL("/blog/2023/05/great-article"),
25+
buildRouteFromURL("/blog/2023/05/great-article", []),
2626
"/blog/:number/:number/great-article"
2727
);
2828
});
2929

3030
t.test("it replaces dates", async () => {
31-
t.same(buildRouteFromURL("/posts/2023-05-01"), "/posts/:date");
32-
t.same(buildRouteFromURL("/posts/2023-05-01/"), "/posts/:date");
31+
t.same(buildRouteFromURL("/posts/2023-05-01", []), "/posts/:date");
32+
t.same(buildRouteFromURL("/posts/2023-05-01/", []), "/posts/:date");
3333
t.same(
34-
buildRouteFromURL("/posts/2023-05-01/comments/2023-05-01"),
34+
buildRouteFromURL("/posts/2023-05-01/comments/2023-05-01", []),
3535
"/posts/:date/comments/:date"
3636
);
37-
t.same(buildRouteFromURL("/posts/01-05-2023"), "/posts/:date");
37+
t.same(buildRouteFromURL("/posts/01-05-2023", []), "/posts/:date");
3838
});
3939

4040
t.test("it ignores API version numbers", async () => {
41-
t.same(buildRouteFromURL("/v1/posts/3"), "/v1/posts/:number");
41+
t.same(buildRouteFromURL("/v1/posts/3", []), "/v1/posts/:number");
4242
});
4343

4444
t.test("it replaces UUIDs v1", async () => {
4545
t.same(
46-
buildRouteFromURL("/posts/d9428888-122b-11e1-b85c-61cd3cbb3210"),
46+
buildRouteFromURL("/posts/d9428888-122b-11e1-b85c-61cd3cbb3210", []),
4747
"/posts/:uuid"
4848
);
4949
});
5050

5151
t.test("it replaces UUIDs v2", async () => {
5252
t.same(
53-
buildRouteFromURL("/posts/000003e8-2363-21ef-b200-325096b39f47"),
53+
buildRouteFromURL("/posts/000003e8-2363-21ef-b200-325096b39f47", []),
5454
"/posts/:uuid"
5555
);
5656
});
5757

5858
t.test("it replaces UUIDs v3", async () => {
5959
t.same(
60-
buildRouteFromURL("/posts/a981a0c2-68b1-35dc-bcfc-296e52ab01ec"),
60+
buildRouteFromURL("/posts/a981a0c2-68b1-35dc-bcfc-296e52ab01ec", []),
6161
"/posts/:uuid"
6262
);
6363
});
6464

6565
t.test("it replaces UUIDs v4", async () => {
6666
t.same(
67-
buildRouteFromURL("/posts/109156be-c4fb-41ea-b1b4-efe1671c5836"),
67+
buildRouteFromURL("/posts/109156be-c4fb-41ea-b1b4-efe1671c5836", []),
6868
"/posts/:uuid"
6969
);
7070
});
7171

7272
t.test("it replaces UUIDs v5", async () => {
7373
t.same(
74-
buildRouteFromURL("/posts/90123e1c-7512-523e-bb28-76fab9f2f73d"),
74+
buildRouteFromURL("/posts/90123e1c-7512-523e-bb28-76fab9f2f73d", []),
7575
"/posts/:uuid"
7676
);
7777
});
7878

7979
t.test("it replaces UUIDs v6", async () => {
8080
t.same(
81-
buildRouteFromURL("/posts/1ef21d2f-1207-6660-8c4f-419efbd44d48"),
81+
buildRouteFromURL("/posts/1ef21d2f-1207-6660-8c4f-419efbd44d48", []),
8282
"/posts/:uuid"
8383
);
8484
});
8585

8686
t.test("it replaces UUIDs v7", async () => {
8787
t.same(
88-
buildRouteFromURL("/posts/017f22e2-79b0-7cc3-98c4-dc0c0c07398f"),
88+
buildRouteFromURL("/posts/017f22e2-79b0-7cc3-98c4-dc0c0c07398f", []),
8989
"/posts/:uuid"
9090
);
9191
});
9292

9393
t.test("it replaces UUIDs v8", async () => {
9494
t.same(
95-
buildRouteFromURL("/posts/0d8f23a0-697f-83ae-802e-48f3756dd581"),
95+
buildRouteFromURL("/posts/0d8f23a0-697f-83ae-802e-48f3756dd581", []),
9696
"/posts/:uuid"
9797
);
9898
});
9999

100100
t.test("it ignores invalid UUIDs", async () => {
101101
t.same(
102-
buildRouteFromURL("/posts/00000000-0000-1000-6000-000000000000"),
102+
buildRouteFromURL("/posts/00000000-0000-1000-6000-000000000000", []),
103103
"/posts/00000000-0000-1000-6000-000000000000"
104104
);
105105
});
106106

107107
t.test("it ignores strings", async () => {
108-
t.same(buildRouteFromURL("/posts/abc"), "/posts/abc");
108+
t.same(buildRouteFromURL("/posts/abc", []), "/posts/abc");
109109
});
110110

111111
t.test("it replaces email addresses", async () => {
112-
t.same(buildRouteFromURL("/login/john.doe@acme.com"), "/login/:email");
113-
t.same(buildRouteFromURL("/login/john.doe+alias@acme.com"), "/login/:email");
112+
t.same(buildRouteFromURL("/login/john.doe@acme.com", []), "/login/:email");
113+
t.same(
114+
buildRouteFromURL("/login/john.doe+alias@acme.com", []),
115+
"/login/:email"
116+
);
114117
});
115118

116119
t.test("it replaces IP addresses", async () => {
117-
t.same(buildRouteFromURL("/block/1.2.3.4"), "/block/:ip");
120+
t.same(buildRouteFromURL("/block/1.2.3.4", []), "/block/:ip");
118121
t.same(
119-
buildRouteFromURL("/block/2001:2:ffff:ffff:ffff:ffff:ffff:ffff"),
122+
buildRouteFromURL("/block/2001:2:ffff:ffff:ffff:ffff:ffff:ffff", []),
120123
"/block/:ip"
121124
);
122-
t.same(buildRouteFromURL("/block/64:ff9a::255.255.255.255"), "/block/:ip");
123-
t.same(buildRouteFromURL("/block/100::"), "/block/:ip");
124-
t.same(buildRouteFromURL("/block/fec0::"), "/block/:ip");
125-
t.same(buildRouteFromURL("/block/227.202.96.196"), "/block/:ip");
125+
t.same(
126+
buildRouteFromURL("/block/64:ff9a::255.255.255.255", []),
127+
"/block/:ip"
128+
);
129+
t.same(buildRouteFromURL("/block/100::", []), "/block/:ip");
130+
t.same(buildRouteFromURL("/block/fec0::", []), "/block/:ip");
131+
t.same(buildRouteFromURL("/block/227.202.96.196", []), "/block/:ip");
126132
});
127133

128134
function generateHash(type: string) {
129135
return createHash(type).update("test").digest("hex");
130136
}
131137

132138
t.test("it replaces hashes", async () => {
133-
t.same(buildRouteFromURL(`/files/${generateHash("md5")}`), "/files/:hash");
134-
t.same(buildRouteFromURL(`/files/${generateHash("sha1")}`), "/files/:hash");
135-
t.same(buildRouteFromURL(`/files/${generateHash("sha256")}`), "/files/:hash");
136-
t.same(buildRouteFromURL(`/files/${generateHash("sha512")}`), "/files/:hash");
139+
t.same(
140+
buildRouteFromURL(`/files/${generateHash("md5")}`, []),
141+
"/files/:hash"
142+
);
143+
t.same(
144+
buildRouteFromURL(`/files/${generateHash("sha1")}`, []),
145+
"/files/:hash"
146+
);
147+
t.same(
148+
buildRouteFromURL(`/files/${generateHash("sha256")}`, []),
149+
"/files/:hash"
150+
);
151+
t.same(
152+
buildRouteFromURL(`/files/${generateHash("sha512")}`, []),
153+
"/files/:hash"
154+
);
137155
});
138156

139157
t.test("it replaces secrets", async () => {
140158
t.same(
141-
buildRouteFromURL("/confirm/CnJ4DunhYfv2db6T1FRfciRBHtlNKOYrjoz"),
159+
buildRouteFromURL("/confirm/CnJ4DunhYfv2db6T1FRfciRBHtlNKOYrjoz", []),
142160
"/confirm/:secret"
143161
);
144162
});
@@ -150,24 +168,24 @@ t.test("it replaces BSON ObjectIDs", async () => {
150168
"/posts/:objectId"
151169
);
152170
t.same(
153-
buildRouteFromURL(`/posts/66ec29159d00113616fc7184`),
171+
buildRouteFromURL(`/posts/66ec29159d00113616fc7184`, []),
154172
"/posts/:objectId"
155173
);
156174
});
157175

158176
t.test("it replaces ULID strings", async () => {
159177
t.same(
160-
buildRouteFromURL("/posts/01ARZ3NDEKTSV4RRFFQ69G5FAV"),
178+
buildRouteFromURL("/posts/01ARZ3NDEKTSV4RRFFQ69G5FAV", []),
161179
"/posts/:ulid"
162180
);
163181
t.same(
164-
buildRouteFromURL("/posts/01arz3ndektsv4rrffq69g5fav"),
182+
buildRouteFromURL("/posts/01arz3ndektsv4rrffq69g5fav", []),
165183
"/posts/:ulid"
166184
);
167185
});
168186

169187
t.test("test_ratelimiting_1 is not a secret", async () => {
170-
t.same(buildRouteFromURL("/test_ratelimiting_1"), "/test_ratelimiting_1");
188+
t.same(buildRouteFromURL("/test_ratelimiting_1", []), "/test_ratelimiting_1");
171189
});
172190

173191
t.test("it does not detect static files as secrets", async () => {
@@ -180,33 +198,37 @@ t.test("it does not detect static files as secrets", async () => {
180198
];
181199

182200
for (const file of files) {
183-
t.same(buildRouteFromURL(`/assets/${file}`), `/assets/${file}`);
201+
t.same(buildRouteFromURL(`/assets/${file}`, []), `/assets/${file}`);
184202
}
185203
});
186204

187205
t.test("it detects numeric comma separated arrays", async (t) => {
188-
t.same(buildRouteFromURL("/users/1,2"), "/users/:array(number)");
189-
t.same(buildRouteFromURL("/users/1,2,3,4,5"), "/users/:array(number)");
206+
t.same(buildRouteFromURL("/users/1,2", []), "/users/:array(number)");
207+
t.same(buildRouteFromURL("/users/1,2,3,4,5", []), "/users/:array(number)");
190208
t.same(
191-
buildRouteFromURL("/users/100,200,3000000,40000000,500000000"),
209+
buildRouteFromURL("/users/100,200,3000000,40000000,500000000", []),
192210
"/users/:array(number)"
193211
);
194212

195-
t.same(buildRouteFromURL("/users/1,2,3,4,"), "/users/1,2,3,4,");
196-
t.same(buildRouteFromURL("/users/1,"), "/users/1,");
197-
t.same(buildRouteFromURL("/users/,1,2"), "/users/,1,2");
198-
t.same(buildRouteFromURL("/users/1,2,3_"), "/users/1,2,3_");
199-
t.same(buildRouteFromURL("/users/1,2,3a"), "/users/1,2,3a");
213+
t.same(buildRouteFromURL("/users/1,2,3,4,", []), "/users/1,2,3,4,");
214+
t.same(buildRouteFromURL("/users/1,", []), "/users/1,");
215+
t.same(buildRouteFromURL("/users/,1,2", []), "/users/,1,2");
216+
t.same(buildRouteFromURL("/users/1,2,3_", []), "/users/1,2,3_");
217+
t.same(buildRouteFromURL("/users/1,2,3a", []), "/users/1,2,3a");
200218
});
201219

202220
t.test("it supports custom patterns", async () => {
203221
t.same(
204-
buildRouteFromURL("/prefix-103799/api/dashboard", ["prefix-{digits}"]),
222+
buildRouteFromURL("/prefix-103799/api/dashboard", [
223+
compileCustomPattern("prefix-{digits}")!,
224+
]),
205225
"/:custom/api/dashboard"
206226
);
207227

208228
t.same(
209-
buildRouteFromURL("/blog/01-31513/slug", ["{digits}-{digits}"]),
229+
buildRouteFromURL("/blog/01-31513/slug", [
230+
compileCustomPattern("{digits}-{digits}")!,
231+
]),
210232
"/blog/:custom/slug"
211233
);
212234
});

0 commit comments

Comments
 (0)