]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor: add encoding within matcher
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 8 Aug 2025 08:30:34 +0000 (10:30 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 8 Aug 2025 08:30:34 +0000 (10:30 +0200)
packages/router/src/experimental/route-resolver/matchers/matcher-pattern.spec.ts
packages/router/src/experimental/route-resolver/matchers/matcher-pattern.ts

index 0cc13cc5c9cbd7e6de049201e98d2e09c0232bcf..8fd542fa0d2e7b6b75eaee445a8bdd8b0d34bdc6 100644 (file)
@@ -112,12 +112,7 @@ describe('MatcherPatternPathCustom', () => {
         // all defaults
         teamId: {},
       },
-      ({ teamId }) => {
-        if (typeof teamId !== 'string') {
-          throw invalid('teamId must be a string')
-        }
-        return pathEncoded`/teams/${teamId}/b`
-      }
+      ['teams', 0, 'b']
     )
 
     expect(pattern.match('/teams/123/b')).toEqual({
@@ -138,12 +133,7 @@ describe('MatcherPatternPathCustom', () => {
       {
         teamId: {},
       },
-      ({ teamId }) => {
-        if (typeof teamId !== 'string') {
-          throw invalid('teamId must be a string')
-        }
-        return pathEncoded`/teams/${teamId}`
-      }
+      ['teams', 0]
     )
     expect(pattern.match('/teams/a%20b')).toEqual({ teamId: 'a b' })
     expect(pattern.build({ teamId: 'a b' })).toBe('/teams/a%20b')
@@ -155,12 +145,7 @@ describe('MatcherPatternPathCustom', () => {
       {
         teamId: { optional: true },
       },
-      ({ teamId }) => {
-        if (teamId != null && typeof teamId !== 'string') {
-          throw invalid('teamId must be a string')
-        }
-        return teamId ? pathEncoded`/teams/${teamId}/b` : '/teams/b'
-      }
+      ['teams', 0, 'b']
     )
 
     expect(pattern.match('/teams/b')).toEqual({ teamId: null })
@@ -177,12 +162,7 @@ describe('MatcherPatternPathCustom', () => {
       {
         teamId: { repeat: true },
       },
-      ({ teamId }) => {
-        if (!Array.isArray(teamId)) {
-          throw invalid('teamId must be an array')
-        }
-        return '/teams/' + teamId.join('/') + '/b'
-      }
+      ['teams', 0, 'b']
     )
 
     expect(pattern.match('/teams/123/b')).toEqual({ teamId: ['123'] })
@@ -201,15 +181,7 @@ describe('MatcherPatternPathCustom', () => {
       {
         teamId: { repeat: true, optional: true },
       },
-      ({ teamId }) => {
-        if (!Array.isArray(teamId)) {
-          throw invalid('teamId must be an array')
-        }
-        const joined = teamId.join('/')
-        return teamId
-          ? '/teams' + (joined ? '/' + joined : '') + '/b'
-          : '/teams/b'
-      }
+      ['teams', 0, 'b']
     )
 
     expect(pattern.match('/teams/123/b')).toEqual({ teamId: ['123'] })
index d239ac7ffdae5c27e83755d89ab235c07757fec6..b015241fa0989527e2069e9bf9cfe85c94710816 100644 (file)
@@ -1,3 +1,4 @@
+import { encodeParam } from '../../../encoding'
 import { warn } from '../../../warning'
 import { decode, MatcherQueryParams } from '../resolver-abstract'
 import { miss } from './errors'
@@ -198,6 +199,7 @@ interface MatcherPatternPathCustomParamOptions<
   TOut = string | string[] | null,
 > {
   repeat?: boolean
+  // TODO: not needed because in the regexp, the value is undefined if the group is optional and not given
   optional?: boolean
   parser?: Param_GetSet<TIn, TOut>
 }
@@ -237,23 +239,20 @@ export const PARAM_NUMBER_REPEATABLE_OPTIONAL = {
 } satisfies Param_GetSet<string[] | null, number[] | null>
 
 export class MatcherPatternPathCustomParams implements MatcherPatternPath {
-  // private paramsKeys: string[]
+  private paramsKeys: string[]
 
   constructor(
-    // TODO: make this work with named groups and simplify `params` to be an array of the repeat flag
     readonly re: RegExp,
     readonly params: Record<
       string,
-      // @ts-expect-error: adapt with generic class
       MatcherPatternPathCustomParamOptions<unknown, unknown>
     >,
-    readonly build: (params: MatcherParamsFormatted) => string
     // A better version could be using all the parts to join them
     // .e.g ['users', 0, 'profile', 1] -> /users/123/profile/456
     // numbers are indexes of the params in the params object keys
-    // readonly pathParts: Array<string | number>
+    readonly pathParts: Array<string | number>
   ) {
-    // this.paramsKeys = Object.keys(this.params)
+    this.paramsKeys = Object.keys(this.params)
   }
 
   match(path: string): MatcherParamsFormatted {
@@ -261,30 +260,27 @@ export class MatcherPatternPathCustomParams implements MatcherPatternPath {
     if (!match) {
       throw miss()
     }
+    // NOTE: if we have params, we assume named groups
     const params = {} as MatcherParamsFormatted
     let i = 1 // index in match array
     for (const paramName in this.params) {
-      const currentParam = this.params[paramName]
-      // an optional group in the regexp will return undefined
-      const currentMatch = (match[i++] as string | undefined) ?? null
-      if (__DEV__ && !currentParam.optional && !currentMatch) {
-        warn(
-          `Unexpected undefined value for param "${paramName}". Regexp: ${String(this.re)}. path: "${path}". This is likely a bug.`
-        )
-        throw miss()
-      }
+      const paramOptions = this.params[paramName]
+      const currentMatch = (match[i] as string | undefined) ?? null
 
-      const value = currentParam.repeat
+      const value = paramOptions.repeat
         ? (currentMatch?.split('/') || []).map(
             // using  just decode makes the type inference fail
             v => decode(v)
           )
         : decode(currentMatch)
 
-      params[paramName] = (currentParam.parser?.get || (v => v))(value)
+      params[paramName] = (paramOptions.parser?.get || (v => v))(value)
     }
 
-    if (__DEV__ && i !== match.length) {
+    if (
+      __DEV__ &&
+      Object.keys(params).length !== Object.keys(this.params).length
+    ) {
       warn(
         `Regexp matched ${match.length} params, but ${i} params are defined. Found when matching "${path}" against ${String(this.re)}`
       )
@@ -292,6 +288,21 @@ export class MatcherPatternPathCustomParams implements MatcherPatternPath {
 
     return params
   }
+
+  build(params: MatcherParamsFormatted): string {
+    return this.pathParts.reduce((acc, part) => {
+      if (typeof part === 'string') {
+        return acc + '/' + part
+      }
+      const paramName = this.paramsKeys[part]
+      const paramOptions = this.params[paramName]
+      const value = (paramOptions.parser?.set || (v => v))(params[paramName])
+      const encodedValue = Array.isArray(value)
+        ? value.map(encodeParam).join('/')
+        : encodeParam(value)
+      return encodedValue ? acc + '/' + encodedValue : acc
+    }, '')
+  }
 }
 
 /**