Skip to content

Commit

Permalink
Define insert method for each node type (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-tymoshenko authored Apr 15, 2022
1 parent b38003a commit 66fa039
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 109 deletions.
120 changes: 71 additions & 49 deletions custom_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,6 @@ Object.defineProperty(Node.prototype, 'types', {
value: types
})

Node.prototype.addChild = function (node) {
const label = node.prefix[0]
switch (node.kind) {
case this.types.STATIC:
assert(
this.staticChildren[label] === undefined,
`There is already a child with label '${label}'`
)
this.staticChildren[label] = node
break
case this.types.PARAM:
case this.types.REGEX:
assert(this.parametricChild === null, 'There is already a parametric child')
this.parametricChild = node
break
case this.types.MATCH_ALL:
assert(this.wildcardChild === null, 'There is already a wildcard child')
this.wildcardChild = node
break
default:
throw new Error(`Unknown node kind: ${node.kind}`)
}

this.numberOfChildren++

this._saveParametricBrother()
this._saveWildcardBrother()

return this
}

Node.prototype._saveParametricBrother = function () {
let parametricBrother = this.parametricBrother
if (this.parametricChild !== null) {
Expand All @@ -76,7 +45,7 @@ Node.prototype._saveParametricBrother = function () {
if (parametricBrother) {
for (const child of Object.values(this.staticChildren)) {
child.parametricBrother = parametricBrother
child._saveParametricBrother(parametricBrother)
child._saveParametricBrother()
}
}
}
Expand All @@ -92,7 +61,7 @@ Node.prototype._saveWildcardBrother = function () {
if (wildcardBrother) {
for (const child of Object.values(this.staticChildren)) {
child.wildcardBrother = wildcardBrother
child._saveWildcardBrother(wildcardBrother)
child._saveWildcardBrother()
}
if (this.parametricChild !== null) {
this.parametricChild.wildcardBrother = wildcardBrother
Expand All @@ -116,7 +85,7 @@ Node.prototype.reset = function (prefix) {
}

Node.prototype.split = function (length) {
const newChild = new Node(
const staticChild = new Node(
{
prefix: this.prefix.slice(length),
staticChildren: this.staticChildren,
Expand All @@ -131,32 +100,85 @@ Node.prototype.split = function (length) {
)

if (this.wildcardChild !== null) {
newChild.wildcardChild = this.wildcardChild
staticChild.wildcardChild = this.wildcardChild
}

if (this.parametricChild !== null) {
newChild.parametricChild = this.parametricChild
staticChild.parametricChild = this.parametricChild
}

this.reset(this.prefix.slice(0, length))
this.addChild(newChild)
return newChild

const label = staticChild.prefix.charAt(0)
this.staticChildren[label] = staticChild

this.numberOfChildren++

this._saveParametricBrother()
this._saveWildcardBrother()

return staticChild
}

Node.prototype.getChildByLabel = function (label, kind) {
if (label.length === 0) {
return null
Node.prototype.insertStaticNode = function (path) {
if (path.length === 0) {
return this
}

switch (kind) {
case this.types.STATIC:
return this.staticChildren[label]
case this.types.MATCH_ALL:
return this.wildcardChild
case this.types.PARAM:
case this.types.REGEX:
return this.parametricChild
let staticChild = this.staticChildren[path.charAt(0)]
if (staticChild) {
let i = 0
for (; i < staticChild.prefix.length; i++) {
if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
staticChild.split(i)
break
}
}
return staticChild.insertStaticNode(path.slice(i))
}

staticChild = new Node({ method: this.method, prefix: path, kind: types.STATIC, constrainer: this.constrainer })

const label = path.charAt(0)
this.staticChildren[label] = staticChild
this.numberOfChildren++

this._saveParametricBrother()
this._saveWildcardBrother()

return staticChild
}

Node.prototype.insertParametricNode = function (regex) {
if (this.parametricChild) {
return this.parametricChild
}

const kind = regex ? types.REGEX : types.PARAM
const parametricChild = new Node({ method: this.method, prefix: ':', kind, regex, constrainer: this.constrainer })

this.parametricChild = parametricChild
this.numberOfChildren++

this._saveParametricBrother()
this._saveWildcardBrother()

return parametricChild
}

Node.prototype.insertWildcardNode = function () {
if (this.wildcardChild) {
return this.wildcardChild
}

const wildcardChild = new Node({ method: this.method, prefix: '*', kind: types.MATCH_ALL, constrainer: this.constrainer })

this.wildcardChild = wildcardChild
this.numberOfChildren++

this._saveWildcardBrother()

return wildcardChild
}

Node.prototype.findStaticMatchingChild = function (path, pathIndex) {
Expand Down
80 changes: 26 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,29 +166,34 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {

const params = []
for (let i = 0; i <= path.length; i++) {
// search for parametric or wildcard routes
// parametric route
if (path.charCodeAt(i) === 58) {
if (path.charCodeAt(i + 1) === 58) {
// It's a double colon. Let's just replace it with a single colon and go ahead
path = path.slice(0, i) + path.slice(i + 1)
continue
}
if (path.charCodeAt(i) === 58 && path.charCodeAt(i + 1) === 58) {
// It's a double colon. Let's just replace it with a single colon and go ahead
path = path.slice(0, i) + path.slice(i + 1)
continue
}

// add the static part of the route to the tree
currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex, i), NODE_TYPES.STATIC, null)
const isParametricNode = path.charCodeAt(i) === 58
const isWildcardNode = path.charCodeAt(i) === 42

const paramStartIndex = i + 1
if (isParametricNode || isWildcardNode || (i === path.length && i !== parentNodePathIndex)) {
let staticNodePath = path.slice(parentNodePathIndex, i)
if (!this.caseSensitive) {
staticNodePath = staticNodePath.toLowerCase()
}
// add the static part of the route to the tree
currentNode = currentNode.insertStaticNode(staticNodePath)
}

if (isParametricNode) {
let isRegexNode = false
const regexps = []
let nodeType = NODE_TYPES.PARAM
let lastParamStartIndex = paramStartIndex

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

if (charCode === 40 || charCode === 45 || charCode === 46) {
nodeType = NODE_TYPES.REGEX
isRegexNode = true

const paramName = path.slice(lastParamStartIndex, j)
params.push(paramName)
Expand Down Expand Up @@ -233,63 +238,30 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
}

if (path.charCodeAt(j) === 47 || j === path.length) {
path = path.slice(0, paramStartIndex) + path.slice(j)
path = path.slice(0, i + 1) + path.slice(j)
break
}
}

let regex = null
if (nodeType === NODE_TYPES.REGEX) {
if (isRegexNode) {
regex = new RegExp('^' + regexps.join('') + '$')
}

currentNode = this._insert(currentNode, method, ':', nodeType, regex)
currentNode = currentNode.insertParametricNode(regex)
parentNodePathIndex = i + 1
// wildcard route
} else if (path.charCodeAt(i) === 42) {
currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex, i), NODE_TYPES.STATIC, null)
} else if (isWildcardNode) {
// add the wildcard parameter
params.push('*')
currentNode = this._insert(currentNode, method, path.slice(i), NODE_TYPES.MATCH_ALL, null)
break
} else if (i === path.length && i !== parentNodePathIndex) {
currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex), NODE_TYPES.STATIC, null)
currentNode = currentNode.insertWildcardNode()
parentNodePathIndex = i + 1
}
}

assert(!currentNode.getHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`)
currentNode.addHandler(handler, params, store, constraints)
}

Router.prototype._insert = function _insert (currentNode, method, path, kind, regex) {
if (!this.caseSensitive) {
path = path.toLowerCase()
}

let childNode = currentNode.getChildByLabel(path.charAt(0), kind)
while (childNode) {
currentNode = childNode

let i = 0
for (; i < currentNode.prefix.length; i++) {
if (path.charCodeAt(i) !== currentNode.prefix.charCodeAt(i)) {
currentNode.split(i)
break
}
}
path = path.slice(i)
childNode = currentNode.getChildByLabel(path.charAt(0), kind)
}

if (path.length > 0) {
const node = new Node({ method, prefix: path, kind, handlers: null, regex, constrainer: this.constrainer })
currentNode.addChild(node)
currentNode = node
}

return currentNode
}

Router.prototype.reset = function reset () {
this.trees = {}
this.routes = []
Expand Down
9 changes: 3 additions & 6 deletions test/issue-104.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,14 @@ test('Mixed parametric routes, with last defined route being static', t => {
test('parametricBrother of Parent Node, with a parametric child', t => {
t.plan(1)
const parent = new Node({ prefix: '/a' })
const parametricChild = new Node({ prefix: ':id', kind: parent.types.PARAM })
parent.addChild(parametricChild)
parent.insertParametricNode()
t.equal(parent.parametricBrother, null)
})

test('parametricBrother of Parent Node, with a parametric child and a static child', t => {
t.plan(1)
const parent = new Node({ prefix: '/a' })
const parametricChild = new Node({ prefix: ':id', kind: parent.types.PARAM })
const staticChild = new Node({ prefix: '/b', kind: parent.types.STATIC })
parent.addChild(parametricChild)
parent.addChild(staticChild)
parent.insertParametricNode()
parent.insertStaticNode('/b')
t.equal(parent.parametricBrother, null)
})

0 comments on commit 66fa039

Please sign in to comment.