diff --git a/src/router/linear-router/router.test.ts b/src/router/linear-router/router.test.ts index fdd95aaa..1bfbcae0 100644 --- a/src/router/linear-router/router.test.ts +++ b/src/router/linear-router/router.test.ts @@ -49,4 +49,17 @@ describe('LinearRouter', () => { expect(res[0][0]).toBe('GET /book') }) }) + + describe('Skip part', () => { + const router = new LinearRouter() + + beforeEach(() => { + router.add('GET', '/products/:id{d+}', 'GET /products/:id{d+}') + }) + + it('GET /products/list', () => { + const [res] = router.match('GET', '/products/list') + expect(res.length).toBe(0) + }) + }) }) diff --git a/src/router/linear-router/router.ts b/src/router/linear-router/router.ts index c05451ea..6a490448 100644 --- a/src/router/linear-router/router.ts +++ b/src/router/linear-router/router.ts @@ -13,117 +13,123 @@ export class LinearRouter implements Router { routes: [string, string, T][] = [] add(method: string, path: string, handler: T) { - ;(checkOptionalParameter(path) || [path]).forEach((p) => { - this.routes.push([method, p, handler]) - }) + for ( + let i = 0, paths = checkOptionalParameter(path) || [path], len = paths.length; + i < len; + i++ + ) { + this.routes.push([method, paths[i], handler]) + } } match(method: string, path: string): Result { const handlers: [T, Params][] = [] ROUTES_LOOP: for (let i = 0, len = this.routes.length; i < len; i++) { const [routeMethod, routePath, handler] = this.routes[i] - if (routeMethod !== method && routeMethod !== METHOD_NAME_ALL) { - continue - } - if (routePath === '*' || routePath === '/*') { - handlers.push([handler, emptyParams]) - continue - } - - const hasStar = routePath.indexOf('*') !== -1 - const hasLabel = routePath.indexOf(':') !== -1 - if (!hasStar && !hasLabel) { - if (routePath === path || routePath + '/' === path) { + if (routeMethod === method || routeMethod === METHOD_NAME_ALL) { + if (routePath === '*' || routePath === '/*') { handlers.push([handler, emptyParams]) + continue } - } else if (hasStar && !hasLabel) { - const endsWithStar = routePath.charCodeAt(routePath.length - 1) === 42 - const parts = (endsWithStar ? routePath.slice(0, -2) : routePath).split(splitByStarRe) - const lastIndex = parts.length - 1 - for (let j = 0, pos = 0, len = parts.length; j < len; j++) { - const part = parts[j] - const index = path.indexOf(part, pos) - if (index !== pos) { - continue ROUTES_LOOP + const hasStar = routePath.indexOf('*') !== -1 + const hasLabel = routePath.indexOf(':') !== -1 + if (!hasStar && !hasLabel) { + if (routePath === path || routePath + '/' === path) { + handlers.push([handler, emptyParams]) } - pos += part.length - if (j === lastIndex) { - if ( - !endsWithStar && - pos !== path.length && - !(pos === path.length - 1 && path.charCodeAt(pos) === 47) - ) { - continue ROUTES_LOOP - } - } else { - const index = path.indexOf('/', pos) - if (index === -1) { - continue ROUTES_LOOP - } - pos = index - } - } - handlers.push([handler, emptyParams]) - } else if (hasLabel && !hasStar) { - const params: Record = Object.create(null) - const parts = routePath.match(splitPathRe) as string[] + } else if (hasStar && !hasLabel) { + const endsWithStar = routePath.charCodeAt(routePath.length - 1) === 42 + const parts = (endsWithStar ? routePath.slice(0, -2) : routePath).split(splitByStarRe) - const lastIndex = parts.length - 1 - for (let j = 0, pos = 0, len = parts.length; j < len; j++) { - if (pos === -1 || pos >= path.length) { - continue ROUTES_LOOP - } - - const part = parts[j] - if (part.charCodeAt(1) === 58) { - // /:label - let name = part.slice(2) - let value - - if (name.charCodeAt(name.length - 1) === 125) { - // :label{pattern} - const openBracePos = name.indexOf('{') - const pattern = name.slice(openBracePos + 1, -1) - const restPath = path.slice(pos + 1) - const match = new RegExp(pattern, 'd').exec(restPath) as RegExpMatchArrayWithIndices - if (!match || match.indices[0][0] !== 0 || match.indices[0][1] === 0) { - continue ROUTES_LOOP - } - name = name.slice(0, openBracePos) - value = restPath.slice(...match.indices[0]) - pos += match.indices[0][1] + 1 - } else { - let endValuePos = path.indexOf('/', pos + 1) - if (endValuePos === -1) { - if (pos + 1 === path.length) { - continue ROUTES_LOOP - } - endValuePos = path.length - } - value = path.slice(pos + 1, endValuePos) - pos = endValuePos - } - - params[name] ||= value as string - } else { + const lastIndex = parts.length - 1 + for (let j = 0, pos = 0, len = parts.length; j < len; j++) { + const part = parts[j] const index = path.indexOf(part, pos) if (index !== pos) { continue ROUTES_LOOP } pos += part.length - } - - if (j === lastIndex) { - if (pos !== path.length && !(pos === path.length - 1 && path.charCodeAt(pos) === 47)) { - continue ROUTES_LOOP + if (j === lastIndex) { + if ( + !endsWithStar && + pos !== path.length && + !(pos === path.length - 1 && path.charCodeAt(pos) === 47) + ) { + continue ROUTES_LOOP + } + } else { + const index = path.indexOf('/', pos) + if (index === -1) { + continue ROUTES_LOOP + } + pos = index } } - } + handlers.push([handler, emptyParams]) + } else if (hasLabel && !hasStar) { + const params: Record = Object.create(null) + const parts = routePath.match(splitPathRe) as string[] - handlers.push([handler, params]) - } else if (hasLabel && hasStar) { - throw new UnsupportedPathError() + const lastIndex = parts.length - 1 + for (let j = 0, pos = 0, len = parts.length; j < len; j++) { + if (pos === -1 || pos >= path.length) { + continue ROUTES_LOOP + } + + const part = parts[j] + if (part.charCodeAt(1) === 58) { + // /:label + let name = part.slice(2) + let value + + if (name.charCodeAt(name.length - 1) === 125) { + // :label{pattern} + const openBracePos = name.indexOf('{') + const pattern = name.slice(openBracePos + 1, -1) + const restPath = path.slice(pos + 1) + const match = new RegExp(pattern, 'd').exec(restPath) as RegExpMatchArrayWithIndices + if (!match || match.indices[0][0] !== 0 || match.indices[0][1] === 0) { + continue ROUTES_LOOP + } + name = name.slice(0, openBracePos) + value = restPath.slice(...match.indices[0]) + pos += match.indices[0][1] + 1 + } else { + let endValuePos = path.indexOf('/', pos + 1) + if (endValuePos === -1) { + if (pos + 1 === path.length) { + continue ROUTES_LOOP + } + endValuePos = path.length + } + value = path.slice(pos + 1, endValuePos) + pos = endValuePos + } + + params[name] ||= value as string + } else { + const index = path.indexOf(part, pos) + if (index !== pos) { + continue ROUTES_LOOP + } + pos += part.length + } + + if (j === lastIndex) { + if ( + pos !== path.length && + !(pos === path.length - 1 && path.charCodeAt(pos) === 47) + ) { + continue ROUTES_LOOP + } + } + } + + handlers.push([handler, params]) + } else if (hasLabel && hasStar) { + throw new UnsupportedPathError() + } } } diff --git a/src/router/pattern-router/router.ts b/src/router/pattern-router/router.ts index 0565fb43..db90bee7 100644 --- a/src/router/pattern-router/router.ts +++ b/src/router/pattern-router/router.ts @@ -40,8 +40,10 @@ export class PatternRouter implements Router { match(method: string, path: string): Result { const handlers: [T, Params][] = [] - for (const [pattern, routeMethod, handler] of this.routes) { - if (routeMethod === METHOD_NAME_ALL || routeMethod === method) { + for (let i = 0, len = this.routes.length; i < len; i++) { + const [pattern, routeMethod, handler] = this.routes[i] + + if (routeMethod === method || routeMethod === METHOD_NAME_ALL) { const match = pattern.exec(path) if (match) { handlers.push([handler, match.groups || Object.create(null)]) diff --git a/src/router/smart-router/router.ts b/src/router/smart-router/router.ts index 33f20b3c..8e7a11a5 100644 --- a/src/router/smart-router/router.ts +++ b/src/router/smart-router/router.ts @@ -30,9 +30,9 @@ export class SmartRouter implements Router { for (; i < len; i++) { const router = routers[i] try { - routes.forEach((args) => { - router.add(...args) - }) + for (let i = 0, len = routes.length; i < len; i++) { + router.add(...routes[i]) + } res = router.match(method, path) } catch (e) { if (e instanceof UnsupportedPathError) { diff --git a/src/router/trie-router/node.ts b/src/router/trie-router/node.ts index 1b8c3fd4..61e6ba45 100644 --- a/src/router/trie-router/node.ts +++ b/src/router/trie-router/node.ts @@ -7,7 +7,6 @@ type HandlerSet = { handler: T possibleKeys: string[] score: number - name: string // For debug } type HandlerParamsSet = HandlerSet & { @@ -20,23 +19,20 @@ export class Node { children: Record> patterns: Pattern[] order: number = 0 - name: string params: Record = Object.create(null) constructor(method?: string, handler?: T, children?: Record>) { this.children = children || Object.create(null) this.methods = [] - this.name = '' if (method && handler) { const m: Record> = Object.create(null) - m[method] = { handler, possibleKeys: [], score: 0, name: this.name } + m[method] = { handler, possibleKeys: [], score: 0 } this.methods = [m] } this.patterns = [] } insert(method: string, path: string, handler: T): Node { - this.name = `${method} ${path}` this.order = ++this.order // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -76,7 +72,6 @@ export class Node { const handlerSet: HandlerSet = { handler, possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i), - name: this.name, score: this.order, } @@ -100,12 +95,14 @@ export class Node { const processedSet: Record = Object.create(null) if (handlerSet !== undefined) { handlerSet.params = Object.create(null) - handlerSet.possibleKeys.forEach((key) => { - const processed = processedSet[handlerSet.name] + for (let i = 0, len = handlerSet.possibleKeys.length; i < len; i++) { + const key = handlerSet.possibleKeys[i] + const processed = processedSet[handlerSet.score] handlerSet.params[key] = params[key] && !processed ? params[key] : nodeParams[key] ?? params[key] - processedSet[handlerSet.name] = true - }) + processedSet[handlerSet.score] = true + } + handlerSets.push(handlerSet) } } @@ -132,7 +129,7 @@ export class Node { if (nextNode) { nextNode.params = node.params - if (isLast === true) { + if (isLast) { // '/hello/*' => match '/hello' if (nextNode.children['*']) { handlerSets.push( @@ -177,10 +174,10 @@ export class Node { continue } - if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) { + if (matcher === true || matcher.test(part)) { if (typeof key === 'string') { params[name] = part - if (isLast === true) { + if (isLast) { handlerSets.push(...this.gHSets(child, method, params, node.params)) if (child.children['*']) { handlerSets.push(...this.gHSets(child.children['*'], method, params, node.params)) diff --git a/src/router/trie-router/router.ts b/src/router/trie-router/router.ts index fa25bfe9..65700ffd 100644 --- a/src/router/trie-router/router.ts +++ b/src/router/trie-router/router.ts @@ -13,8 +13,8 @@ export class TrieRouter implements Router { add(method: string, path: string, handler: T) { const results = checkOptionalParameter(path) if (results) { - for (const p of results) { - this.node.insert(method, p, handler) + for (let i = 0, len = results.length; i < len; i++) { + this.node.insert(method, results[i], handler) } return }