Skip to content

Commit 6f2da87

Browse files
committed
chore: static path matcher
1 parent eda52f6 commit 6f2da87

File tree

8 files changed

+181
-76
lines changed

8 files changed

+181
-76
lines changed

packages/router/src/new-matcher/matcher-pattern.ts renamed to packages/router/src/new-route-resolver/matcher-pattern.ts

Lines changed: 72 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,34 @@ import type {
66
} from './matcher'
77
import type { MatcherParamsFormatted } from './matcher-location'
88

9+
/**
10+
* Allows to match, extract, parse and build a path. Tailored to iterate through route records and check if a location
11+
* matches. When it cannot match, it returns `null` instead of throwing to not force a try/catch block around each
12+
* iteration in for loops.
13+
*/
914
export interface MatcherPattern {
1015
/**
1116
* Name of the matcher. Unique across all matchers.
1217
*/
1318
name: MatcherName
1419

20+
// TODO: add route record to be able to build the matched
21+
1522
/**
16-
* Extracts from a formatted, unencoded params object the ones belonging to the path, query, and hash.
17-
* @param params - Params to extract from.
23+
* Extracts from an unencoded, parsed params object the ones belonging to the path, query, and hash in their
24+
* serialized format but still unencoded. e.g. `{ id: 2 }` -> `{ id: '2' }`. If any params are missing, return `null`.
25+
*
26+
* @param params - Params to extract from. If any params are missing, throws
1827
*/
19-
unformatParams(
28+
matchParams(
2029
params: MatcherParamsFormatted
21-
): [path: MatcherPathParams, query: MatcherQueryParams, hash: string]
30+
):
31+
| readonly [
32+
pathParams: MatcherPathParams,
33+
queryParams: MatcherQueryParams,
34+
hashParam: string
35+
]
36+
| null
2237

2338
/**
2439
* Extracts the defined params from an encoded path, query, and hash parsed from a URL. Does not apply formatting or
@@ -44,23 +59,34 @@ export interface MatcherPattern {
4459
path: string
4560
query: MatcherQueryParams
4661
hash: string
47-
}): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] | null
62+
}):
63+
| readonly [
64+
pathParams: MatcherPathParams,
65+
queryParams: MatcherQueryParams,
66+
hashParam: string
67+
]
68+
| null
4869

4970
/**
5071
* Takes encoded params object to form the `path`,
51-
* @param path - encoded path params
72+
*
73+
* @param pathParams - encoded path params
5274
*/
53-
buildPath(path: MatcherPathParams): string
75+
buildPath(pathParams: MatcherPathParams): string
5476

5577
/**
56-
* Runs the decoded params through the formatting functions if any.
57-
* @param params - Params to format.
78+
* Runs the decoded params through the parsing functions if any, allowing them to be in be of a type other than a
79+
* string.
80+
*
81+
* @param pathParams - decoded path params
82+
* @param queryParams - decoded query params
83+
* @param hashParam - decoded hash param
5884
*/
59-
formatParams(
60-
path: MatcherPathParams,
61-
query: MatcherQueryParams,
62-
hash: string
63-
): MatcherParamsFormatted
85+
parseParams(
86+
pathParams: MatcherPathParams,
87+
queryParams: MatcherQueryParams,
88+
hashParam: string
89+
): MatcherParamsFormatted | null
6490
}
6591

6692
interface PatternParamOptions_Base<T = unknown> {
@@ -69,7 +95,11 @@ interface PatternParamOptions_Base<T = unknown> {
6995
default?: T | (() => T)
7096
}
7197

72-
export interface PatternParamOptions extends PatternParamOptions_Base {}
98+
export interface PatternPathParamOptions<T = unknown>
99+
extends PatternParamOptions_Base<T> {
100+
re: RegExp
101+
keys: string[]
102+
}
73103

74104
export interface PatternQueryParamOptions<T = unknown>
75105
extends PatternParamOptions_Base<T> {
@@ -82,16 +112,16 @@ export interface PatternHashParamOptions
82112
extends PatternParamOptions_Base<string> {}
83113

84114
export interface MatcherPatternPath {
85-
build(path: MatcherPathParams): string
115+
buildPath(path: MatcherPathParams): string
86116
match(path: string): MatcherPathParams
87-
format(params: MatcherPathParams): MatcherParamsFormatted
88-
unformat(params: MatcherParamsFormatted): MatcherPathParams
117+
parse?(params: MatcherPathParams): MatcherParamsFormatted
118+
serialize?(params: MatcherParamsFormatted): MatcherPathParams
89119
}
90120

91121
export interface MatcherPatternQuery {
92122
match(query: MatcherQueryParams): MatcherQueryParams
93-
format(params: MatcherQueryParams): MatcherParamsFormatted
94-
unformat(params: MatcherParamsFormatted): MatcherQueryParams
123+
parse(params: MatcherQueryParams): MatcherParamsFormatted
124+
serialize(params: MatcherParamsFormatted): MatcherQueryParams
95125
}
96126

97127
export interface MatcherPatternHash {
@@ -100,8 +130,8 @@ export interface MatcherPatternHash {
100130
* @param hash - encoded hash
101131
*/
102132
match(hash: string): string
103-
format(hash: string): MatcherParamsFormatted
104-
unformat(params: MatcherParamsFormatted): string
133+
parse(hash: string): MatcherParamsFormatted
134+
serialize(params: MatcherParamsFormatted): string
105135
}
106136

107137
export class MatcherPatternImpl implements MatcherPattern {
@@ -116,37 +146,42 @@ export class MatcherPatternImpl implements MatcherPattern {
116146
path: string
117147
query: MatcherQueryParams
118148
hash: string
119-
}): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] {
120-
return [
121-
this.path.match(location.path),
122-
this.query?.match(location.query) ?? {},
123-
this.hash?.match(location.hash) ?? '',
124-
]
149+
}) {
150+
// TODO: is this performant? Compare to a check with `null
151+
try {
152+
return [
153+
this.path.match(location.path),
154+
this.query?.match(location.query) ?? {},
155+
this.hash?.match(location.hash) ?? '',
156+
] as const
157+
} catch {
158+
return null
159+
}
125160
}
126161

127-
formatParams(
162+
parseParams(
128163
path: MatcherPathParams,
129164
query: MatcherQueryParams,
130165
hash: string
131166
): MatcherParamsFormatted {
132167
return {
133-
...this.path.format(path),
134-
...this.query?.format(query),
135-
...this.hash?.format(hash),
168+
...this.path.parse?.(path),
169+
...this.query?.parse(query),
170+
...this.hash?.parse(hash),
136171
}
137172
}
138173

139174
buildPath(path: MatcherPathParams): string {
140-
return this.path.build(path)
175+
return this.path.buildPath(path)
141176
}
142177

143-
unformatParams(
178+
matchParams(
144179
params: MatcherParamsFormatted
145180
): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] {
146181
return [
147-
this.path.unformat(params),
148-
this.query?.unformat(params) ?? {},
149-
this.hash?.unformat(params) ?? '',
182+
this.path.serialize?.(params) ?? {},
183+
this.query?.serialize(params) ?? {},
184+
this.hash?.serialize(params) ?? '',
150185
]
151186
}
152187
}

packages/router/src/new-matcher/matcher.spec.ts renamed to packages/router/src/new-route-resolver/matcher.spec.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ function createMatcherPattern(
1010

1111
const EMPTY_PATH_PATTERN_MATCHER = {
1212
match: (path: string) => ({}),
13-
format: (params: {}) => ({}),
14-
unformat: (params: {}) => ({}),
15-
build: () => '/',
13+
parse: (params: {}) => ({}),
14+
serialize: (params: {}) => ({}),
15+
buildPath: () => '/',
1616
} satisfies MatcherPatternPath
1717

1818
describe('Matcher', () => {
@@ -42,9 +42,9 @@ describe('Matcher', () => {
4242
if (!match) throw new Error('no match')
4343
return { id: match[1] }
4444
},
45-
format: (params: { id: string }) => ({ id: Number(params.id) }),
46-
unformat: (params: { id: number }) => ({ id: String(params.id) }),
47-
build: params => `/foo/${params.id}`,
45+
parse: (params: { id: string }) => ({ id: Number(params.id) }),
46+
serialize: (params: { id: number }) => ({ id: String(params.id) }),
47+
buildPath: params => `/foo/${params.id}`,
4848
})
4949
)
5050

@@ -69,8 +69,8 @@ describe('Matcher', () => {
6969
match: query => ({
7070
id: Array.isArray(query.id) ? query.id[0] : query.id,
7171
}),
72-
format: (params: { id: string }) => ({ id: Number(params.id) }),
73-
unformat: (params: { id: number }) => ({ id: String(params.id) }),
72+
parse: (params: { id: string }) => ({ id: Number(params.id) }),
73+
serialize: (params: { id: number }) => ({ id: String(params.id) }),
7474
})
7575
)
7676

@@ -94,8 +94,8 @@ describe('Matcher', () => {
9494
undefined,
9595
{
9696
match: hash => hash,
97-
format: hash => ({ a: hash.slice(1) }),
98-
unformat: ({ a }) => '#a',
97+
parse: hash => ({ a: hash.slice(1) }),
98+
serialize: ({ a }) => '#a',
9999
}
100100
)
101101
)
@@ -138,26 +138,26 @@ describe('Matcher', () => {
138138
createMatcherPattern(
139139
Symbol('foo'),
140140
{
141-
build: params => `/foo/${params.id}`,
141+
buildPath: params => `/foo/${params.id}`,
142142
match: path => {
143143
const match = path.match(/^\/foo\/([^/]+?)$/)
144144
if (!match) throw new Error('no match')
145145
return { id: match[1] }
146146
},
147-
format: params => ({ id: Number(params.id) }),
148-
unformat: params => ({ id: String(params.id) }),
147+
parse: params => ({ id: Number(params.id) }),
148+
serialize: params => ({ id: String(params.id) }),
149149
},
150150
{
151151
match: query => ({
152152
id: Array.isArray(query.id) ? query.id[0] : query.id,
153153
}),
154-
format: params => ({ q: Number(params.id) }),
155-
unformat: params => ({ id: String(params.q) }),
154+
parse: params => ({ q: Number(params.id) }),
155+
serialize: params => ({ id: String(params.q) }),
156156
},
157157
{
158158
match: hash => hash,
159-
format: hash => ({ a: hash.slice(1) }),
160-
unformat: ({ a }) => '#a',
159+
parse: hash => ({ a: hash.slice(1) }),
160+
serialize: ({ a }) => '#a',
161161
}
162162
)
163163
)
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { describe, it } from 'vitest'
2-
import { NEW_MatcherLocationResolved, createCompiledMatcher } from './matcher'
2+
import { NEW_LocationResolved, createCompiledMatcher } from './matcher'
33

44
describe('Matcher', () => {
55
it('resolves locations', () => {
66
const matcher = createCompiledMatcher()
77
matcher.resolve('/foo')
88
// @ts-expect-error: needs currentLocation
99
matcher.resolve('foo')
10-
matcher.resolve('foo', {} as NEW_MatcherLocationResolved)
10+
matcher.resolve('foo', {} as NEW_LocationResolved)
1111
matcher.resolve({ name: 'foo', params: {} })
1212
// @ts-expect-error: needs currentLocation
1313
matcher.resolve({ params: { id: 1 } })
14-
matcher.resolve({ params: { id: 1 } }, {} as NEW_MatcherLocationResolved)
14+
matcher.resolve({ params: { id: 1 } }, {} as NEW_LocationResolved)
1515
})
1616
})

0 commit comments

Comments
 (0)