// 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({
{
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')
{
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 })
{
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'] })
{
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'] })
+import { encodeParam } from '../../../encoding'
import { warn } from '../../../warning'
import { decode, MatcherQueryParams } from '../resolver-abstract'
import { miss } from './errors'
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>
}
} 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 {
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)}`
)
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
+ }, '')
+ }
}
/**