{
type: TokenType.Param,
value: 'id',
+ regexp: '',
+ repeatable: false,
+ optional: false,
+ },
+ ],
+ ])
+ })
+
+ it('param custom re', () => {
+ expect(tokenizePath('/:id(\\d+)')).toEqual([
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ regexp: '\\d+',
repeatable: false,
optional: false,
},
{
type: TokenType.Param,
value: 'id',
+ regexp: '',
repeatable: false,
optional: true,
},
})
it('param single+', () => {
- expect(tokenizePath('/:id+')).toEqual([
+ expect(tokenizePath('/:id+')).toMatchObject([
[
{
type: TokenType.Param,
})
it('param single*', () => {
- expect(tokenizePath('/:id*')).toEqual([
+ expect(tokenizePath('/:id*')).toMatchObject([
[
{
type: TokenType.Param,
})
it('param multiple', () => {
- expect(tokenizePath('/:id/:other')).toEqual([
+ expect(tokenizePath('/:id/:other')).toMatchObject([
[
{
type: TokenType.Param,
])
})
+ it('param multiple together', () => {
+ expect(tokenizePath('/:id:other:more')).toMatchObject([
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ repeatable: false,
+ optional: false,
+ },
+ {
+ type: TokenType.Param,
+ value: 'other',
+ repeatable: false,
+ optional: false,
+ },
+ {
+ type: TokenType.Param,
+ value: 'more',
+ repeatable: false,
+ optional: false,
+ },
+ ],
+ ])
+ })
+
it('param with static in between', () => {
- expect(tokenizePath('/:id-:other')).toEqual([
+ expect(tokenizePath('/:id-:other')).toMatchObject([
[
{
type: TokenType.Param,
})
it('param with static beginning', () => {
- expect(tokenizePath('/hey-:id')).toEqual([
+ expect(tokenizePath('/hey-:id')).toMatchObject([
[
{
type: TokenType.Static,
})
it('param with static end', () => {
- expect(tokenizePath('/:id-end')).toEqual([
+ expect(tokenizePath('/:id-end')).toMatchObject([
[
{
type: TokenType.Param,
) {
const pathParser = tokensToRegExp(...args)
expect(expectedRe).toBe(
- pathParser.re.toString().replace(/(:?^\/|\\|\/$)/g, '')
+ pathParser.re
+ .toString()
+ .replace(/(:?^\/|\/$)/g, '')
+ .replace(/\\\//g, '/')
)
}
- it('static', () => {
+ it('static single', () => {
+ matchRegExp('^/$', [[]])
+ })
+
+ it('static single', () => {
matchRegExp('^/home$', [[{ type: TokenType.Static, value: 'home' }]])
})
- it('param simple', () => {
- matchRegExp('^/([^/]+)$', [
+ it('static multiple', () => {
+ matchRegExp('^/home/other$', [
+ [{ type: TokenType.Static, value: 'home' }],
+ [{ type: TokenType.Static, value: 'other' }],
+ ])
+ })
+
+ it('param single', () => {
+ matchRegExp('^/([^/]+?)$', [
[
{
type: TokenType.Param,
],
])
})
+
+ it('param multiple', () => {
+ matchRegExp('^/([^/]+?)/([^/]+?)$', [
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ repeatable: false,
+ optional: false,
+ },
+ ],
+ [
+ {
+ type: TokenType.Param,
+ value: 'two',
+ repeatable: false,
+ optional: false,
+ },
+ ],
+ ])
+ })
+
+ it('param*', () => {
+ matchRegExp('^/((?:\\d+)(?:/(?:\\d+))*)?$', [
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ regexp: '\\d+',
+ repeatable: true,
+ optional: true,
+ },
+ ],
+ ])
+ })
+
+ it('param?', () => {
+ matchRegExp('^/(\\d+)?$', [
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ regexp: '\\d+',
+ repeatable: false,
+ optional: true,
+ },
+ ],
+ ])
+ })
+
+ it('param+', () => {
+ matchRegExp('^/((?:\\d+)(?:/(?:\\d+))*)$', [
+ [
+ {
+ type: TokenType.Param,
+ value: 'id',
+ regexp: '\\d+',
+ repeatable: true,
+ optional: false,
+ },
+ ],
+ ])
+ })
+ // end of describe
})
})
const enum TokenizerState {
Static,
Param,
+ ParamRegExp, // custom re for a param
EscapeNext,
}
interface TokenParam {
type: TokenType.Param
- regex?: string
- value: string
-}
-
-interface TokenParam {
- type: TokenType.Param
- regex?: string
+ regexp?: string
value: string
optional: boolean
repeatable: boolean
let char: string
// buffer of the value read
let buffer: string = ''
+ // custom regexp for a param
+ let customRe: string = ''
function consumeBuffer() {
if (!buffer) return
type: TokenType.Static,
value: buffer,
})
- } else if (state === TokenizerState.Param) {
+ } else if (
+ state === TokenizerState.Param ||
+ state === TokenizerState.ParamRegExp
+ ) {
segment.push({
type: TokenType.Param,
value: buffer,
+ regexp: customRe,
repeatable: char === '*' || char === '+',
optional: char === '*' || char === '?',
})
while (i < path.length) {
char = path[i++]
- if (char === '\\') {
+ if (char === '\\' && state !== TokenizerState.ParamRegExp) {
previousState = state
state = TokenizerState.EscapeNext
continue
case TokenizerState.Param:
if (char === '(') {
- // TODO: start custom regex
+ state = TokenizerState.ParamRegExp
+ customRe = ''
} else if (VALID_PARAM_RE.test(char)) {
addCharToBuffer()
} else {
}
break
+ case TokenizerState.ParamRegExp:
+ if (char === ')') {
+ consumeBuffer()
+ state = TokenizerState.Static
+ } else {
+ customRe += char
+ }
+ break
+
default:
crash('Unkwnonw state')
break
}
}
+ if (state === TokenizerState.ParamRegExp)
+ crash(`Unfinished custom RegExp for param "${buffer}"`)
+
consumeBuffer()
finalizeSegment()
keys: string[]
}
+const BASE_PARAM_PATTERN = '[^/]+?'
+
export function tokensToRegExp(segments: Array<Token[]>): PathParser {
let score = 0
let pattern = '^'
pattern += token.value
} else if (token.type === TokenType.Param) {
keys.push(token.value)
- pattern += `([^/]+)`
- // TODO: repeatable and others
+ const re = token.regexp ? token.regexp : BASE_PARAM_PATTERN
+ pattern += token.repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`
+ if (token.optional) pattern += '?'
}
}
}