radix-router
Advanced tools
Comparing version 0.1.6 to 1.0.0
896
index.js
@@ -1,30 +0,28 @@ | ||
'use strict'; | ||
'use strict' | ||
/** | ||
* JS Radix Router implementation | ||
*/ | ||
var assert = require('assert') | ||
// node types | ||
var NORMAL_NODE = 0; | ||
var WILDCARD_NODE = 1; | ||
var PLACEHOLDER_NODE = 2; | ||
var NORMAL_NODE = 0 | ||
var WILDCARD_NODE = 1 | ||
var PLACEHOLDER_NODE = 2 | ||
// noop | ||
function noop() {} | ||
/** | ||
* Returns all children that match the prefix | ||
* | ||
* @param {Node} - the node to check children for | ||
* @param {prefix} - the prefix to match | ||
* @param { Node } - the node to check children for | ||
* @param { prefix } - the prefix to match | ||
*/ | ||
function _getAllPrefixChildren(node, str) { | ||
var nodes = []; | ||
var children = node.children; | ||
for (var i = 0; i < children.length; i++) { | ||
// only need to check for first char | ||
if (children[i].path[0] === str[0]) { | ||
nodes.push(children[i]); | ||
} | ||
function _getAllPrefixChildren (node, str) { | ||
var nodes = [] | ||
var children = node.children | ||
for (var i = 0; i < children.length; i++) { | ||
// only need to check for first char | ||
if (children[i].path[0] === str[0]) { | ||
nodes.push(children[i]) | ||
} | ||
return nodes; | ||
} | ||
return nodes | ||
} | ||
@@ -35,13 +33,13 @@ | ||
* | ||
* @param {Node} - the node to check children for | ||
* @param {prefix} - the prefix to match | ||
* @param { Node } - the node to check children for | ||
* @param { prefix } - the prefix to match | ||
*/ | ||
function _getChildNode(node, prefix) { | ||
var children = node.children; | ||
for (var i = 0; i < children.length; i++) { | ||
if (children[i].path === prefix) { | ||
return children[i]; | ||
} | ||
function _getChildNode (node, prefix) { | ||
var children = node.children | ||
for (var i = 0; i < children.length; i++) { | ||
if (children[i].path === prefix) { | ||
return children[i] | ||
} | ||
return null; | ||
} | ||
return null | ||
} | ||
@@ -52,22 +50,23 @@ | ||
* | ||
* @param {object <string, Node>} children - a dictionary of childNodes | ||
* @param {string} str - the string used to find the largest prefix with | ||
* @param { object <string, Node> } children - a dictionary of childNodes | ||
* @param { string } str - the string used to find the largest prefix with | ||
*/ | ||
function _getLargestPrefix(children, str) { | ||
var index = 0; | ||
for (var i = 0; i < children.length; i++) { | ||
var path = children[i].path; | ||
var totalIterations = Math.min(str.length, path.length); | ||
for (; index < totalIterations; index++) { | ||
if (str[index] !== path[index]) { | ||
break; | ||
} | ||
} | ||
if (index > 0) { | ||
break; | ||
} | ||
function _getLargestPrefix (children, str) { | ||
var index = 0 | ||
for (var i = 0; i < children.length; i++) { | ||
var path = children[i].path | ||
var totalIterations = Math.min(str.length, path.length) | ||
while (index < totalIterations) { | ||
if (str[index] !== path[index]) { | ||
break | ||
} | ||
index++ | ||
} | ||
if (index > 0) { | ||
break | ||
} | ||
} | ||
// largest prefix | ||
return str.slice(0, index); | ||
return str.slice(0, index) | ||
} | ||
@@ -78,93 +77,93 @@ | ||
* | ||
* @param {Node} node - the node to attempt to traverse | ||
* @param {string} str - the string used as the basis for traversal | ||
* @param {function} onExactMatch - the handler for exact matches | ||
* @param {function} onPartialMatch - the handler for partial matches | ||
* @param {function} onNoMatch - the handler for when no match is found | ||
* @param { Node } node - the node to attempt to traverse | ||
* @param { string } str - the string used as the basis for traversal | ||
* @param { function } onExactMatch - the handler for exact matches | ||
* @param { function } onPartialMatch - the handler for partial matches | ||
* @param { function } onNoMatch - the handler for when no match is found | ||
*/ | ||
function _traverse(options) { | ||
var node = options.node; | ||
var str = options.str; | ||
var onExactMatch = options.onExactMatch; | ||
var onPartialMatch = options.onPartialMatch; | ||
var onNoMatch = options.onNoMatch; | ||
var onPlaceholder = options.onPlaceholder; | ||
var data = options.data; | ||
function _traverse (options) { | ||
var node = options.node | ||
var str = options.str | ||
var onExactMatch = options.onExactMatch | ||
var onPartialMatch = options.onPartialMatch | ||
var onNoMatch = options.onNoMatch | ||
var onPlaceholder = options.onPlaceholder | ||
var data = options.data | ||
var children = node.children; | ||
var childNode; | ||
var children = node.children | ||
var childNode | ||
// check if a child is possibly a placeholder or a wildcard | ||
// if wildcard is found, use it as a backup if no result is found, | ||
// if placeholder is found, grab the data and traverse | ||
var wildcardNode = null; | ||
if (onPlaceholder) { | ||
for (var i = 0; i < children.length; i++) { | ||
childNode = children[i]; | ||
if (children[i].type === WILDCARD_NODE) { | ||
wildcardNode = childNode; | ||
} else if (children[i].type === PLACEHOLDER_NODE) { | ||
var key = childNode.path.slice(1); | ||
var slashIndex = str.indexOf('/'); | ||
// check if a child is possibly a placeholder or a wildcard | ||
// if wildcard is found, use it as a backup if no result is found, | ||
// if placeholder is found, grab the data and traverse | ||
var wildcardNode = null | ||
if (onPlaceholder) { | ||
for (var i = 0; i < children.length; i++) { | ||
childNode = children[i] | ||
if (children[i].type === WILDCARD_NODE) { | ||
wildcardNode = childNode | ||
} else if (children[i].type === PLACEHOLDER_NODE) { | ||
var key = childNode.path.slice(1) | ||
var slashIndex = str.indexOf('/') | ||
var param; | ||
if (slashIndex !== -1) { | ||
param = str.slice(0, slashIndex); | ||
} else { | ||
param = str; | ||
} | ||
var param | ||
if (slashIndex !== -1) { | ||
param = str.slice(0, slashIndex) | ||
} else { | ||
param = str | ||
} | ||
options.node = children[i]; | ||
options.str = str.slice(param.length); | ||
options.node = children[i] | ||
options.str = str.slice(param.length) | ||
return onPlaceholder({ | ||
key: key, | ||
param: param, | ||
data: data, | ||
options: options, | ||
childNode: childNode | ||
}); | ||
} | ||
} | ||
return onPlaceholder({ | ||
key: key, | ||
param: param, | ||
options: options, | ||
childNode: childNode | ||
}) | ||
} | ||
} | ||
} | ||
var prefix = _getLargestPrefix(children, str); | ||
var prefix = _getLargestPrefix(children, str) | ||
// no matches, return null | ||
if (prefix.length === 0) { | ||
return onNoMatch(options) || wildcardNode; | ||
} | ||
// no matches, return null | ||
if (prefix.length === 0) { | ||
return onNoMatch(options) || wildcardNode | ||
} | ||
// exact match with input string was found | ||
if (prefix.length === str.length) { | ||
return onExactMatch({ | ||
node: node, | ||
prefix: prefix, | ||
str: str, | ||
data: data | ||
}) || wildcardNode; | ||
} | ||
// exact match with input string was found | ||
if (prefix.length === str.length) { | ||
return onExactMatch({ | ||
node: node, | ||
prefix: prefix, | ||
str: str, | ||
data: data | ||
}) || wildcardNode | ||
} | ||
// get child | ||
childNode = _getChildNode(node, prefix); | ||
// child exists, continue traversing | ||
if (childNode) { | ||
options.node = childNode; | ||
options.str = str.slice(prefix.length); | ||
var result = _traverse(options); | ||
// if no result, return the wildcard node | ||
if (!result && wildcardNode) { | ||
return wildcardNode; | ||
} else { | ||
return result; | ||
} | ||
// get child | ||
childNode = _getChildNode(node, prefix) | ||
// child exists, continue traversing | ||
if (childNode) { | ||
options.node = childNode | ||
options.str = str.slice(prefix.length) | ||
var result = _traverse(options) | ||
// if no result, return the wildcard node | ||
if (!result && wildcardNode) { | ||
return wildcardNode | ||
} else { | ||
return result | ||
} | ||
} | ||
// partial match was found | ||
return onPartialMatch({ | ||
node: node, | ||
prefix: prefix, | ||
str: str, | ||
data: data | ||
}) || wildcardNode; | ||
// partial match was found | ||
return onPartialMatch({ | ||
node: node, | ||
prefix: prefix, | ||
str: str, | ||
data: data | ||
}) || wildcardNode | ||
} | ||
@@ -175,17 +174,14 @@ | ||
* | ||
* @param {Node} node - the node to attempt to traverse | ||
* @param {string} str - the string that is the base of the key | ||
* @param {object} map - the map to traverse the cobrowse event with | ||
* @param { Node } node - the node to attempt to traverse | ||
* @param { string } str - the string that is the base of the key | ||
* @param { object } map - the map to traverse the cobrowse event with | ||
*/ | ||
function _traverseDepths(node, str, array) { | ||
if (node.data) { | ||
array.push({ | ||
path: str, | ||
data: node.data | ||
}); | ||
} | ||
function _traverseDepths (node, str, array) { | ||
if (node.data) { | ||
array.push(node.data) | ||
} | ||
node.children.forEach(function(child) { | ||
_traverseDepths(child, str + child.path, array); | ||
}); | ||
node.children.forEach(function (child) { | ||
_traverseDepths(child, str + child.path, array) | ||
}) | ||
} | ||
@@ -196,72 +192,73 @@ | ||
*/ | ||
function _createNode(path, data) { | ||
var node; | ||
if (path[0] === ':') { | ||
node = new Node(path, data, PLACEHOLDER_NODE); | ||
} else if (path === '**') { | ||
node = new Node(path, data, WILDCARD_NODE); | ||
} else { | ||
// normal string to match | ||
node = new Node(path, data); | ||
} | ||
return node; | ||
function _createNode (path, data) { | ||
var node | ||
if (path[0] === ':') { | ||
node = new Node(path, data, PLACEHOLDER_NODE) | ||
} else if (path === '**') { | ||
node = new Node(path, data, WILDCARD_NODE) | ||
} else { | ||
// normal string to match | ||
node = new Node(path, data) | ||
} | ||
return node | ||
} | ||
function _buildNodeChain(str, data) { | ||
var parentNode; | ||
var currentNode; | ||
var startingPoint = 0; | ||
function _buildNodeChain (str, data) { | ||
var parentNode | ||
var currentNode | ||
var startingPoint = 0 | ||
// if the string is just a single slash, return the node | ||
// otherwise just slash the node | ||
if (str.length === 0 || str === '/') { | ||
return new Node('/', data); | ||
} | ||
// if the string is just a single slash, return the node | ||
// otherwise just slash the node | ||
if (str.length === 0 || str === '/') { | ||
return new Node('/', data) | ||
} | ||
var sections = str.split('/'); | ||
// first section is a special case, if it has real content, create a node | ||
// otherwise, create an empty node | ||
if (sections[startingPoint].length > 0) { | ||
parentNode = currentNode = _createNode(sections[startingPoint]); | ||
} else { | ||
parentNode = currentNode = new Node(''); | ||
} | ||
startingPoint++; | ||
var sections = str.split('/') | ||
for (var i = startingPoint; i < sections.length; i++) { | ||
var parseRemaining = true; | ||
var newNode; | ||
// first section is a special case, if it has real content, create a node | ||
// otherwise, create an empty node | ||
if (sections[startingPoint].length > 0) { | ||
parentNode = currentNode = _createNode(sections[startingPoint]) | ||
} else { | ||
parentNode = currentNode = new Node('') | ||
} | ||
startingPoint++ | ||
// add slash to last node if the last section was empty | ||
if (i > 0 && sections[i - 1].length === 0){ | ||
currentNode.path += '/'; | ||
} else if (sections[i].length === 0) { | ||
newNode = new Node('/'); | ||
parseRemaining = false; | ||
} else { | ||
var node = new Node('/'); | ||
currentNode.children.push(node); | ||
node.parent = currentNode; | ||
currentNode = node; | ||
} | ||
for (var i = startingPoint; i < sections.length; i++) { | ||
var parseRemaining = true | ||
var newNode | ||
if (parseRemaining) { | ||
var path = sections[i]; | ||
newNode = _createNode(path); | ||
} | ||
currentNode.children.push(newNode); | ||
newNode.parent = currentNode; | ||
currentNode = newNode; | ||
// add slash to last node if the last section was empty | ||
if (i > 0 && sections[i - 1].length === 0) { | ||
currentNode.path += '/' | ||
} else if (sections[i].length === 0) { | ||
newNode = new Node('/') | ||
parseRemaining = false | ||
} else { | ||
var node = new Node('/') | ||
currentNode.children.push(node) | ||
node.parent = currentNode | ||
currentNode = node | ||
} | ||
// if the last node's path is empty, remove it. | ||
if (currentNode.path === '') { | ||
currentNode.parent.children = []; | ||
currentNode.parent.data = data; | ||
} else { | ||
currentNode.data = data; | ||
if (parseRemaining) { | ||
var path = sections[i] | ||
newNode = _createNode(path) | ||
} | ||
return parentNode; | ||
currentNode.children.push(newNode) | ||
newNode.parent = currentNode | ||
currentNode = newNode | ||
} | ||
// if the last node's path is empty, remove it. | ||
if (currentNode.path === '') { | ||
currentNode.parent.children = [] | ||
currentNode.parent.data = data | ||
} else { | ||
currentNode.data = data | ||
} | ||
return parentNode | ||
} | ||
@@ -273,40 +270,40 @@ | ||
* | ||
* @param {Node} node - the node to split | ||
* @param {string} prefix - the largest prefix found | ||
* @param {string} str - the leftover parts of the input string | ||
* @param {object} data - the data to store in the new node | ||
* @param { Node } node - the node to split | ||
* @param { string } prefix - the largest prefix found | ||
* @param { string } str - the leftover parts of the input string | ||
* @param { object } data - the data to store in the new node | ||
*/ | ||
function _splitNode(node, prefix, str, data) { | ||
var originalNode; | ||
var oldIndex; | ||
function _splitNode (node, prefix, str, data) { | ||
var originalNode | ||
var oldIndex | ||
var children = node.children; | ||
for (var i = 0; i < children.length; i++) { | ||
if (children[i].path.startsWith(prefix)) { | ||
originalNode = children[i]; | ||
oldIndex = i; | ||
break; | ||
} | ||
var children = node.children | ||
for (var i = 0; i < children.length; i++) { | ||
if (children[i].path.startsWith(prefix)) { | ||
originalNode = children[i] | ||
oldIndex = i | ||
break | ||
} | ||
} | ||
var newLink = str.substring(prefix.length); | ||
var oldLink = originalNode.path.substring(prefix.length); | ||
var newLink = str.substring(prefix.length) | ||
var oldLink = originalNode.path.substring(prefix.length) | ||
// set new path | ||
originalNode.path = oldLink; | ||
var newNode = _buildNodeChain(newLink, data); | ||
var intermediateNode = new Node(prefix); | ||
// set new path | ||
originalNode.path = oldLink | ||
var newNode = _buildNodeChain(newLink, data) | ||
var intermediateNode = new Node(prefix) | ||
originalNode.parent = intermediateNode; | ||
newNode.parent = intermediateNode; | ||
intermediateNode.parent = node; | ||
originalNode.parent = intermediateNode | ||
newNode.parent = intermediateNode | ||
intermediateNode.parent = node | ||
intermediateNode.children.push(originalNode); | ||
intermediateNode.children.push(newNode); | ||
intermediateNode.children.push(originalNode) | ||
intermediateNode.children.push(newNode) | ||
node.children.push(intermediateNode); | ||
node.children.push(intermediateNode) | ||
// remove old node the list of children | ||
node.children.splice(oldIndex, 1); | ||
return newNode; | ||
// remove old node the list of children | ||
node.children.splice(oldIndex, 1) | ||
return newNode | ||
} | ||
@@ -316,110 +313,130 @@ | ||
var EXACT_MATCH_HANDLERS = { | ||
'insert': function(options) { | ||
var node = options.node; | ||
var prefix = options.prefix; | ||
var str = options.str; | ||
var data = options.data; | ||
var childNode = _getChildNode(node, prefix); | ||
childNode.data = data; | ||
return node; | ||
}, | ||
'delete': function(options) { | ||
var parentNode = options.node; | ||
var prefix = options.prefix; | ||
var childNode = _getChildNode(parentNode, prefix); | ||
if (childNode.children.length === 0) { | ||
// delete node from parent | ||
for (var i = 0; i < parentNode.children.length; i++) { | ||
if (parentNode.children[i].path === prefix) { | ||
break; | ||
} | ||
} | ||
parentNode.children.splice(i, 1); | ||
} else { | ||
childNode.data = null; | ||
'insert': function (options) { | ||
var node = options.node | ||
var prefix = options.prefix | ||
var data = options.data | ||
var childNode = _getChildNode(node, prefix) | ||
childNode.data = data | ||
return node | ||
}, | ||
'remove': function (options) { | ||
var parentNode = options.node | ||
var prefix = options.prefix | ||
var result = options.data | ||
var childNode = _getChildNode(parentNode, prefix) | ||
if (childNode.data) { | ||
result.success = true | ||
} | ||
if (childNode.children.length === 0) { | ||
// remove node from parent | ||
for (var i = 0; i < parentNode.children.length; i++) { | ||
if (parentNode.children[i].path === prefix) { | ||
break | ||
} | ||
return childNode; | ||
}, | ||
'lookup': function(options) { | ||
var node = options.node; | ||
var prefix = options.prefix; | ||
var discoveredNode = _getChildNode(node, prefix); | ||
return discoveredNode; | ||
}, | ||
'startsWith': function(options) { | ||
var node = options.node; | ||
var prefix = options.prefix; | ||
var str = options.str; | ||
var childNode = _getChildNode(node, prefix); | ||
if (childNode) { | ||
return childNode; | ||
} | ||
parentNode.children.splice(i, 1) | ||
if (parentNode.children.length === 1) { | ||
var lastChildNode = parentNode.children[0] | ||
if (parentNode.data && Object.keys(parentNode.data).length === 1) { | ||
// no real data is associated with the parent | ||
if (parentNode.type === NORMAL_NODE && parentNode.path !== '/' && | ||
lastChildNode.type === NORMAL_NODE && lastChildNode.path !== '/') { | ||
// child node just a regular node, merge them together | ||
parentNode.children = [] | ||
parentNode.path += lastChildNode.path | ||
parentNode.data = lastChildNode.data | ||
parentNode.data.path = parentNode.path | ||
} | ||
} | ||
return _getAllPrefixChildren(node, prefix); | ||
} | ||
} else { | ||
childNode.data = null | ||
} | ||
}; | ||
return childNode | ||
}, | ||
'lookup': function (options) { | ||
var node = options.node | ||
var prefix = options.prefix | ||
var discoveredNode = _getChildNode(node, prefix) | ||
return discoveredNode | ||
}, | ||
'startsWith': function (options) { | ||
var node = options.node | ||
var prefix = options.prefix | ||
var childNode = _getChildNode(node, prefix) | ||
if (childNode) { | ||
return childNode | ||
} | ||
return _getAllPrefixChildren(node, prefix) | ||
} | ||
} | ||
// handle situations where there is a partial match | ||
var PARTIAL_MATCH_HANDLERS = { | ||
'insert': function(options) { | ||
var node = options.node; | ||
var prefix = options.prefix; | ||
var str = options.str; | ||
var data = options.data; | ||
var newNode = _splitNode(node, prefix, str, data); | ||
return newNode; | ||
}, | ||
'delete': function() { | ||
return null; | ||
}, | ||
'lookup': function() { | ||
return null; | ||
}, | ||
'startsWith': function(options) { | ||
var node = options.node; | ||
var prefix = options.prefix; | ||
return _getAllPrefixChildren(node, prefix); | ||
} | ||
}; | ||
'insert': function (options) { | ||
var node = options.node | ||
var prefix = options.prefix | ||
var str = options.str | ||
var data = options.data | ||
var newNode = _splitNode(node, prefix, str, data) | ||
return newNode | ||
}, | ||
'remove': function () { | ||
return null | ||
}, | ||
'lookup': function () { | ||
return null | ||
}, | ||
'startsWith': function (options) { | ||
var node = options.node | ||
var prefix = options.prefix | ||
return _getAllPrefixChildren(node, prefix) | ||
} | ||
} | ||
// handle situtations where there is no match | ||
var NO_MATCH_HANDLERS = { | ||
'insert': function(options) { | ||
var parentNode = options.node; | ||
var prefix = options.prefix; | ||
var str = options.str; | ||
var data = options.data; | ||
var newNode = _buildNodeChain(str, data); | ||
parentNode.children.push(newNode); | ||
newNode.parent = parentNode; | ||
return newNode; | ||
}, | ||
'delete': function() { | ||
return null; | ||
}, | ||
'lookup': function() { | ||
return null; | ||
}, | ||
'startsWith': function() { | ||
return []; | ||
} | ||
}; | ||
'insert': function (options) { | ||
var parentNode = options.node | ||
var str = options.str | ||
var data = options.data | ||
var newNode = _buildNodeChain(str, data) | ||
parentNode.children.push(newNode) | ||
newNode.parent = parentNode | ||
return newNode | ||
}, | ||
'remove': function () { | ||
return null | ||
}, | ||
'lookup': function () { | ||
return null | ||
}, | ||
'startsWith': function () { | ||
return [] | ||
} | ||
} | ||
function _onPlaceholder(placeholderOptions) { | ||
var options = placeholderOptions.options; | ||
var childNode = options.node; | ||
var parentNode = options.node.parent; | ||
var str = options.str; | ||
var data = options.data; | ||
function _onPlaceholder (placeholderOptions) { | ||
var options = placeholderOptions.options | ||
var childNode = options.node | ||
var parentNode = options.node.parent | ||
var str = options.str | ||
var data = options.data | ||
// return the child node if there is nowhere else to go | ||
// otherwise, traverse to the child | ||
if (options.str.length === 0) { | ||
return options.onExactMatch({ | ||
node: parentNode, | ||
prefix: childNode.path, | ||
str: str, | ||
data: data | ||
}); | ||
} | ||
return _traverse(options); | ||
if (options.str.length === 0) { | ||
return options.onExactMatch({ | ||
node: parentNode, | ||
prefix: childNode.path, | ||
str: str, | ||
data: data | ||
}) | ||
} | ||
return _traverse(options) | ||
} | ||
@@ -429,26 +446,27 @@ | ||
var PLACEHOLDER_HANDLERS = { | ||
// lookup handles placeholders differently | ||
'lookup': function(placeholderOptions) { | ||
var key = placeholderOptions.key; | ||
var param = placeholderOptions.param; | ||
var options = placeholderOptions.options; | ||
var data = options.data; | ||
// lookup handles placeholders differently | ||
'lookup': function (placeholderOptions) { | ||
var key = placeholderOptions.key | ||
var param = placeholderOptions.param | ||
var options = placeholderOptions.options | ||
var data = options.data | ||
if (!data.params) { | ||
data.params = {}; | ||
} | ||
data.params[key] = param; | ||
if (!data.params) { | ||
data.params = {} | ||
} | ||
data.params[key] = param | ||
if (options.str.length === 0) { | ||
return options.node; | ||
} | ||
if (options.str.length === 0) { | ||
return options.node | ||
} | ||
return _traverse(options); | ||
}, | ||
// inserts shouldn't care about placeholders at all | ||
'insert': null, | ||
'delete': _onPlaceholder, | ||
'startsWith': _onPlaceholder | ||
}; | ||
return _traverse(options) | ||
}, | ||
// inserts shouldn't care about placeholders at all | ||
'insert': null, | ||
'remove': _onPlaceholder, | ||
'startsWith': _onPlaceholder | ||
} | ||
/** | ||
@@ -459,24 +477,22 @@ * Helper method for retrieving all needed action handlers | ||
*/ | ||
function _getHandlers(action) { | ||
return { | ||
onExactMatch: EXACT_MATCH_HANDLERS[action], | ||
onPartialMatch: PARTIAL_MATCH_HANDLERS[action], | ||
onNoMatch: NO_MATCH_HANDLERS[action], | ||
onPlaceholder: PLACEHOLDER_HANDLERS[action] | ||
}; | ||
function _getHandlers (action) { | ||
return { | ||
onExactMatch: EXACT_MATCH_HANDLERS[action], | ||
onPartialMatch: PARTIAL_MATCH_HANDLERS[action], | ||
onNoMatch: NO_MATCH_HANDLERS[action], | ||
onPlaceholder: PLACEHOLDER_HANDLERS[action] | ||
} | ||
} | ||
function _validateInput (input, strictPaths) { | ||
var path = input | ||
assert(path, '"path" must be provided') | ||
assert(typeof path === 'string', '"path" must be that of a string') | ||
function _validateInput(input, strictPaths) { | ||
var path = input; | ||
if (typeof path !== 'string') { | ||
throw new Error('Radix Tree input must be a string'); | ||
} | ||
// allow for trailing slashes to match by removing it | ||
// allow for trailing slashes to match by removing it | ||
if (!strictPaths && path.length > 1 && path[path.length - 1] === '/') { | ||
path = path.slice(0, path.length - 1) | ||
} | ||
if (!strictPaths && path.length > 1 && path[path.length - 1] === '/') { | ||
path = path.slice(0, path.length - 1); | ||
} | ||
return path; | ||
return path | ||
} | ||
@@ -487,18 +503,18 @@ | ||
* | ||
* @param {Node} rootNode - the node to start from | ||
* @param {string} action - the action to perform, this will be used to get handlers | ||
* @param {string} input - the string to use for traversal | ||
* @param {object} data - the object to store in the Radix Tree | ||
* @param { Node } rootNode - the node to start from | ||
* @param { string } action - the action to perform, this will be used to get handlers | ||
* @param { string } input - the string to use for traversal | ||
* @param { object } data - the object to store in the Radix Tree | ||
*/ | ||
function _startTraversal(rootNode, action, input, data) { | ||
var handlers = _getHandlers(action); | ||
return _traverse({ | ||
node: rootNode, | ||
str: input, | ||
onExactMatch: handlers.onExactMatch, | ||
onPartialMatch: handlers.onPartialMatch, | ||
onNoMatch: handlers.onNoMatch, | ||
onPlaceholder: handlers.onPlaceholder, | ||
data: data | ||
}); | ||
function _startTraversal (rootNode, action, input, data) { | ||
var handlers = _getHandlers(action) | ||
return _traverse({ | ||
node: rootNode, | ||
str: input, | ||
onExactMatch: handlers.onExactMatch, | ||
onPartialMatch: handlers.onPartialMatch, | ||
onNoMatch: handlers.onNoMatch, | ||
onPlaceholder: handlers.onPlaceholder, | ||
data: data | ||
}) | ||
} | ||
@@ -510,8 +526,8 @@ | ||
*/ | ||
function Node(path, data, type) { | ||
this.type = type || NORMAL_NODE; | ||
this.path = path; | ||
this.parent = undefined; | ||
this.children = []; | ||
this.data = data || null; | ||
function Node (path, data, type) { | ||
this.type = type || NORMAL_NODE | ||
this.path = path | ||
this.parent = undefined | ||
this.children = [] | ||
this.data = data || null | ||
} | ||
@@ -523,53 +539,95 @@ | ||
*/ | ||
function RadixRouter(options) { | ||
this._rootNode = new Node(); | ||
this._strictMode = !!options && options.strict; | ||
// TODO: handle routes passed in via options | ||
function RadixRouter (options) { | ||
var self = this | ||
self._rootNode = new Node() | ||
self._strictMode = options && options.strict | ||
// handle insertion of routes passed into constructor | ||
var routes = options && options.routes | ||
if (routes) { | ||
routes.forEach(function (route) { | ||
self.insert(route) | ||
}) | ||
} | ||
} | ||
RadixRouter.prototype = { | ||
lookup: function(input) { | ||
var self = this; | ||
var path = _validateInput(input, self._strictMode); | ||
var result = { | ||
path: input, | ||
data: null | ||
}; | ||
var node = _startTraversal(self._rootNode, 'lookup', path, result); | ||
result.data = node ? node.data : null; | ||
return result; | ||
}, | ||
/** | ||
* Perform lookup of given path in radix tree | ||
* @param { string } path - the path to search for | ||
* | ||
* @returns { object } The data that was originally inserted into the tree | ||
*/ | ||
lookup: function (path) { | ||
var self = this | ||
path = _validateInput(path, self._strictMode) | ||
var data = {} | ||
startsWith: function(prefix) { | ||
var self = this; | ||
_validateInput(prefix, self._strictMode); | ||
var result = _startTraversal(self._rootNode, 'startsWith', prefix); | ||
// find the node | ||
var node = _startTraversal(self._rootNode, 'lookup', path, data) | ||
var result = node && node.data | ||
var resultArray = []; | ||
if (result instanceof Node) { | ||
_traverseDepths(result, prefix, resultArray); | ||
} else { | ||
result.forEach(function(child) { | ||
_traverseDepths(child, | ||
prefix.substring(0, prefix.indexOf(child.path[0])) + child.path, | ||
resultArray); | ||
}); | ||
} | ||
return resultArray; | ||
}, | ||
if (result && data.params) { | ||
result.params = data.params | ||
} | ||
insert: function(input, data) { | ||
var self = this; | ||
var path = _validateInput(input, self._strictMode); | ||
return _startTraversal(self._rootNode, 'insert', path, data); | ||
}, | ||
return result | ||
}, | ||
delete: function(input) { | ||
var self = this; | ||
var path = _validateInput(input, self._strictMode); | ||
return _startTraversal(self._rootNode, 'delete', path); | ||
/** | ||
* Perform lookup of all paths that start with the given prefix | ||
* @param { string } prefix - the prefix to match | ||
* | ||
* @returns { object[] } An array of matches along with any data that | ||
* was originally passed in when inserted | ||
*/ | ||
startsWith: function (prefix) { | ||
var self = this | ||
_validateInput(prefix, self._strictMode) | ||
var result = _startTraversal(self._rootNode, 'startsWith', prefix) | ||
var resultArray = [] | ||
if (result instanceof Node) { | ||
_traverseDepths(result, prefix, resultArray) | ||
} else { | ||
result.forEach(function (child) { | ||
_traverseDepths(child, | ||
prefix.substring(0, prefix.indexOf(child.path[0])) + child.path, | ||
resultArray) | ||
}) | ||
} | ||
}; | ||
return resultArray | ||
}, | ||
module.exports = RadixRouter; | ||
/** | ||
* Perform an insert into the radix tree | ||
* @param { string } data.path - the prefix to match | ||
* | ||
* Note: any other params attached to the data object will | ||
* also be inserted as part of the node's data | ||
*/ | ||
insert: function (data) { | ||
var self = this | ||
var path = data.path | ||
path = _validateInput(path, self._strictMode) | ||
_startTraversal(self._rootNode, 'insert', path, data) | ||
}, | ||
/** | ||
* Perform a remove on the tree | ||
* @param { string } data.path - the route to match | ||
* | ||
* @returns { boolean } A boolean signifying if the remove was | ||
* successful or not | ||
*/ | ||
remove: function (path) { | ||
var self = this | ||
path = _validateInput(path, self._strictMode) | ||
var result = { success: false } | ||
_startTraversal(self._rootNode, 'remove', path, result) | ||
return result.success | ||
} | ||
} | ||
module.exports = RadixRouter |
@@ -6,2 +6,6 @@ { | ||
"coveralls": "^2.11.14", | ||
"eslint": "^3.15.0", | ||
"eslint-config-standard": "^6.2.1", | ||
"eslint-plugin-promise": "^3.4.1", | ||
"eslint-plugin-standard": "^2.0.1", | ||
"git-hooks": "^1.1.6", | ||
@@ -16,3 +20,3 @@ "istanbul": "^0.4.5", | ||
"name": "radix-router", | ||
"version": "0.1.6", | ||
"version": "1.0.0", | ||
"description": "Radix tree based router", | ||
@@ -29,4 +33,5 @@ "main": "index.js", | ||
"test-coverage": "istanbul cover _mocha --include-all-sources -- --ui bdd --reporter spec ./tests", | ||
"coveralls": "cat ./coverage/lcov.info | coveralls" | ||
"coveralls": "cat ./coverage/lcov.info | coveralls", | ||
"lint": "eslint ." | ||
} | ||
} |
193
README.md
@@ -10,12 +10,10 @@ # Radix Router | ||
### Installation | ||
``` | ||
```bash | ||
npm install --save radix-router | ||
``` | ||
better yet | ||
``` | ||
yarn add radix-router | ||
``` | ||
### Usage | ||
#### Creating a new Router | ||
`new RadixRouter(options)` - Creates a new instance of a router. The `options` object is optional. | ||
@@ -25,81 +23,148 @@ | ||
- `routes` - The routes to insert into the router. | ||
- `strict` - Setting this option to `true` will force lookups to match exact paths (trailing slashes will not be ignored). Defaults to `false`. | ||
`insert(path, data)` - Adds the given path to the router and associates the given data with the path. | ||
```js | ||
const RadixRouter = require('radix-router') | ||
`lookup(path)` - Performs a lookup of the path. If there is a match, the data associated with the route is returned. | ||
const router = new RadixRouter({ | ||
strict: true, | ||
routes: [ | ||
{ | ||
path: '/my/api/route/a', // "path" is a required field | ||
// any other fields will also be stored by the router | ||
extraRouteData: {}, | ||
description: 'this is a route' | ||
}, | ||
{ | ||
path: '/my/api/route/b', | ||
extraRouteData: {}, | ||
description: 'this is a different route', | ||
routeBSpecificData: {} | ||
} | ||
] | ||
}) | ||
``` | ||
`delete(path)` - Deletes the path from the router. | ||
#### Router methods | ||
`startsWith(prefix)` - Returns a map of all routes starting with the given prefix and the data associated with them. | ||
##### `insert(routeData)` | ||
### Example | ||
Adds the given data to the router. The object passed in must contain a `path` attribute that is a string. | ||
The `path` will be used by the router to know where to place the route. | ||
Example input: | ||
```js | ||
router.insert({ | ||
path: '/api/route/c', // required | ||
// any additional data goes here | ||
extraData: 'anything can be added', | ||
handler: function (req, res) { | ||
// ... | ||
} | ||
}) | ||
``` | ||
const RadixRouter = require('radix-router'); | ||
let router = new RadixRouter({ | ||
strict: true | ||
}); | ||
##### `lookup(path)` | ||
router.insert('/api/v1/route', { | ||
much: 'data' | ||
}); | ||
Performs a lookup of the path. If there is a match, the data associated with the | ||
route is returned, otherwise this will return `null`. | ||
router.insert('/api/v2/**', { | ||
such: 'wildcard' | ||
}); | ||
Usage: | ||
router.insert('/api/v1/other-route/:id', { | ||
```js | ||
const routeThatExists = router.lookup('/api/route/c') | ||
``` | ||
Example output: | ||
```js | ||
{ | ||
path: '/api/route/c', | ||
extraData: 'anything can be added', | ||
handler: function (req, res) { | ||
// ... | ||
} | ||
} | ||
``` | ||
##### `remove(path)` | ||
Removes the path from the router. Returns `true` if the route was found and removed. | ||
Usage: | ||
``` | ||
const routeRemoved = router.remove('/some/route') | ||
``` | ||
##### `startsWith(path)` | ||
Returns a map of all routes starting with the given prefix and the data associated with them. | ||
Usage: | ||
``` | ||
const apiRoutes = router.startsWith('/api') | ||
``` | ||
Example output: | ||
```js | ||
[ | ||
{ | ||
path:'/api/v1/route', | ||
much: 'data' | ||
}, | ||
{ | ||
path: '/api/v1/other-route/:id', | ||
so: 'placeholder', | ||
much: 'wow' | ||
}); | ||
} | ||
] | ||
``` | ||
router.lookup('/api/v1/route'); | ||
// returns { | ||
// path: '/api/v1/route', | ||
// data: { | ||
// much: 'data' | ||
// } | ||
// } | ||
### Wildcard and placeholder matching | ||
router.lookup('/api/v2/anything/goes/here'); | ||
// returns { | ||
// path: '/api/v2/anything/goes/here', | ||
// data: { | ||
// such: 'wildcard' | ||
// } | ||
// } | ||
Wildcards can be added by to the end of routes by adding `/**` to the end of your route. | ||
router.lookup('/api/v1/other-route/abcd'); | ||
// returns { | ||
// path: '/api/v1/other-route/abcd', | ||
// data: { | ||
// so: 'placeholder', | ||
// much: 'wow' | ||
// }, | ||
// params: { | ||
// id: 'abcd' | ||
// } | ||
// } | ||
Example: | ||
// remove route | ||
router.delete('/api/v2/**'); | ||
```js | ||
router.insert( | ||
path: '/api/v2/**', | ||
such: 'wildcard' | ||
}) | ||
``` | ||
router.lookup('/api/v2/anything/goes/here'); | ||
// returns { | ||
// path: '/api/v2/anything/goes/here', | ||
// data: null | ||
// } | ||
Output of `router.lookup('/api/v2/some/random/route')`: | ||
```js | ||
{ | ||
path: '/api/v2/**', | ||
sucn: 'wildcard' | ||
} | ||
``` | ||
route.startsWith('/api') | ||
// returns { | ||
// '/api/v1/route': { | ||
// much: 'data' | ||
// }, | ||
// '/api/v1/other-route/:id': { | ||
// so: 'placeholder', | ||
// much: 'wow' | ||
// } | ||
// } | ||
Placeholders can be used in routes by starting a segment of the route with a colon `:`. Whatever | ||
content fills the position of the placeholder will be added to the lookup result | ||
under the `params` attribute. | ||
Example: | ||
```js | ||
router.insert( | ||
path: '/api/v2/:myPlaceholder/route', | ||
very: 'placeholder' | ||
}) | ||
``` | ||
Output of `router.lookup('/api/v2/application/route')`: | ||
```js | ||
{ | ||
path: '/api/v2/:myPlaceholder/route', | ||
very: 'placeholder', | ||
params: { | ||
myPlaceholder: 'application' | ||
} | ||
} | ||
``` |
@@ -1,173 +0,164 @@ | ||
const {expect} = require('chai'); | ||
const RadixRouter = require('../index'); | ||
var expect = require('chai').expect | ||
var RadixRouter = require('../index') | ||
var _putRoute = require('./util/putRoute') | ||
describe('Router lookup', () => { | ||
it('should be able lookup static routes', () => { | ||
let router = new RadixRouter(); | ||
describe('Router lookup', function () { | ||
it('should be able lookup static routes', function () { | ||
var router = new RadixRouter() | ||
router.insert('/', true); | ||
router.insert('/route', true); | ||
router.insert('/another-route', true); | ||
router.insert('/this/is/yet/another/route', true); | ||
_putRoute(router, '/', true) | ||
_putRoute(router, '/route', true) | ||
_putRoute(router, '/another-route', true) | ||
_putRoute(router, '/this/is/yet/another/route', true) | ||
expect(router.lookup('/')).to.deep.equal({ | ||
path: '/', | ||
data: true | ||
}); | ||
expect(router.lookup('/')).to.deep.equal({ | ||
path: '/', | ||
data: true | ||
}) | ||
expect(router.lookup('/route')).to.deep.equal({ | ||
path: '/route', | ||
data: true | ||
}); | ||
expect(router.lookup('/another-route')).to.deep.equal({ | ||
path: '/another-route', | ||
data: true | ||
}); | ||
expect(router.lookup('/this/is/yet/another/route')).to.deep.equal({ | ||
path: '/this/is/yet/another/route', | ||
data: true | ||
}); | ||
}); | ||
expect(router.lookup('/route')).to.deep.equal({ | ||
path: '/route', | ||
data: true | ||
}) | ||
expect(router.lookup('/another-route')).to.deep.equal({ | ||
path: '/another-route', | ||
data: true | ||
}) | ||
expect(router.lookup('/this/is/yet/another/route')).to.deep.equal({ | ||
path: '/this/is/yet/another/route', | ||
data: true | ||
}) | ||
}) | ||
it('should be able to retrieve placeholders', () => { | ||
let router = new RadixRouter(); | ||
router.insert('carbon/:element', 14); | ||
router.insert('carbon/:element/test/:testing', 15); | ||
router.insert('this/:route/has/:cool/stuff', 16); | ||
it('should be able to retrieve placeholders', function () { | ||
var router = new RadixRouter() | ||
_putRoute(router, 'carbon/:element', 14) | ||
_putRoute(router, 'carbon/:element/test/:testing', 15) | ||
_putRoute(router, 'this/:route/has/:cool/stuff', 16) | ||
expect(router.lookup('carbon/test1')).to.deep.equal({ | ||
path: 'carbon/test1', | ||
data: 14, | ||
params: { | ||
'element': 'test1' | ||
} | ||
}); | ||
expect(router.lookup('carbon/test1')).to.deep.equal({ | ||
path: 'carbon/test1', | ||
data: 14, | ||
params: { | ||
'element': 'test1' | ||
} | ||
}); | ||
expect(router.lookup('carbon/test2/test/test23')).to.deep.equal({ | ||
path: 'carbon/test2/test/test23', | ||
data: 15, | ||
params: { | ||
'element': 'test2', | ||
'testing': 'test23' | ||
} | ||
}); | ||
expect(router.lookup('this/test/has/more/stuff')).to.deep.equal({ | ||
path: 'this/test/has/more/stuff', | ||
data: 16, | ||
params: { | ||
route: 'test', | ||
cool: 'more' | ||
} | ||
}); | ||
}); | ||
expect(router.lookup('carbon/test1')).to.deep.equal({ | ||
path: 'carbon/:element', | ||
data: 14, | ||
params: { | ||
'element': 'test1' | ||
} | ||
}) | ||
expect(router.lookup('carbon/test1')).to.deep.equal({ | ||
path: 'carbon/:element', | ||
data: 14, | ||
params: { | ||
'element': 'test1' | ||
} | ||
}) | ||
expect(router.lookup('carbon/test2/test/test23')).to.deep.equal({ | ||
path: 'carbon/:element/test/:testing', | ||
data: 15, | ||
params: { | ||
'element': 'test2', | ||
'testing': 'test23' | ||
} | ||
}) | ||
expect(router.lookup('this/test/has/more/stuff')).to.deep.equal({ | ||
path: 'this/:route/has/:cool/stuff', | ||
data: 16, | ||
params: { | ||
route: 'test', | ||
cool: 'more' | ||
} | ||
}) | ||
}) | ||
it('should be able to perform wildcard lookups', () => { | ||
let router = new RadixRouter(); | ||
it('should be able to perform wildcard lookups', function () { | ||
var router = new RadixRouter() | ||
router.insert('polymer/**', 12); | ||
router.insert('polymer/another/route', 13); | ||
_putRoute(router, 'polymer/**', 12) | ||
_putRoute(router, 'polymer/another/route', 13) | ||
expect(router.lookup('polymer/another/route')).to.deep.equal({ | ||
path: 'polymer/another/route', | ||
data: 13 | ||
}); | ||
expect(router.lookup('polymer/another/route')).to.deep.equal({ | ||
path: 'polymer/another/route', | ||
data: 13 | ||
}) | ||
expect(router.lookup('polymer/anon')).to.deep.equal({ | ||
path: 'polymer/anon', | ||
data: 12 | ||
}); | ||
expect(router.lookup('polymer/anon')).to.deep.equal({ | ||
path: 'polymer/**', | ||
data: 12 | ||
}) | ||
expect(router.lookup('polymer/2415')).to.deep.equal({ | ||
path: 'polymer/2415', | ||
data: 12 | ||
}); | ||
expect(router.lookup('polymer/2415')).to.deep.equal({ | ||
path: 'polymer/**', | ||
data: 12 | ||
}) | ||
}) | ||
}); | ||
it('should be able to match routes with trailing slash', function () { | ||
var router = new RadixRouter() | ||
it('should be able to match routes with trailing slash', () => { | ||
let router = new RadixRouter(); | ||
_putRoute(router, 'route/without/trailing/slash', true) | ||
_putRoute(router, 'route/with/trailing/slash/', true) | ||
router.insert('route/without/trailing/slash', true); | ||
router.insert('route/with/trailing/slash/', true); | ||
expect(router.lookup('route/without/trailing/slash')).to.deep.equal({ | ||
path: 'route/without/trailing/slash', | ||
data: true | ||
}) | ||
expect(router.lookup('route/without/trailing/slash')).to.deep.equal({ | ||
path: 'route/without/trailing/slash', | ||
data: true | ||
}); | ||
expect(router.lookup('route/without/trailing/slash/')).to.deep.equal({ | ||
path: 'route/without/trailing/slash', | ||
data: true | ||
}) | ||
expect(router.lookup('route/without/trailing/slash/')).to.deep.equal({ | ||
path: 'route/without/trailing/slash/', | ||
data: true | ||
}); | ||
expect(router.lookup('route/with/trailing/slash')).to.deep.equal({ | ||
path: 'route/with/trailing/slash/', | ||
data: true | ||
}) | ||
expect(router.lookup('route/with/trailing/slash')).to.deep.equal({ | ||
path: 'route/with/trailing/slash', | ||
data: true | ||
}); | ||
expect(router.lookup('route/with/trailing/slash/')).to.deep.equal({ | ||
path: 'route/with/trailing/slash/', | ||
data: true | ||
}) | ||
}) | ||
expect(router.lookup('route/with/trailing/slash/')).to.deep.equal({ | ||
path: 'route/with/trailing/slash/', | ||
data: true | ||
}); | ||
it('should not match routes with trailing slash if router is created with strict mode', function () { | ||
var router = new RadixRouter({ | ||
strict: true | ||
}) | ||
}); | ||
_putRoute(router, '/', 1) | ||
_putRoute(router, '//', 2) | ||
_putRoute(router, '///', 3) | ||
_putRoute(router, '////', 4) | ||
_putRoute(router, 'route/without/trailing/slash', true) | ||
_putRoute(router, 'route/with/trailing/slash/', true) | ||
it('should not match routes with trailing slash if router is created with strict mode', () => { | ||
let router = new RadixRouter({ | ||
strict: true | ||
}); | ||
expect(router.lookup('/')).to.deep.equal({ | ||
path: '/', | ||
data: 1 | ||
}) | ||
router.insert('/', 1); | ||
router.insert('//', 2); | ||
router.insert('///', 3); | ||
router.insert('////', 4); | ||
router.insert('route/without/trailing/slash', true); | ||
router.insert('route/with/trailing/slash/', true); | ||
expect(router.lookup('//')).to.deep.equal({ | ||
path: '//', | ||
data: 2 | ||
}) | ||
expect(router.lookup('/')).to.deep.equal({ | ||
path: '/', | ||
data: 1 | ||
}); | ||
expect(router.lookup('///')).to.deep.equal({ | ||
path: '///', | ||
data: 3 | ||
}) | ||
expect(router.lookup('//')).to.deep.equal({ | ||
path: '//', | ||
data: 2 | ||
}); | ||
expect(router.lookup('////')).to.deep.equal({ | ||
path: '////', | ||
data: 4 | ||
}) | ||
expect(router.lookup('///')).to.deep.equal({ | ||
path: '///', | ||
data: 3 | ||
}); | ||
expect(router.lookup('route/without/trailing/slash')).to.deep.equal({ | ||
path: 'route/without/trailing/slash', | ||
data: true | ||
}) | ||
expect(router.lookup('////')).to.deep.equal({ | ||
path: '////', | ||
data: 4 | ||
}); | ||
expect(router.lookup('route/without/trailing/slash')).to.deep.equal({ | ||
path: 'route/without/trailing/slash', | ||
data: true | ||
}); | ||
expect(router.lookup('route/without/trailing/slash/')).to.deep.equal({ | ||
path: 'route/without/trailing/slash/', | ||
data: null | ||
}); | ||
expect(router.lookup('route/with/trailing/slash')).to.deep.equal({ | ||
path: 'route/with/trailing/slash', | ||
data: null | ||
}); | ||
expect(router.lookup('route/with/trailing/slash/')).to.deep.equal({ | ||
path: 'route/with/trailing/slash/', | ||
data: true | ||
}); | ||
}); | ||
}); | ||
expect(router.lookup('route/without/trailing/slash/')).to.deep.equal(null) | ||
expect(router.lookup('route/with/trailing/slash')).to.deep.equal(null) | ||
expect(router.lookup('route/with/trailing/slash/')).to.deep.equal({ | ||
path: 'route/with/trailing/slash/', | ||
data: true | ||
}) | ||
}) | ||
}) |
@@ -1,33 +0,35 @@ | ||
const {expect} = require('chai'); | ||
const RadixRouter = require('../index'); | ||
var expect = require('chai').expect | ||
var RadixRouter = require('../index') | ||
var _putRoute = require('./util/putRoute') | ||
function containsPath(array, path) { | ||
for (let i = 0; i < array.length; i++) { | ||
if (array[i].path === path) { | ||
return true; | ||
} | ||
function containsPath (array, path, data) { | ||
for (var i = 0; i < array.length; i++) { | ||
if (array[i].path === path && array[i].data === data) { | ||
return true | ||
} | ||
return false; | ||
} | ||
return false | ||
} | ||
describe('Router startsWith', () => { | ||
it('should be able retrieve all results via prefix', () => { | ||
let router = new RadixRouter(); | ||
router.insert('hello', 1); | ||
router.insert('hi', 2); | ||
router.insert('helium', 3); | ||
router.insert('chrome', 6); | ||
router.insert('choot', 7); | ||
router.insert('chromium', 8); | ||
let setA = router.startsWith('h'); | ||
expect(setA.length).to.equal(3); | ||
expect(containsPath(setA, 'hello')).to.equal(true); | ||
expect(containsPath(setA, 'hi')).to.equal(true); | ||
expect(containsPath(setA, 'helium')).to.equal(true); | ||
describe('Router startsWith', function () { | ||
it('should be able retrieve all results via prefix', function () { | ||
var router = new RadixRouter() | ||
_putRoute(router, 'hello', 1) | ||
_putRoute(router, 'hi', 2) | ||
_putRoute(router, 'helium', 3) | ||
_putRoute(router, 'chrome', 6) | ||
_putRoute(router, 'choot', 7) | ||
_putRoute(router, 'chromium', 8) | ||
let setB = router.startsWith('c'); | ||
expect(containsPath(setB, 'chrome')).to.equal(true); | ||
expect(containsPath(setB, 'choot')).to.equal(true); | ||
expect(containsPath(setB, 'chromium')).to.equal(true); | ||
}); | ||
}); | ||
var setA = router.startsWith('h') | ||
expect(setA.length).to.equal(3) | ||
expect(containsPath(setA, 'hello', 1)).to.equal(true) | ||
expect(containsPath(setA, 'hi', 2)).to.equal(true) | ||
expect(containsPath(setA, 'helium', 3)).to.equal(true) | ||
var setB = router.startsWith('c') | ||
expect(containsPath(setB, 'chrome', 6)).to.equal(true) | ||
expect(containsPath(setB, 'choot', 7)).to.equal(true) | ||
expect(containsPath(setB, 'chromium', 8)).to.equal(true) | ||
}) | ||
}) |
@@ -1,76 +0,193 @@ | ||
const {expect} = require('chai'); | ||
var expect = require('chai').expect | ||
var RadixRouter = require('../index') | ||
var _putRoute = require('./util/putRoute') | ||
let RadixRouter = require('../index'); | ||
function getChild(node, prefix) { | ||
for (var i = 0; i < node.children.length; i++) { | ||
if (node.children[i].path === prefix) { | ||
return node.children[i]; | ||
} | ||
function _getChild (node, prefix) { | ||
for (var i = 0; i < node.children.length; i++) { | ||
if (node.children[i].path === prefix) { | ||
return node.children[i] | ||
} | ||
return null; | ||
} | ||
return null | ||
} | ||
const WILDCARD_TYPE = 1; | ||
const PLACEHOLDER_TYPE = 2; | ||
var WILDCARD_TYPE = 1 | ||
var PLACEHOLDER_TYPE = 2 | ||
describe('Router tree structure', () => { | ||
it('should be able to insert nodes correctly into the tree', () => { | ||
let router = new RadixRouter(); | ||
router.insert('hello'); | ||
router.insert('cool'); | ||
router.insert('hi'); | ||
router.insert('helium'); | ||
router.insert('coooool'); | ||
router.insert('chrome'); | ||
router.insert('choot'); | ||
/** | ||
* Expected structure: | ||
* root | ||
* / \ | ||
* h c | ||
* / \ / \ | ||
* i el oo h | ||
* / \ / \ / \ | ||
* lo ium l oool rome oot | ||
*/ | ||
describe('Router tree structure', function () { | ||
it('should be able to insert nodes correctly into the tree', function () { | ||
var router = new RadixRouter() | ||
_putRoute(router, 'hello') | ||
_putRoute(router, 'cool') | ||
_putRoute(router, 'hi') | ||
_putRoute(router, 'helium') | ||
_putRoute(router, 'coooool') | ||
_putRoute(router, 'chrome') | ||
_putRoute(router, 'choot') | ||
_putRoute(router, '/choot') | ||
_putRoute(router, '//choot') | ||
/** | ||
* Expected structure: | ||
* root | ||
* / \ | ||
* h c | ||
* / \ / \ | ||
* i el oo h | ||
* / \ / \ / \ | ||
* lo ium l oool rome oot | ||
*/ | ||
let h_node = getChild(router._rootNode, 'h'); | ||
let i_node = getChild(h_node, 'i'); | ||
let el_node = getChild(h_node, 'el'); | ||
expect(h_node.children.length).to.equal(2); | ||
expect(i_node.path).to.not.equal(null); | ||
expect(el_node.children.length).to.equal(2); | ||
expect(getChild(el_node, 'lo').path).to.not.equal(null); | ||
expect(getChild(el_node, 'ium').path).to.not.equal(null); | ||
var hNode = _getChild(router._rootNode, 'h') | ||
var iNode = _getChild(hNode, 'i') | ||
var elNode = _getChild(hNode, 'el') | ||
expect(hNode.children.length).to.equal(2) | ||
expect(iNode.path).to.not.equal(null) | ||
expect(elNode.children.length).to.equal(2) | ||
expect(_getChild(elNode, 'lo').path).to.not.equal(null) | ||
expect(_getChild(elNode, 'ium').path).to.not.equal(null) | ||
let c_node = getChild(router._rootNode, 'c'); | ||
let oo_node = getChild(c_node, 'oo'); | ||
let h2_node = getChild(c_node, 'h'); | ||
expect(oo_node.children.length).to.equal(2); | ||
expect(h2_node.children.length).to.equal(2); | ||
expect(getChild(oo_node, 'l')).to.not.equal(null); | ||
expect(getChild(oo_node, 'oool')).to.not.equal(null); | ||
var cNode = _getChild(router._rootNode, 'c') | ||
var ooNode = _getChild(cNode, 'oo') | ||
var h2Node = _getChild(cNode, 'h') | ||
expect(ooNode.children.length).to.equal(2) | ||
expect(h2Node.children.length).to.equal(2) | ||
expect(_getChild(ooNode, 'l')).to.not.equal(null) | ||
expect(_getChild(ooNode, 'oool')).to.not.equal(null) | ||
expect(getChild(h2_node, 'rome')).to.not.equal(null); | ||
expect(getChild(h2_node, 'oot')).to.not.equal(null); | ||
}); | ||
expect(_getChild(h2Node, 'rome')).to.not.equal(null) | ||
expect(_getChild(h2Node, 'oot')).to.not.equal(null) | ||
}) | ||
it('insert placeholder and wildcard nodes correctly into the tree', () => { | ||
let router = new RadixRouter(); | ||
router.insert('hello/:placeholder/tree'); | ||
router.insert('choot/choo/**'); | ||
it('should insert placeholder and wildcard nodes correctly into the tree', function () { | ||
var router = new RadixRouter() | ||
_putRoute(router, 'hello/:placeholder/tree') | ||
_putRoute(router, 'choot/choo/**') | ||
let hello_node = getChild(router._rootNode, 'hello'); | ||
let hello_slash_node = getChild(hello_node, '/'); | ||
let hello_slash_placeholder_node = getChild(hello_slash_node, ':placeholder'); | ||
expect(hello_slash_placeholder_node.type).to.equal(PLACEHOLDER_TYPE); | ||
var helloNode = _getChild(router._rootNode, 'hello') | ||
var helloSlashNode = _getChild(helloNode, '/') | ||
var helloSlashPlaceholderNode = _getChild(helloSlashNode, ':placeholder') | ||
expect(helloSlashPlaceholderNode.type).to.equal(PLACEHOLDER_TYPE) | ||
let choot_node = getChild(router._rootNode, 'choot'); | ||
let choot_slash_node = getChild(choot_node, '/'); | ||
let choot_slash_choo_node = getChild(choot_slash_node, 'choo'); | ||
let choot_slash_choo_slash_node = getChild(choot_slash_choo_node, '/'); | ||
let choot_slash_choo_slash_wildcard_node = getChild(choot_slash_choo_slash_node, '**'); | ||
expect(choot_slash_choo_slash_wildcard_node.type).to.equal(WILDCARD_TYPE); | ||
}); | ||
}); | ||
var chootNode = _getChild(router._rootNode, 'choot') | ||
var chootSlashNode = _getChild(chootNode, '/') | ||
var chootSlashChooNode = _getChild(chootSlashNode, 'choo') | ||
var chootSlashChooSlashNode = _getChild(chootSlashChooNode, '/') | ||
var chootSlashChooSlashWildcardNode = _getChild(chootSlashChooSlashNode, '**') | ||
expect(chootSlashChooSlashWildcardNode.type).to.equal(WILDCARD_TYPE) | ||
}) | ||
it('should throw an error if a path is not supplied when inserting a route', function () { | ||
var router = new RadixRouter() | ||
var insert = router.insert.bind(router, { | ||
notAPath: 'this is not a path' | ||
}) | ||
expect(insert).to.throw(/"path" must be provided/) | ||
}) | ||
it('should be able to initialize routes via the router contructor', function () { | ||
var router = new RadixRouter({ | ||
routes: [ | ||
{ path: '/api/v1', value: 1 }, | ||
{ path: '/api/v2', value: 2 }, | ||
{ path: '/api/v3', value: 3 } | ||
] | ||
}) | ||
var rootSlashNode = _getChild(router._rootNode, '/') | ||
var apiNode = _getChild(rootSlashNode, 'api') | ||
var apiSlashNode = _getChild(apiNode, '/') | ||
var vNode = _getChild(apiSlashNode, 'v') | ||
var v1Node = _getChild(vNode, '1') | ||
var v2Node = _getChild(vNode, '2') | ||
var v3Node = _getChild(vNode, '3') | ||
expect(v1Node).to.exist | ||
expect(v2Node).to.exist | ||
expect(v3Node).to.exist | ||
expect(v1Node.data.value).to.equal(1) | ||
expect(v2Node.data.value).to.equal(2) | ||
expect(v3Node.data.value).to.equal(3) | ||
}) | ||
it('should throw an error if a path is not supplied when inserting a route via constructor', function () { | ||
function createRouter () { | ||
return new RadixRouter({ | ||
routes: [ | ||
{ path: '/api/v1' }, | ||
{ notAPath: '/api/v2' } | ||
] | ||
}) | ||
} | ||
expect(createRouter).to.throw(/"path" must be provided/) | ||
}) | ||
context('upon removal of route', function () { | ||
it('should merge childNodes left with no siblings with parent if parent contains no data', function () { | ||
var router = new RadixRouter() | ||
router.insert({ path: 'thisIsA' }) | ||
router.insert({ path: 'thisIsAnotherRoute', value: 1 }) | ||
router.insert({ path: 'thisIsAboutToGetDeleted' }) | ||
var baseNode = _getChild(router._rootNode, 'thisIsA') | ||
var anotherRouteNode = _getChild(baseNode, 'notherRoute') | ||
var aboutToGetDeletedNode = _getChild(baseNode, 'boutToGetDeleted') | ||
expect(anotherRouteNode).to.exist | ||
expect(aboutToGetDeletedNode).to.exist | ||
router.remove('thisIsAboutToGetDeleted') | ||
var newBaseNode = _getChild(router._rootNode, 'thisIsAnotherRoute') | ||
expect(newBaseNode).to.exist | ||
expect(newBaseNode.data.value).to.equal(1) | ||
expect(newBaseNode.data.path).to.equal('thisIsAnotherRoute') | ||
}) | ||
it('should NOT merge childNodes left with no siblings with parent if contains data', function () { | ||
var router = new RadixRouter() | ||
router.insert({ path: 'thisIsA', data: 1 }) | ||
router.insert({ path: 'thisIsAnotherRoute' }) | ||
router.insert({ path: 'thisIsAboutToGetDeleted' }) | ||
var baseNode = _getChild(router._rootNode, 'thisIsA') | ||
var anotherRouteNode = _getChild(baseNode, 'notherRoute') | ||
var aboutToGetDeletedNode = _getChild(baseNode, 'boutToGetDeleted') | ||
expect(anotherRouteNode).to.exist | ||
expect(aboutToGetDeletedNode).to.exist | ||
router.remove('thisIsAboutToGetDeleted') | ||
var newBaseNode = _getChild(router._rootNode, 'thisIsAnotherRoute') | ||
expect(newBaseNode).to.not.exist | ||
var originalBaseNode = _getChild(router._rootNode, 'thisIsA') | ||
expect(originalBaseNode).to.exist | ||
var originalAnotherRouteNode = _getChild(baseNode, 'notherRoute') | ||
expect(originalAnotherRouteNode).to.exist | ||
}) | ||
it('should merge childNodes with parent if parent is a slash separator', function () { | ||
var router = new RadixRouter() | ||
router.insert({ path: 'thisIsA/', data: 1 }) | ||
router.insert({ path: 'thisIsA/notherRoute' }) | ||
router.insert({ path: 'thisIsA/boutToGetDeleted' }) | ||
var baseNode = _getChild(router._rootNode, 'thisIsA') | ||
var slashNode = _getChild(baseNode, '/') | ||
var anotherRouteNode = _getChild(slashNode, 'notherRoute') | ||
var aboutToGetDeletedNode = _getChild(slashNode, 'boutToGetDeleted') | ||
expect(anotherRouteNode).to.exist | ||
expect(aboutToGetDeletedNode).to.exist | ||
router.remove('thisIsA/boutToGetDeleted') | ||
var originalBaseNode = _getChild(router._rootNode, 'thisIsA') | ||
expect(originalBaseNode).to.exist | ||
var originalAnotherRouteNode = _getChild(slashNode, 'notherRoute') | ||
expect(originalAnotherRouteNode).to.exist | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
81554
964
1
169
14
15
1