|
| 1 | +import { encodeParam } from '../../../encoding' |
1 | 2 | import { warn } from '../../../warning' |
2 | 3 | import { decode, MatcherQueryParams } from '../resolver-abstract' |
3 | 4 | import { miss } from './errors' |
@@ -198,6 +199,7 @@ interface MatcherPatternPathCustomParamOptions< |
198 | 199 | TOut = string | string[] | null, |
199 | 200 | > { |
200 | 201 | repeat?: boolean |
| 202 | + // TODO: not needed because in the regexp, the value is undefined if the group is optional and not given |
201 | 203 | optional?: boolean |
202 | 204 | parser?: Param_GetSet<TIn, TOut> |
203 | 205 | } |
@@ -237,61 +239,70 @@ export const PARAM_NUMBER_REPEATABLE_OPTIONAL = { |
237 | 239 | } satisfies Param_GetSet<string[] | null, number[] | null> |
238 | 240 |
|
239 | 241 | export class MatcherPatternPathCustomParams implements MatcherPatternPath { |
240 | | - // private paramsKeys: string[] |
| 242 | + private paramsKeys: string[] |
241 | 243 |
|
242 | 244 | constructor( |
243 | | - // TODO: make this work with named groups and simplify `params` to be an array of the repeat flag |
244 | 245 | readonly re: RegExp, |
245 | 246 | readonly params: Record< |
246 | 247 | string, |
247 | | - // @ts-expect-error: adapt with generic class |
248 | 248 | MatcherPatternPathCustomParamOptions<unknown, unknown> |
249 | 249 | >, |
250 | | - readonly build: (params: MatcherParamsFormatted) => string |
251 | 250 | // A better version could be using all the parts to join them |
252 | 251 | // .e.g ['users', 0, 'profile', 1] -> /users/123/profile/456 |
253 | 252 | // numbers are indexes of the params in the params object keys |
254 | | - // readonly pathParts: Array<string | number> |
| 253 | + readonly pathParts: Array<string | number> |
255 | 254 | ) { |
256 | | - // this.paramsKeys = Object.keys(this.params) |
| 255 | + this.paramsKeys = Object.keys(this.params) |
257 | 256 | } |
258 | 257 |
|
259 | 258 | match(path: string): MatcherParamsFormatted { |
260 | 259 | const match = path.match(this.re) |
261 | 260 | if (!match) { |
262 | 261 | throw miss() |
263 | 262 | } |
| 263 | + // NOTE: if we have params, we assume named groups |
264 | 264 | const params = {} as MatcherParamsFormatted |
265 | 265 | let i = 1 // index in match array |
266 | 266 | 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 |
276 | 269 |
|
277 | | - const value = currentParam.repeat |
| 270 | + const value = paramOptions.repeat |
278 | 271 | ? (currentMatch?.split('/') || []).map( |
279 | 272 | // using just decode makes the type inference fail |
280 | 273 | v => decode(v) |
281 | 274 | ) |
282 | 275 | : decode(currentMatch) |
283 | 276 |
|
284 | | - params[paramName] = (currentParam.parser?.get || (v => v))(value) |
| 277 | + params[paramName] = (paramOptions.parser?.get || (v => v))(value) |
285 | 278 | } |
286 | 279 |
|
287 | | - if (__DEV__ && i !== match.length) { |
| 280 | + if ( |
| 281 | + __DEV__ && |
| 282 | + Object.keys(params).length !== Object.keys(this.params).length |
| 283 | + ) { |
288 | 284 | warn( |
289 | 285 | `Regexp matched ${match.length} params, but ${i} params are defined. Found when matching "${path}" against ${String(this.re)}` |
290 | 286 | ) |
291 | 287 | } |
292 | 288 |
|
293 | 289 | return params |
294 | 290 | } |
| 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 | + } |
295 | 306 | } |
296 | 307 |
|
297 | 308 | /** |
|
0 commit comments