Skip to content

Commit

Permalink
feat: add has method
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-tymoshenko committed Sep 24, 2023
1 parent 86e7331 commit 33ae435
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 0 deletions.
146 changes: 146 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,152 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
currentNode.addRoute(route, this.constrainer)
}

Router.prototype.has = function has (opts) {
const method = opts.method
const path = opts.path
const constraints = opts.constraints || {}

if (this.trees[method] === undefined) {
return false
}

let pattern = path

let currentNode = this.trees[method]
let parentNodePathIndex = currentNode.prefix.length

const params = []
for (let i = 0; i <= pattern.length; i++) {
if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) {
// It's a double colon
i++
continue
}

const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58
const isWildcardNode = pattern.charCodeAt(i) === 42

if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) {
let staticNodePath = pattern.slice(parentNodePathIndex, i)
if (!this.caseSensitive) {
staticNodePath = staticNodePath.toLowerCase()
}
staticNodePath = staticNodePath.split('::').join(':')
staticNodePath = staticNodePath.split('%').join('%25')
// add the static part of the route to the tree
currentNode = currentNode.createStaticChild(staticNodePath)
if (currentNode === null) {
return false
}
}

if (isParametricNode) {
let isRegexNode = false
const regexps = []

let lastParamStartIndex = i + 1
for (let j = lastParamStartIndex; ; j++) {
const charCode = pattern.charCodeAt(j)

const isRegexParam = charCode === 40
const isStaticPart = charCode === 45 || charCode === 46
const isEndOfNode = charCode === 47 || j === pattern.length

if (isRegexParam || isStaticPart || isEndOfNode) {
const paramName = pattern.slice(lastParamStartIndex, j)
params.push(paramName)

isRegexNode = isRegexNode || isRegexParam || isStaticPart

if (isRegexParam) {
const endOfRegexIndex = getClosingParenthensePosition(pattern, j)
const regexString = pattern.slice(j, endOfRegexIndex + 1)

if (!this.allowUnsafeRegex) {
assert(isRegexSafe(new RegExp(regexString)), `The regex '${regexString}' is not safe!`)
}

regexps.push(trimRegExpStartAndEnd(regexString))

j = endOfRegexIndex + 1
} else {
regexps.push('(.*?)')
}

const staticPartStartIndex = j
for (; j < pattern.length; j++) {
const charCode = pattern.charCodeAt(j)
if (charCode === 47) break
if (charCode === 58) {
const nextCharCode = pattern.charCodeAt(j + 1)
if (nextCharCode === 58) j++
else break
}
}

let staticPart = pattern.slice(staticPartStartIndex, j)
if (staticPart) {
staticPart = staticPart.split('::').join(':')
staticPart = staticPart.split('%').join('%25')
regexps.push(escapeRegExp(staticPart))
}

lastParamStartIndex = j + 1

if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) {
const nodePattern = isRegexNode ? '()' + staticPart : staticPart
const nodePath = pattern.slice(i, j)

pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j)
i += nodePattern.length

const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null
currentNode = currentNode.getParametricChild(regex, staticPart || null, nodePath)
if (currentNode === null) {
return false
}
parentNodePathIndex = i + 1
break
}
}
}
} else if (isWildcardNode) {
// add the wildcard parameter
params.push('*')
currentNode = currentNode.createWildcardChild()
if (currentNode === null) {
return false
}
parentNodePathIndex = i + 1

if (i !== pattern.length - 1) {
throw new Error('Wildcard must be the last character in the route')
}
}
}

if (!this.caseSensitive) {
pattern = pattern.toLowerCase()
}

if (pattern === '*') {
pattern = '/*'
}

for (const existRoute of this.routes) {
const routeConstraints = existRoute.opts.constraints || {}
if (
existRoute.method === method &&
existRoute.pattern === pattern &&
deepEqual(routeConstraints, constraints)
) {
return true
}
}

return false
}

Router.prototype.hasConstraintStrategy = function (strategyName) {
return this.constrainer.hasConstraintStrategy(strategyName)
}
Expand Down
44 changes: 44 additions & 0 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ class ParentNode extends Node {
return staticChild
}

getStaticChild (path) {
if (path.length === 0) {
return this
}

let staticChild = this.staticChildren[path.charAt(0)]
if (staticChild) {
let i = 1
for (; i < staticChild.prefix.length; i++) {
if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
staticChild = staticChild.split(this, i)
break
}
}
return staticChild.getStaticChild(path.slice(i))
}

return null
}

createStaticChild (path) {
if (path.length === 0) {
return this
Expand Down Expand Up @@ -75,6 +95,22 @@ class StaticNode extends ParentNode {
this._compilePrefixMatch()
}

getParametricChild (regex, staticSuffix, nodePath) {
const regexpSource = regex && regex.source

const parametricChild = this.parametricChildren.find(child => {
const childRegexSource = child.regex && child.regex.source
return childRegexSource === regexpSource
})

if (parametricChild) {
// parametricChild.nodePaths.add(nodePath)
return parametricChild
}

return null
}

createParametricChild (regex, staticSuffix, nodePath) {
const regexpSource = regex && regex.source

Expand Down Expand Up @@ -106,6 +142,14 @@ class StaticNode extends ParentNode {
return parametricChild
}

getWildcardChild () {
if (this.wildcardChild) {
return this.wildcardChild
}

return null
}

createWildcardChild () {
if (this.wildcardChild) {
return this.wildcardChild
Expand Down
157 changes: 157 additions & 0 deletions test/has.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
'use strict'

const t = require('tap')
const test = t.test
const FindMyWay = require('..')

test('has returns false if there is no routes', t => {
t.plan(1)

const findMyWay = FindMyWay()

const hastRoute = findMyWay.has({ method: 'GET', path: '/example' })
t.equal(hastRoute, false)
})

test('has returns true for a static route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/example', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/example' })
t.equal(hastRoute, true)
})

test('has returns false for a static route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/example', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/example1' })
t.equal(hastRoute, false)
})

test('has returns true for a parametric route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param' })
t.equal(hastRoute, true)
})

test('has returns false for a parametric route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/foo/:param', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/bar/:param' })
t.equal(hastRoute, false)
})

test('has returns true for a parametric route with static suffix', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param-static', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param-static' })
t.equal(hastRoute, true)
})

test('has returns false for a parametric route with static suffix', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param-static1', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param-static2' })
t.equal(hastRoute, false)
})

test('has returns true even if a param name different', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param1', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param2' })
t.equal(hastRoute, true)
})

test('has returns true for a multi-parametric route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param1-:param2', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param1-:param2' })
t.equal(hastRoute, true)
})

test('has returns false for a multi-parametric route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/foo/:param1-:param2/bar1', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/foo/:param1-:param2/bar2' })
t.equal(hastRoute, false)
})

test('has returns true for a regexp route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:param(^\\d+$)', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:param(^\\d+$)' })
t.equal(hastRoute, true)
})

test('has returns false for a regexp route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/:file(^\\S+).png', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/:file(^\\D+).png' })
t.equal(hastRoute, false)
})

test('has returns true for a wildcard route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/example/*', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/example/*' })
t.equal(hastRoute, true)
})

test('has returns false for a wildcard route', t => {
t.plan(1)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/foo1/*', () => {})

const hastRoute = findMyWay.has({ method: 'GET', path: '/foo2/*' })
t.equal(hastRoute, false)
})

0 comments on commit 33ae435

Please sign in to comment.