find-my-way
Advanced tools
Comparing version 3.0.4 to 3.0.5
27
bench.js
@@ -20,3 +20,6 @@ 'use strict' | ||
findMyWay.on('GET', '/user/:id/static', () => true) | ||
findMyWay.on('POST', '/user/:id', () => true) | ||
findMyWay.on('PUT', '/user/:id', () => true) | ||
findMyWay.on('GET', '/customer/:name-:surname', () => true) | ||
findMyWay.on('POST', '/customer', () => true) | ||
findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', () => true) | ||
@@ -26,2 +29,20 @@ findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true) | ||
findMyWay.on('GET', '/products', () => true) | ||
findMyWay.on('GET', '/products/:id', () => true) | ||
findMyWay.on('GET', '/products/:id/options', () => true) | ||
findMyWay.on('GET', '/posts', () => true) | ||
findMyWay.on('POST', '/posts', () => true) | ||
findMyWay.on('GET', '/posts/:id', () => true) | ||
findMyWay.on('GET', '/posts/:id/author', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments', () => true) | ||
findMyWay.on('POST', '/posts/:id/comments', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments/:id', () => true) | ||
findMyWay.on('GET', '/posts/:id/comments/:id/author', () => true) | ||
findMyWay.on('GET', '/posts/:id/counter', () => true) | ||
findMyWay.on('GET', '/pages', () => true) | ||
findMyWay.on('POST', '/pages', () => true) | ||
findMyWay.on('GET', '/pages/:id', () => true) | ||
suite | ||
@@ -70,2 +91,8 @@ .add('lookup static route', function () { | ||
}) | ||
.add('find long nested dynamic route', function () { | ||
findMyWay.find('GET', '/posts/10/comments/42/author', undefined) | ||
}) | ||
.add('find long nested dynamic route with other method', function () { | ||
findMyWay.find('POST', '/posts/10/comments', undefined) | ||
}) | ||
.on('cycle', function (event) { | ||
@@ -72,0 +99,0 @@ console.log(String(event.target)) |
87
index.js
@@ -18,2 +18,3 @@ 'use strict' | ||
const isRegexSafe = require('safe-regex2') | ||
const { flattenNode, compressFlattenedNode, prettyPrintFlattenedNode } = require('./lib/pretty-print') | ||
const Node = require('./node') | ||
@@ -54,4 +55,4 @@ const NODE_TYPES = Node.prototype.types | ||
this.allowUnsafeRegex = opts.allowUnsafeRegex || false | ||
this.versioning = opts.versioning || acceptVersionStrategy | ||
this.tree = new Node({ versions: this.versioning.storage() }) | ||
this.versioning = opts.versioning || acceptVersionStrategy(false) | ||
this.trees = {} | ||
this.routes = [] | ||
@@ -115,2 +116,5 @@ } | ||
const version = opts.version | ||
if (version != null && this.versioning.disabled) { | ||
this.versioning = acceptVersionStrategy(true) | ||
} | ||
@@ -201,3 +205,2 @@ for (var i = 0, len = path.length; i < len; i++) { | ||
const route = path | ||
var currentNode = this.tree | ||
var prefix = '' | ||
@@ -210,2 +213,8 @@ var pathLen = 0 | ||
var currentNode = this.trees[method] | ||
if (typeof currentNode === 'undefined') { | ||
currentNode = new Node({ method: method, versions: this.versioning.storage() }) | ||
this.trees[method] = currentNode | ||
} | ||
while (true) { | ||
@@ -226,6 +235,7 @@ prefix = currentNode.prefix | ||
{ | ||
method: method, | ||
prefix: prefix.slice(len), | ||
children: currentNode.children, | ||
kind: currentNode.kind, | ||
handlers: new Node.Handlers(currentNode.handlers), | ||
handler: currentNode.handler, | ||
regex: currentNode.regex, | ||
@@ -248,7 +258,7 @@ versions: currentNode.versions | ||
if (version) { | ||
assert(!currentNode.getVersionHandler(version, method), `Method '${method}' already declared for route '${route}' version '${version}'`) | ||
currentNode.setVersionHandler(version, method, handler, params, store) | ||
assert(!currentNode.getVersionHandler(version), `Method '${method}' already declared for route '${route}' version '${version}'`) | ||
currentNode.setVersionHandler(version, handler, params, store) | ||
} else { | ||
assert(!currentNode.getHandler(method), `Method '${method}' already declared for route '${route}'`) | ||
currentNode.setHandler(method, handler, params, store) | ||
assert(!currentNode.handler, `Method '${method}' already declared for route '${route}'`) | ||
currentNode.setHandler(handler, params, store) | ||
} | ||
@@ -258,2 +268,3 @@ currentNode.kind = kind | ||
node = new Node({ | ||
method: method, | ||
prefix: path.slice(len), | ||
@@ -266,5 +277,5 @@ kind: kind, | ||
if (version) { | ||
node.setVersionHandler(version, method, handler, params, store) | ||
node.setVersionHandler(version, handler, params, store) | ||
} else { | ||
node.setHandler(method, handler, params, store) | ||
node.setHandler(handler, params, store) | ||
} | ||
@@ -287,7 +298,7 @@ currentNode.addChild(node) | ||
// there are not children within the given label, let's create a new one! | ||
node = new Node({ prefix: path, kind: kind, handlers: null, regex: regex, versions: this.versioning.storage() }) | ||
node = new Node({ method: method, prefix: path, kind: kind, regex: regex, versions: this.versioning.storage() }) | ||
if (version) { | ||
node.setVersionHandler(version, method, handler, params, store) | ||
node.setVersionHandler(version, handler, params, store) | ||
} else { | ||
node.setHandler(method, handler, params, store) | ||
node.setHandler(handler, params, store) | ||
} | ||
@@ -300,7 +311,7 @@ | ||
if (version) { | ||
assert(!currentNode.getVersionHandler(version, method), `Method '${method}' already declared for route '${route}' version '${version}'`) | ||
currentNode.setVersionHandler(version, method, handler, params, store) | ||
assert(!currentNode.getVersionHandler(version), `Method '${method}' already declared for route '${route}' version '${version}'`) | ||
currentNode.setVersionHandler(version, handler, params, store) | ||
} else { | ||
assert(!currentNode.getHandler(method), `Method '${method}' already declared for route '${route}'`) | ||
currentNode.setHandler(method, handler, params, store) | ||
assert(!currentNode.handler, `Method '${method}' already declared for route '${route}'`) | ||
currentNode.setHandler(handler, params, store) | ||
} | ||
@@ -313,3 +324,3 @@ } | ||
Router.prototype.reset = function reset () { | ||
this.tree = new Node({ versions: this.versioning.storage() }) | ||
this.trees = {} | ||
this.routes = [] | ||
@@ -373,2 +384,5 @@ } | ||
Router.prototype.find = function find (method, path, version) { | ||
var currentNode = this.trees[method] | ||
if (!currentNode) return null | ||
if (path.charCodeAt(0) !== 47) { // 47 is '/' | ||
@@ -386,3 +400,2 @@ path = path.replace(FULL_PATH_REGEXP, '/') | ||
var maxParamLength = this.maxParamLength | ||
var currentNode = this.tree | ||
var wildcardNode = null | ||
@@ -405,4 +418,4 @@ var pathLenWildcard = 0 | ||
var handle = version === undefined | ||
? currentNode.handlers[method] | ||
: currentNode.getVersionHandler(version, method) | ||
? currentNode.handler | ||
: currentNode.getVersionHandler(version) | ||
if (handle !== null && handle !== undefined) { | ||
@@ -437,4 +450,4 @@ var paramsObj = {} | ||
var node = version === undefined | ||
? currentNode.findChild(path, method) | ||
: currentNode.findVersionChild(version, path, method) | ||
? currentNode.findChild(path) | ||
: currentNode.findVersionChild(version, path) | ||
@@ -444,3 +457,3 @@ if (node === null) { | ||
if (node === null) { | ||
return this._getWildcardNode(wildcardNode, method, originalPath, pathLenWildcard) | ||
return this._getWildcardNode(wildcardNode, originalPath, pathLenWildcard) | ||
} | ||
@@ -468,3 +481,3 @@ | ||
// if exist, save the wildcard child | ||
if (currentNode.wildcardChild !== null && currentNode.wildcardChild.handlers[method] !== null) { | ||
if (currentNode.wildcardChild !== null) { | ||
wildcardNode = currentNode.wildcardChild | ||
@@ -478,7 +491,7 @@ pathLenWildcard = pathLen | ||
if (len !== prefixLen) { | ||
return this._getWildcardNode(wildcardNode, method, originalPath, pathLenWildcard) | ||
return this._getWildcardNode(wildcardNode, originalPath, pathLenWildcard) | ||
} | ||
// if exist, save the wildcard child | ||
if (currentNode.wildcardChild !== null && currentNode.wildcardChild.handlers[method] !== null) { | ||
if (currentNode.wildcardChild !== null) { | ||
wildcardNode = currentNode.wildcardChild | ||
@@ -567,3 +580,3 @@ pathLenWildcard = pathLen | ||
Router.prototype._getWildcardNode = function (node, method, path, len) { | ||
Router.prototype._getWildcardNode = function (node, path, len) { | ||
if (node === null) return null | ||
@@ -576,3 +589,3 @@ var decoded = fastDecode(path.slice(-len)) | ||
} | ||
var handle = node.handlers[method] | ||
var handle = node.handler | ||
if (handle !== null && handle !== undefined) { | ||
@@ -609,3 +622,17 @@ return { | ||
Router.prototype.prettyPrint = function () { | ||
return this.tree.prettyPrint('', true) | ||
const root = { | ||
prefix: '/', | ||
nodes: [], | ||
children: {} | ||
} | ||
for (const node of Object.values(this.trees)) { | ||
if (node) { | ||
flattenNode(root, node) | ||
} | ||
} | ||
compressFlattenedNode(root) | ||
return prettyPrintFlattenedNode(root, '', true) | ||
} | ||
@@ -612,0 +639,0 @@ |
@@ -5,7 +5,18 @@ 'use strict' | ||
module.exports = { | ||
storage: SemVerStore, | ||
deriveVersion: function (req, ctx) { | ||
return req.headers['accept-version'] | ||
function build (enabled) { | ||
if (enabled) { | ||
return { | ||
storage: SemVerStore, | ||
deriveVersion: function (req, ctx) { | ||
return req.headers['accept-version'] | ||
} | ||
} | ||
} | ||
return { | ||
storage: SemVerStore, | ||
deriveVersion: function (req, ctx) {}, | ||
disabled: true | ||
} | ||
} | ||
module.exports = build |
102
node.js
'use strict' | ||
const assert = require('assert') | ||
const http = require('http') | ||
const Handlers = buildHandlers() | ||
@@ -17,10 +15,10 @@ const types = { | ||
function Node (options) { | ||
// former arguments order: prefix, children, kind, handlers, regex, versions | ||
options = options || {} | ||
this.prefix = options.prefix || '/' | ||
this.label = this.prefix[0] | ||
this.method = options.method // just for debugging and error messages | ||
this.children = options.children || {} | ||
this.numberOfChildren = Object.keys(this.children).length | ||
this.kind = options.kind || this.types.STATIC | ||
this.handlers = new Handlers(options.handlers) | ||
this.handler = options.handler | ||
this.regex = options.regex || null | ||
@@ -106,3 +104,3 @@ this.wildcardChild = null | ||
this.kind = this.types.STATIC | ||
this.handlers = new Handlers() | ||
this.handler = null | ||
this.numberOfChildren = 0 | ||
@@ -119,5 +117,5 @@ this.regex = null | ||
Node.prototype.findChild = function (path, method) { | ||
Node.prototype.findChild = function (path) { | ||
var child = this.children[path[0]] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handler !== null)) { | ||
if (path.slice(0, child.prefix.length) === child.prefix) { | ||
@@ -129,3 +127,3 @@ return child | ||
child = this.children[':'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handler !== null)) { | ||
return child | ||
@@ -135,3 +133,3 @@ } | ||
child = this.children['*'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.handler !== null)) { | ||
return child | ||
@@ -143,5 +141,5 @@ } | ||
Node.prototype.findVersionChild = function (version, path, method) { | ||
Node.prototype.findVersionChild = function (version, path) { | ||
var child = this.children[path[0]] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version) !== null)) { | ||
if (path.slice(0, child.prefix.length) === child.prefix) { | ||
@@ -153,3 +151,3 @@ return child | ||
child = this.children[':'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version) !== null)) { | ||
return child | ||
@@ -159,3 +157,3 @@ } | ||
child = this.children['*'] | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { | ||
if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version) !== null)) { | ||
return child | ||
@@ -167,11 +165,11 @@ } | ||
Node.prototype.setHandler = function (method, handler, params, store) { | ||
Node.prototype.setHandler = function (handler, params, store) { | ||
if (!handler) return | ||
assert( | ||
this.handlers[method] !== undefined, | ||
`There is already an handler with method '${method}'` | ||
!this.handler, | ||
`There is already an handler with method '${this.method}'` | ||
) | ||
this.handlers[method] = { | ||
this.handler = { | ||
handler: handler, | ||
@@ -184,12 +182,11 @@ params: params, | ||
Node.prototype.setVersionHandler = function (version, method, handler, params, store) { | ||
Node.prototype.setVersionHandler = function (version, handler, params, store) { | ||
if (!handler) return | ||
const handlers = this.versions.get(version) || new Handlers() | ||
assert( | ||
handlers[method] === null, | ||
`There is already an handler with version '${version}' and method '${method}'` | ||
!this.versions.get(version), | ||
`There is already an handler with version '${version}' and method '${this.method}'` | ||
) | ||
handlers[method] = { | ||
this.versions.set(version, { | ||
handler: handler, | ||
@@ -199,64 +196,9 @@ params: params, | ||
paramsLength: params.length | ||
} | ||
this.versions.set(version, handlers) | ||
}) | ||
} | ||
Node.prototype.getHandler = function (method) { | ||
return this.handlers[method] | ||
Node.prototype.getVersionHandler = function (version) { | ||
return this.versions.get(version) | ||
} | ||
Node.prototype.getVersionHandler = function (version, method) { | ||
var handlers = this.versions.get(version) | ||
return handlers === null ? handlers : handlers[method] | ||
} | ||
Node.prototype.prettyPrint = function (prefix, tail) { | ||
var paramName = '' | ||
var handlers = this.handlers || {} | ||
var methods = Object.keys(handlers).filter(method => handlers[method] && handlers[method].handler) | ||
if (this.prefix === ':') { | ||
methods.forEach((method, index) => { | ||
var params = this.handlers[method].params | ||
var param = params[params.length - 1] | ||
if (methods.length > 1) { | ||
if (index === 0) { | ||
paramName += param + ` (${method})\n` | ||
return | ||
} | ||
paramName += prefix + ' :' + param + ` (${method})` | ||
paramName += (index === methods.length - 1 ? '' : '\n') | ||
} else { | ||
paramName = params[params.length - 1] + ` (${method})` | ||
} | ||
}) | ||
} else if (methods.length) { | ||
paramName = ` (${methods.join('|')})` | ||
} | ||
var tree = `${prefix}${tail ? '└── ' : '├── '}${this.prefix}${paramName}\n` | ||
prefix = `${prefix}${tail ? ' ' : '│ '}` | ||
const labels = Object.keys(this.children) | ||
for (var i = 0; i < labels.length - 1; i++) { | ||
tree += this.children[labels[i]].prettyPrint(prefix, false) | ||
} | ||
if (labels.length > 0) { | ||
tree += this.children[labels[labels.length - 1]].prettyPrint(prefix, true) | ||
} | ||
return tree | ||
} | ||
function buildHandlers (handlers) { | ||
var code = `handlers = handlers || {} | ||
` | ||
for (var i = 0; i < http.METHODS.length; i++) { | ||
var m = http.METHODS[i] | ||
code += `this['${m}'] = handlers['${m}'] || null | ||
` | ||
} | ||
return new Function('handlers', code) // eslint-disable-line | ||
} | ||
module.exports = Node | ||
module.exports.Handlers = Handlers |
{ | ||
"name": "find-my-way", | ||
"version": "3.0.4", | ||
"version": "3.0.5", | ||
"description": "Crazy fast http radix based router", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -147,4 +147,6 @@ # find-my-way | ||
If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route. | ||
If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route. If you never configure a versioned route, the `'Accept-Version'` header will be ignored. | ||
Remember to set a [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) header in your responses with the value you are using for deifning the versioning (e.g.: 'Accept-Version'), to prevent cache poisoning attacks. You can also configure this as part your Proxy/CDN. | ||
###### default | ||
@@ -151,0 +153,0 @@ <a name="semver"></a> |
@@ -39,6 +39,4 @@ 'use strict' | ||
├── test (GET) | ||
│ └── / | ||
│ └── :hello (GET) | ||
└── hello/ | ||
└── :world (GET) | ||
│ └── /:hello (GET) | ||
└── hello/:world (GET) | ||
` | ||
@@ -61,8 +59,7 @@ | ||
const expected = `└── / | ||
└── test (GET) | ||
└── / | ||
└── :hello (GET) | ||
:hello (POST) | ||
└── /world (GET) | ||
const expected = `└── /test (GET) | ||
└── / | ||
└── :hello (GET) | ||
:hello (POST) | ||
└── /world (GET) | ||
` | ||
@@ -86,6 +83,4 @@ | ||
├── test (GET) | ||
│ └── / | ||
│ └── * (GET) | ||
└── hello/ | ||
└── * (GET) | ||
│ └── /* (GET) | ||
└── hello/* (GET) | ||
` | ||
@@ -108,9 +103,8 @@ | ||
const expected = `└── / | ||
└── test (GET) | ||
└── /hello | ||
├── / | ||
│ └── :id (GET) | ||
│ :id (POST) | ||
└── world (GET) | ||
const expected = `└── /test (GET) | ||
└── /hello | ||
├── / | ||
│ └── :id (GET) | ||
│ :id (POST) | ||
└── world (GET) | ||
` | ||
@@ -117,0 +111,0 @@ |
@@ -243,1 +243,26 @@ 'use strict' | ||
}) | ||
test('Versioning won\'t work if there are no versioned routes', t => { | ||
t.plan(2) | ||
const findMyWay = FindMyWay({ | ||
defaultRoute: (req, res) => { | ||
t.fail('We should not be here') | ||
} | ||
}) | ||
findMyWay.on('GET', '/', (req, res) => { | ||
t.pass('Yeah!') | ||
}) | ||
findMyWay.lookup({ | ||
method: 'GET', | ||
url: '/', | ||
headers: { 'accept-version': '2.x' } | ||
}, null) | ||
findMyWay.lookup({ | ||
method: 'GET', | ||
url: '/' | ||
}, null) | ||
}) |
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
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
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
174478
48
4851
396
5