Skip to content

Commit f846fad

Browse files
committed
refactor: add encoding within matcher
1 parent 9d15638 commit f846fad

File tree

2 files changed

+34
-51
lines changed

2 files changed

+34
-51
lines changed

packages/router/src/experimental/route-resolver/matchers/matcher-pattern.spec.ts

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,7 @@ describe('MatcherPatternPathCustom', () => {
112112
// all defaults
113113
teamId: {},
114114
},
115-
({ teamId }) => {
116-
if (typeof teamId !== 'string') {
117-
throw invalid('teamId must be a string')
118-
}
119-
return pathEncoded`/teams/${teamId}/b`
120-
}
115+
['teams', 0, 'b']
121116
)
122117

123118
expect(pattern.match('/teams/123/b')).toEqual({
@@ -138,12 +133,7 @@ describe('MatcherPatternPathCustom', () => {
138133
{
139134
teamId: {},
140135
},
141-
({ teamId }) => {
142-
if (typeof teamId !== 'string') {
143-
throw invalid('teamId must be a string')
144-
}
145-
return pathEncoded`/teams/${teamId}`
146-
}
136+
['teams', 0]
147137
)
148138
expect(pattern.match('/teams/a%20b')).toEqual({ teamId: 'a b' })
149139
expect(pattern.build({ teamId: 'a b' })).toBe('/teams/a%20b')
@@ -155,12 +145,7 @@ describe('MatcherPatternPathCustom', () => {
155145
{
156146
teamId: { optional: true },
157147
},
158-
({ teamId }) => {
159-
if (teamId != null && typeof teamId !== 'string') {
160-
throw invalid('teamId must be a string')
161-
}
162-
return teamId ? pathEncoded`/teams/${teamId}/b` : '/teams/b'
163-
}
148+
['teams', 0, 'b']
164149
)
165150

166151
expect(pattern.match('/teams/b')).toEqual({ teamId: null })
@@ -177,12 +162,7 @@ describe('MatcherPatternPathCustom', () => {
177162
{
178163
teamId: { repeat: true },
179164
},
180-
({ teamId }) => {
181-
if (!Array.isArray(teamId)) {
182-
throw invalid('teamId must be an array')
183-
}
184-
return '/teams/' + teamId.join('/') + '/b'
185-
}
165+
['teams', 0, 'b']
186166
)
187167

188168
expect(pattern.match('/teams/123/b')).toEqual({ teamId: ['123'] })
@@ -201,15 +181,7 @@ describe('MatcherPatternPathCustom', () => {
201181
{
202182
teamId: { repeat: true, optional: true },
203183
},
204-
({ teamId }) => {
205-
if (!Array.isArray(teamId)) {
206-
throw invalid('teamId must be an array')
207-
}
208-
const joined = teamId.join('/')
209-
return teamId
210-
? '/teams' + (joined ? '/' + joined : '') + '/b'
211-
: '/teams/b'
212-
}
184+
['teams', 0, 'b']
213185
)
214186

215187
expect(pattern.match('/teams/123/b')).toEqual({ teamId: ['123'] })

packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { encodeParam } from '../../../encoding'
12
import { warn } from '../../../warning'
23
import { decode, MatcherQueryParams } from '../resolver-abstract'
34
import { miss } from './errors'
@@ -198,6 +199,7 @@ interface MatcherPatternPathCustomParamOptions<
198199
TOut = string | string[] | null,
199200
> {
200201
repeat?: boolean
202+
// TODO: not needed because in the regexp, the value is undefined if the group is optional and not given
201203
optional?: boolean
202204
parser?: Param_GetSet<TIn, TOut>
203205
}
@@ -237,61 +239,70 @@ export const PARAM_NUMBER_REPEATABLE_OPTIONAL = {
237239
} satisfies Param_GetSet<string[] | null, number[] | null>
238240

239241
export class MatcherPatternPathCustomParams implements MatcherPatternPath {
240-
// private paramsKeys: string[]
242+
private paramsKeys: string[]
241243

242244
constructor(
243-
// TODO: make this work with named groups and simplify `params` to be an array of the repeat flag
244245
readonly re: RegExp,
245246
readonly params: Record<
246247
string,
247-
// @ts-expect-error: adapt with generic class
248248
MatcherPatternPathCustomParamOptions<unknown, unknown>
249249
>,
250-
readonly build: (params: MatcherParamsFormatted) => string
251250
// A better version could be using all the parts to join them
252251
// .e.g ['users', 0, 'profile', 1] -> /users/123/profile/456
253252
// numbers are indexes of the params in the params object keys
254-
// readonly pathParts: Array<string | number>
253+
readonly pathParts: Array<string | number>
255254
) {
256-
// this.paramsKeys = Object.keys(this.params)
255+
this.paramsKeys = Object.keys(this.params)
257256
}
258257

259258
match(path: string): MatcherParamsFormatted {
260259
const match = path.match(this.re)
261260
if (!match) {
262261
throw miss()
263262
}
263+
// NOTE: if we have params, we assume named groups
264264
const params = {} as MatcherParamsFormatted
265265
let i = 1 // index in match array
266266
for (const paramName in this.params) {
267-
const currentParam = this.params[paramName]
268-
// an optional group in the regexp will return undefined
269-
const currentMatch = (match[i++] as string | undefined) ?? null
270-
if (__DEV__ && !currentParam.optional && !currentMatch) {
271-
warn(
272-
`Unexpected undefined value for param "${paramName}". Regexp: ${String(this.re)}. path: "${path}". This is likely a bug.`
273-
)
274-
throw miss()
275-
}
267+
const paramOptions = this.params[paramName]
268+
const currentMatch = (match[i] as string | undefined) ?? null
276269

277-
const value = currentParam.repeat
270+
const value = paramOptions.repeat
278271
? (currentMatch?.split('/') || []).map(
279272
// using just decode makes the type inference fail
280273
v => decode(v)
281274
)
282275
: decode(currentMatch)
283276

284-
params[paramName] = (currentParam.parser?.get || (v => v))(value)
277+
params[paramName] = (paramOptions.parser?.get || (v => v))(value)
285278
}
286279

287-
if (__DEV__ && i !== match.length) {
280+
if (
281+
__DEV__ &&
282+
Object.keys(params).length !== Object.keys(this.params).length
283+
) {
288284
warn(
289285
`Regexp matched ${match.length} params, but ${i} params are defined. Found when matching "${path}" against ${String(this.re)}`
290286
)
291287
}
292288

293289
return params
294290
}
291+
292+
build(params: MatcherParamsFormatted): string {
293+
return this.pathParts.reduce((acc, part) => {
294+
if (typeof part === 'string') {
295+
return acc + '/' + part
296+
}
297+
const paramName = this.paramsKeys[part]
298+
const paramOptions = this.params[paramName]
299+
const value = (paramOptions.parser?.set || (v => v))(params[paramName])
300+
const encodedValue = Array.isArray(value)
301+
? value.map(encodeParam).join('/')
302+
: encodeParam(value)
303+
return encodedValue ? acc + '/' + encodedValue : acc
304+
}, '')
305+
}
295306
}
296307

297308
/**

0 commit comments

Comments
 (0)