Skip to content

Commit 4f42dfa

Browse files
committed
chore: refine trailing slashes with wildcards and params
1 parent 961190e commit 4f42dfa

3 files changed

Lines changed: 38 additions & 9 deletions

File tree

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ This function supports the following features:
5454

5555
`matchPattern` uses token-based comparision to completely forego regular expressions, which should, technically, make it more performant and less prone to vulnerabilities. Doesn't promise full feature parity with `path-to-regexp` but currently uses its test suite as the compliance bar.
5656

57+
### Trailing slashes
58+
59+
The pattern is the source of truth. If it ends with a trailing slash, then the input must also end with it to match. If the pattern doesn't end with a trailing slash, then the trailing slash in the input is ignored when matching.
60+
61+
```ts
62+
// No trailing slash in the pattern? Ignore it.
63+
matchPattern('/api', '/api') //
64+
matchPattern('/api/', '/api') //
65+
66+
// Trailing slash in the pattern? It's required.
67+
matchPattern('/api/', '/api/') //
68+
matchPattern('/api', '/api/') //
69+
```
70+
71+
> This is to accommodate to JavaScript developers not being used to providing trailing slashes.
72+
73+
### Wildcards
74+
75+
Wildcards _do not require value_ at their position.
76+
77+
```ts
78+
matchPattern('/user/', '/user/*') // ✅ { params: { '0': ''} }
79+
matchPattern('/user', '/user/*') //
80+
```
81+
82+
> Note that the slash before the wildcard is still treated as required.
83+
5784
## Benchmarks
5885

5986
```sh

src/index.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,17 +338,21 @@ function matchTokens(
338338

339339
// Check emptiness before allocating the captured substring.
340340
const allowEmpty =
341-
token.type === TokenType.Param &&
342-
(token.modifier === '?' || token.modifier === '*')
341+
token.type === TokenType.Wildcard ||
342+
(token.type === TokenType.Param &&
343+
(token.modifier === '?' || token.modifier === '*'))
343344

344345
if (!allowEmpty && position === endPosition) {
345346
return NO_MATCH
346347
}
347348

348349
if (token.type === TokenType.Wildcard) {
349-
params[String(wildcardIndex++)] = decode(
350-
inputString.slice(position, endPosition),
351-
)
350+
if (position !== endPosition) {
351+
params[String(wildcardIndex)] = decode(
352+
inputString.slice(position, endPosition),
353+
)
354+
}
355+
wildcardIndex++
352356
} else if (position !== endPosition) {
353357
const captured = decode(inputString.slice(position, endPosition))
354358
const existing = params[token.name]
@@ -432,7 +436,7 @@ export function matchPattern(
432436

433437
// Pure literal patterns are just a string equality check.
434438
// Try raw first, then decoded (for encoded inputs like %C3%A9 vs é).
435-
// When the pattern doesn't end with '/', also match with trailing
439+
// When the pattern doesn't end with '/', also try with trailing
436440
// slash stripped from the input.
437441
if (isLiteralOnly) {
438442
if (inputString === pattern || decode(inputString) === pattern) {

tests/index.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,8 @@ it.each<
7979
'http://localhost/user/*/*',
8080
MATCHES_WITH_PARAMS({ '0': '123', '1': 'settings' }),
8181
],
82-
83-
/* Wildcard non-match */
8482
['http://localhost', 'http://localhost/*', NO_MATCH],
85-
['http://localhost/', 'http://localhost/*', NO_MATCH],
83+
['http://localhost/', 'http://localhost/*', MATCHES_WITHOUT_PARAMS],
8684

8785
/* Three or more wildcards */
8886
[

0 commit comments

Comments
 (0)