From f19a0dbb9428d2297d00358d6b3d08487c1016dc Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 11:21:54 +0100 Subject: [PATCH 01/41] move accept version to strategies folder and rename deriveVersion to the generic name deriveConstraint --- lib/{ => strategies}/accept-version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename lib/{ => strategies}/accept-version.js (78%) diff --git a/lib/accept-version.js b/lib/strategies/accept-version.js similarity index 78% rename from lib/accept-version.js rename to lib/strategies/accept-version.js index 3d698c69..92a2e714 100644 --- a/lib/accept-version.js +++ b/lib/strategies/accept-version.js @@ -4,7 +4,7 @@ const SemVerStore = require('semver-store') module.exports = { storage: SemVerStore, - deriveVersion: function (req, ctx) { + deriveConstraint: function (req, ctx) { return req.headers['accept-version'] } } From 1220b0726afe34cda396b5afeec83baef2d9961b Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 11:24:42 +0100 Subject: [PATCH 02/41] add accept-host strategy with exact match support --- lib/strategies/accept-host.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/strategies/accept-host.js diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js new file mode 100644 index 00000000..965fddbc --- /dev/null +++ b/lib/strategies/accept-host.js @@ -0,0 +1,17 @@ +'use strict' + +// TODO: Add regex support +module.exports = { + storage: function () { + let hosts = {} + return { + get: (host) => { return hosts[host] || null }, + set: (host, store) => { hosts[host] = store }, + del: (host) => { delete hosts[host] }, + empty: () => { hosts = {} } + } + }, + deriveConstraint: function (req, ctx) { + return req.headers.host + } +} From 5544329bea4083100869fc09e699b5da216f7b4d Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:29:22 +0100 Subject: [PATCH 03/41] add constraints store --- lib/constraints-store.js | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/constraints-store.js diff --git a/lib/constraints-store.js b/lib/constraints-store.js new file mode 100644 index 00000000..5c72b04c --- /dev/null +++ b/lib/constraints-store.js @@ -0,0 +1,47 @@ +'use strict' + +const { assert } = require('console') + +function ConstraintsStore (strategies) { + if (!(this instanceof ConstraintsStore)) { + return new ConstraintsStore(strategies) + } + + this.strategies = strategies +} + +ConstraintsStore.prototype.set = function (constraints, store) { + // TODO: Should I check for existence of at least one constraint? + if (typeof constraints !== 'object' || constraints === null) { + throw new TypeError('Constraints should be an object') + } + + Object.keys(constraints).forEach(kConstraint => { + assert(this.strategies[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) + this.strategies[kConstraint].set(constraints[kConstraint], { store, constraints }) + }) + + return this +} + +ConstraintsStore.prototype.get = function (constraints, method) { + if (typeof constraints !== 'object' || constraints === null) { + throw new TypeError('Constraints should be an object') + } + + var returnedStore = null + const keys = Object.keys(constraints) + for (var i = 0; i < keys.length; i++) { + const kConstraint = keys[i] + assert(this.strategies[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) + const storedObject = this.strategies[kConstraint].get(constraints[kConstraint]) + if (!storedObject || !storedObject.store || !storedObject.store[method]) return null + // TODO: Order of properties may result in inequality + if (JSON.stringify(constraints) !== JSON.stringify(storedObject.constraints)) return null + if (!returnedStore) returnedStore = storedObject.store + } + + return returnedStore +} + +module.exports = ConstraintsStore From ab39b39f376777459ec406cc6e58817d54d468ae Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:29:50 +0100 Subject: [PATCH 04/41] add accept-constraints --- lib/accept-constraints.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/accept-constraints.js diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js new file mode 100644 index 00000000..b2bc799a --- /dev/null +++ b/lib/accept-constraints.js @@ -0,0 +1,36 @@ +'use strict' + +const ConstraintsStore = require('./constraints-store') + +const acceptVersionStrategy = require('./strategies/accept-version') +const acceptHostStrategy = require('./strategies/accept-host') + +module.exports = (strats) => { + const strategies = { + version: acceptVersionStrategy, + host: acceptHostStrategy, + ...strats + } + + return { + storage: ConstraintsStore.bind(null, instanciateStorage()), + getConstraintsExtractor: function (req, ctx) { + return function (kConstraints) { + const derivedConstraints = {} + kConstraints.forEach(key => { + var value = strategies[key].deriveConstraint(req, ctx) + if (value) derivedConstraints[key] = value + }) + return derivedConstraints + } + } + } + + function instanciateStorage () { + const result = {} + Object.keys(strategies).forEach(strategy => { + result[strategy] = strategies[strategy].storage() + }) + return result + } +} From b311ec0012836f6b5fec0da545f065415af1bafd Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:35:01 +0100 Subject: [PATCH 05/41] use constraintsStorage instead of version storage --- node.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node.js b/node.js index a38088cc..15efcb19 100644 --- a/node.js +++ b/node.js @@ -13,8 +13,8 @@ const types = { MULTI_PARAM: 4 } -function Node (options) { - // former arguments order: prefix, children, kind, handlers, regex, versions +function Node(options) { + // former arguments order: prefix, children, kind, handlers, regex, constraints options = options || {} this.prefix = options.prefix || '/' this.label = this.prefix[0] @@ -26,6 +26,7 @@ function Node (options) { this.wildcardChild = null this.parametricBrother = null this.versions = options.versions + this.constraintsStorage = options.constraints } Object.defineProperty(Node.prototype, 'types', { From 141f42f22fd94d3cbe35b141e666f0122d037a0e Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:36:16 +0100 Subject: [PATCH 06/41] add kConstraints property and update reset() --- node.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/node.js b/node.js index 15efcb19..157bcd13 100644 --- a/node.js +++ b/node.js @@ -25,7 +25,8 @@ function Node(options) { this.regex = options.regex || null this.wildcardChild = null this.parametricBrother = null - this.versions = options.versions + // kConstraints allows us to know which constraints we need to extract from the request + this.kConstraints = new Set() this.constraintsStorage = options.constraints } @@ -99,7 +100,7 @@ Node.prototype.addChild = function (node) { return this } -Node.prototype.reset = function (prefix, versions) { +Node.prototype.reset = function (prefix, constraints) { this.prefix = prefix this.children = {} this.kind = this.types.STATIC @@ -107,7 +108,8 @@ Node.prototype.reset = function (prefix, versions) { this.numberOfChildren = 0 this.regex = null this.wildcardChild = null - this.versions = versions + this.kConstraints = new Set() + this.constraintsStorage = constraints return this } From 25f7b04d1b588f3294a779cd18f5f29be1fede00 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:39:45 +0100 Subject: [PATCH 07/41] update getVersionHandler() to support constraints --- node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node.js b/node.js index 157bcd13..7bc918a3 100644 --- a/node.js +++ b/node.js @@ -197,8 +197,8 @@ Node.prototype.getHandler = function (method) { return this.handlers[method] } -Node.prototype.getVersionHandler = function (version, method) { - var handlers = this.versions.get(version) +Node.prototype.getConstraintsHandler = function (constraints, method) { + var handlers = this.constraintsStorage.get(constraints, method) return handlers === null ? handlers : handlers[method] } From c6b11d794c5b2d974962a2bf0d64c7f685cc6080 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:40:05 +0100 Subject: [PATCH 08/41] add getMatchingHandler() to support both constrained and non-constrained handler retrieval --- node.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/node.js b/node.js index 7bc918a3..11a03706 100644 --- a/node.js +++ b/node.js @@ -202,6 +202,17 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { return handlers === null ? handlers : handlers[method] } +Node.prototype.getMatchingHandler = function (constraintsExtractor, method) { + var constraints = constraintsExtractor(this.kConstraints) + + if (Object.keys(constraints).length) { + var handler = this.getConstraintsHandler(constraints, method) + if (handler) return handler; + } + + return this.getHandler(method) +} + Node.prototype.prettyPrint = function (prefix, tail) { var paramName = '' var handlers = this.handlers || {} From eede77e3fd57af33ac334f6281b408e0db5cdb8d Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:41:06 +0100 Subject: [PATCH 09/41] fix typo --- node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js b/node.js index 11a03706..266f8f3f 100644 --- a/node.js +++ b/node.js @@ -164,7 +164,7 @@ Node.prototype.setHandler = function (method, handler, params, store) { assert( this.handlers[method] !== undefined, - `There is already an handler with method '${method}'` + `There is already a handler with method '${method}'` ) this.handlers[method] = { From 9db137ba451d97a3ea7e603dd480e363a939918b Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:42:13 +0100 Subject: [PATCH 10/41] replace findChild() and findVersionChild() with generic findMatchingChild() --- node.js | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/node.js b/node.js index 266f8f3f..b498ab69 100644 --- a/node.js +++ b/node.js @@ -117,42 +117,21 @@ Node.prototype.findByLabel = function (path) { return this.children[path[0]] } -Node.prototype.findChild = function (path, method) { +Node.prototype.findMatchingChild = function (constraintsExtractor, path, method) { var child = this.children[path[0]] - if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { if (path.slice(0, child.prefix.length) === child.prefix) { return child } } child = this.children[':'] - if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { return child } child = this.children['*'] - if (child !== undefined && (child.numberOfChildren > 0 || child.handlers[method] !== null)) { - return child - } - - return null -} - -Node.prototype.findVersionChild = function (version, path, method) { - var child = this.children[path[0]] - if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { - if (path.slice(0, child.prefix.length) === child.prefix) { - return child - } - } - - child = this.children[':'] - if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { - return child - } - - child = this.children['*'] - if (child !== undefined && (child.numberOfChildren > 0 || child.getVersionHandler(version, method) !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { return child } From ac61bdebbb9d48a95214880d80f5a7141226abc8 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:42:49 +0100 Subject: [PATCH 11/41] update setVersionHandler() to support constraints --- node.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/node.js b/node.js index b498ab69..5fd48ee7 100644 --- a/node.js +++ b/node.js @@ -154,14 +154,15 @@ Node.prototype.setHandler = function (method, handler, params, store) { } } -Node.prototype.setVersionHandler = function (version, method, handler, params, store) { +Node.prototype.setConstraintsHandler = function (constraints, method, 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}'` - ) + const handlers = this.constraintsStorage.get(constraints, method) || new Handlers() + + assert(handlers[method] === null, `There is already a handler with constraints '${JSON.stringify(constraints)}' and method '${method}'`) + + // Update kConstraints with new constraint keys for this node + Object.keys(constraints).forEach(kConstraint => this.kConstraints.add(kConstraint)) handlers[method] = { handler: handler, @@ -169,7 +170,8 @@ Node.prototype.setVersionHandler = function (version, method, handler, params, s store: store || null, paramsLength: params.length } - this.versions.set(version, handlers) + + this.constraintsStorage.set(constraints, handlers) } Node.prototype.getHandler = function (method) { From 9c3d4d8cd9b9f15aaf5aa2473afb1342681f0b0c Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:43:28 +0100 Subject: [PATCH 12/41] use acceptConstraints instead of acceptVersionStrategy --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index f45755c0..154e8218 100644 --- a/index.js +++ b/index.js @@ -24,7 +24,7 @@ if (!isRegexSafe(FULL_PATH_REGEXP)) { throw new Error('the FULL_PATH_REGEXP is not safe, update this module') } -const acceptVersionStrategy = require('./lib/accept-version') +const acceptConstraints = require('./lib/accept-constraints') function Router (opts) { if (!(this instanceof Router)) { @@ -50,8 +50,8 @@ function Router (opts) { this.ignoreTrailingSlash = opts.ignoreTrailingSlash || false this.maxParamLength = opts.maxParamLength || 100 this.allowUnsafeRegex = opts.allowUnsafeRegex || false - this.versioning = opts.versioning || acceptVersionStrategy - this.tree = new Node({ versions: this.versioning.storage() }) + this.constraining = acceptConstraints(opts.constrainingStrategies) + this.tree = new Node({ constraints: this.constraining.storage() }) this.routes = [] } From fe27b59e86451fbe07554e2fdf1904d0d4e5fe3d Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:46:38 +0100 Subject: [PATCH 13/41] replace version usage with constraints --- index.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 154e8218..7fb403f8 100644 --- a/index.js +++ b/index.js @@ -93,9 +93,13 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { assert(typeof method === 'string', 'Method should be a string') assert(httpMethods.indexOf(method) !== -1, `Method '${method}' is not an http method.`) - // version validation - if (opts.version !== undefined) { - assert(typeof opts.version === 'string', 'Version should be a string') + // constraints validation + if (opts.constraints !== undefined) { + // TODO: Support more explicit validation? + assert(typeof opts.constraints === 'object' && opts.constraints !== null, 'Constraints should be an object') + if (Object.keys(opts.constraints).length === 0) { + opts.constraints = undefined + } } const params = [] @@ -109,7 +113,7 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { store: store }) - const version = opts.version + const constraints = opts.constraints for (var i = 0, len = path.length; i < len; i++) { // search for parametric or wildcard routes @@ -124,7 +128,7 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { } // add the static part of the route to the tree - this._insert(method, staticPart, NODE_TYPES.STATIC, null, null, null, null, version) + this._insert(method, staticPart, NODE_TYPES.STATIC, null, null, null, null, constraints) // isolate the parameter name var isRegex = false @@ -166,22 +170,22 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { if (this.caseSensitive === false) { completedPath = completedPath.toLowerCase() } - return this._insert(method, completedPath, nodeType, params, handler, store, regex, version) + return this._insert(method, completedPath, nodeType, params, handler, store, regex, constraints) } // add the parameter and continue with the search staticPart = path.slice(0, i) if (this.caseSensitive === false) { staticPart = staticPart.toLowerCase() } - this._insert(method, staticPart, nodeType, params, null, null, regex, version) + this._insert(method, staticPart, nodeType, params, null, null, regex, constraints) i-- // wildcard route } else if (path.charCodeAt(i) === 42) { - this._insert(method, path.slice(0, i), NODE_TYPES.STATIC, null, null, null, null, version) + this._insert(method, path.slice(0, i), NODE_TYPES.STATIC, null, null, null, null, constraints) // add the wildcard parameter params.push('*') - return this._insert(method, path.slice(0, len), NODE_TYPES.MATCH_ALL, params, handler, store, null, version) + return this._insert(method, path.slice(0, len), NODE_TYPES.MATCH_ALL, params, handler, store, null, constraints) } } @@ -190,10 +194,10 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { } // static route - this._insert(method, path, NODE_TYPES.STATIC, params, handler, store, null, version) + this._insert(method, path, NODE_TYPES.STATIC, params, handler, store, null, constraints) } -Router.prototype._insert = function _insert (method, path, kind, params, handler, store, regex, version) { +Router.prototype._insert = function _insert (method, path, kind, params, handler, store, regex, constraints) { const route = path var currentNode = this.tree var prefix = '' @@ -223,7 +227,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler kind: currentNode.kind, handlers: new Node.Handlers(currentNode.handlers), regex: currentNode.regex, - versions: currentNode.versions + constraints: currentNode.constraintsStorage } ) if (currentNode.wildcardChild !== null) { @@ -232,7 +236,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler // reset the parent currentNode - .reset(prefix.slice(0, len), this.versioning.storage()) + .reset(prefix.slice(0, len), this.constraining.storage()) .addChild(node) // if the longest common prefix has the same length of the current path @@ -252,7 +256,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler kind: kind, handlers: null, regex: regex, - versions: this.versioning.storage() + constraints: this.constraining.storage() }) if (version) { node.setVersionHandler(version, method, handler, params, store) @@ -275,9 +279,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler continue } // 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() }) - if (version) { - node.setVersionHandler(version, method, handler, params, store) + node = new Node({ prefix: path, kind: kind, handlers: null, regex: regex, constraints: this.constraining.storage() }) } else { node.setHandler(method, handler, params, store) } @@ -299,7 +301,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler } Router.prototype.reset = function reset () { - this.tree = new Node({ versions: this.versioning.storage() }) + this.tree = new Node({ constraints: this.constraining.storage() }) this.routes = [] } From 212c3ae7538d6993a38b980d4009f6363e7c0662 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:49:41 +0100 Subject: [PATCH 14/41] use constraintsExtractor --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 7fb403f8..2c3c5d2b 100644 --- a/index.js +++ b/index.js @@ -352,14 +352,14 @@ Router.prototype.off = function off (method, path) { } Router.prototype.lookup = function lookup (req, res, ctx) { - var handle = this.find(req.method, sanitizeUrl(req.url), this.versioning.deriveVersion(req, ctx)) + var handle = this.find(req.method, sanitizeUrl(req.url), this.constraining.getConstraintsExtractor(req, ctx)) if (handle === null) return this._defaultRoute(req, res, ctx) return ctx === undefined ? handle.handler(req, res, handle.params, handle.store) : handle.handler.call(ctx, req, res, handle.params, handle.store) } -Router.prototype.find = function find (method, path, version) { +Router.prototype.find = function find (method, path, constraintsExtractor) { if (path.charCodeAt(0) !== 47) { // 47 is '/' path = path.replace(FULL_PATH_REGEXP, '/') } From 8650a7cff4cb00dc25c0b12e483e83eab8269ab9 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:50:19 +0100 Subject: [PATCH 15/41] use getMatchingHandler() and findMatchingChild() --- index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 2c3c5d2b..9a7f426c 100644 --- a/index.js +++ b/index.js @@ -389,9 +389,7 @@ Router.prototype.find = function find (method, path, constraintsExtractor) { var previousPath = path // found the route if (pathLen === 0 || path === prefix) { - var handle = version === undefined - ? currentNode.handlers[method] - : currentNode.getVersionHandler(version, method) + var handle = currentNode.getMatchingHandler(constraintsExtractor, method) if (handle !== null && handle !== undefined) { var paramsObj = {} if (handle.paramsLength > 0) { @@ -420,9 +418,7 @@ Router.prototype.find = function find (method, path, constraintsExtractor) { idxInOriginalPath += len } - var node = version === undefined - ? currentNode.findChild(path, method) - : currentNode.findVersionChild(version, path, method) + var node = currentNode.findMatchingChild(constraintsExtractor, path, method) if (node === null) { node = currentNode.parametricBrother From edd00c98f5eaaad5fd6f09b7ac645e3d2ab8d9bf Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sat, 3 Oct 2020 12:52:05 +0100 Subject: [PATCH 16/41] use getConstraintsHandler() and setConstraintsHandler() --- index.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 9a7f426c..0905d1bd 100644 --- a/index.js +++ b/index.js @@ -99,7 +99,7 @@ Router.prototype._on = function _on (method, path, opts, handler, store) { assert(typeof opts.constraints === 'object' && opts.constraints !== null, 'Constraints should be an object') if (Object.keys(opts.constraints).length === 0) { opts.constraints = undefined - } + } } const params = [] @@ -242,9 +242,9 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler // if the longest common prefix has the same length of the current path // the handler should be added to the current node, to a child otherwise if (len === pathLen) { - if (version) { - assert(!currentNode.getVersionHandler(version, method), `Method '${method}' already declared for route '${route}' version '${version}'`) - currentNode.setVersionHandler(version, method, handler, params, store) + if (constraints) { + assert(!currentNode.getConstraintsHandler(constraints, method), `Method '${method}' already declared for route '${route}' with constraints '${JSON.stringify(constraints)}'`) + currentNode.setConstraintsHandler(constraints, method, handler, params, store) } else { assert(!currentNode.getHandler(method), `Method '${method}' already declared for route '${route}'`) currentNode.setHandler(method, handler, params, store) @@ -258,8 +258,8 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler regex: regex, constraints: this.constraining.storage() }) - if (version) { - node.setVersionHandler(version, method, handler, params, store) + if (constraints) { + node.setConstraintsHandler(constraints, method, handler, params, store) } else { node.setHandler(method, handler, params, store) } @@ -280,6 +280,8 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler } // 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, constraints: this.constraining.storage() }) + if (constraints) { + node.setConstraintsHandler(constraints, method, handler, params, store) } else { node.setHandler(method, handler, params, store) } @@ -288,9 +290,9 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler // the node already exist } else if (handler) { - if (version) { - assert(!currentNode.getVersionHandler(version, method), `Method '${method}' already declared for route '${route}' version '${version}'`) - currentNode.setVersionHandler(version, method, handler, params, store) + if (constraints) { + assert(!currentNode.getConstraintsHandler(constraints, method), `Method '${method}' already declared for route '${route}' with constraints '${JSON.stringify(constraints)}'`) + currentNode.setConstraintsHandler(constraints, method, handler, params, store) } else { assert(!currentNode.getHandler(method), `Method '${method}' already declared for route '${route}'`) currentNode.setHandler(method, handler, params, store) From edde841f4913c88c9827bec9c8f98563a73b52e4 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 13:27:49 +0100 Subject: [PATCH 17/41] refactor version & host strategies to use prototype for performance improvement --- lib/accept-constraints.js | 22 +++++++++++++++++----- lib/strategies/accept-host.js | 29 +++++++++++++++++------------ lib/strategies/accept-version.js | 13 ++++++++----- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index b2bc799a..3957f9e4 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -5,11 +5,23 @@ const ConstraintsStore = require('./constraints-store') const acceptVersionStrategy = require('./strategies/accept-version') const acceptHostStrategy = require('./strategies/accept-host') -module.exports = (strats) => { - const strategies = { - version: acceptVersionStrategy, - host: acceptHostStrategy, - ...strats +const DEFAULT_STRATEGIES_NAMES = ['version', 'host'] + +module.exports = (customStrategies) => { + const strategies = [ + new acceptVersionStrategy(), + new acceptHostStrategy() + ] + + if (customStrategies) { + for (let i = 0; i < customStrategies.length; i++) { + const strategy = new customStrategies[i]() + if (DEFAULT_STRATEGIES_NAMES.indexOf(strategy.name) !== -1) { + strategies[i] = strategy + } else { + strategies.push(strategy) + } + } } return { diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js index 965fddbc..749922f7 100644 --- a/lib/strategies/accept-host.js +++ b/lib/strategies/accept-host.js @@ -1,17 +1,22 @@ 'use strict' // TODO: Add regex support -module.exports = { - storage: function () { - let hosts = {} - return { - get: (host) => { return hosts[host] || null }, - set: (host, store) => { hosts[host] = store }, - del: (host) => { delete hosts[host] }, - empty: () => { hosts = {} } - } - }, - deriveConstraint: function (req, ctx) { - return req.headers.host +function acceptHost() {} + +function HostStore() { + let hosts = {} + return { + get: (host) => { return hosts[host] || null }, + set: (host, store) => { hosts[host] = store }, + del: (host) => { delete hosts[host] }, + empty: () => { hosts = {} } } } + +acceptHost.prototype.name = 'host' +acceptHost.prototype.storage = HostStore +acceptHost.prototype.deriveConstraint = function (req, ctx) { + return req.headers['host'] +} + +module.exports = acceptHost \ No newline at end of file diff --git a/lib/strategies/accept-version.js b/lib/strategies/accept-version.js index 92a2e714..2f055d41 100644 --- a/lib/strategies/accept-version.js +++ b/lib/strategies/accept-version.js @@ -2,9 +2,12 @@ const SemVerStore = require('semver-store') -module.exports = { - storage: SemVerStore, - deriveConstraint: function (req, ctx) { - return req.headers['accept-version'] - } +function acceptVersion() { } + +acceptVersion.prototype.name = 'version' +acceptVersion.prototype.storage = SemVerStore +acceptVersion.prototype.deriveConstraint = function (req, ctx) { + return req.headers['version'] } + +module.exports = acceptVersion \ No newline at end of file From 537b684a035460caab776f5d757eecf3a04733d1 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 13:28:40 +0100 Subject: [PATCH 18/41] replace getConstraintsExtractor with deriveConstraints --- index.js | 8 ++++---- lib/accept-constraints.js | 35 ++++++++++++++++++----------------- node.js | 16 +++++++--------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 0905d1bd..048fc5c7 100644 --- a/index.js +++ b/index.js @@ -354,14 +354,14 @@ Router.prototype.off = function off (method, path) { } Router.prototype.lookup = function lookup (req, res, ctx) { - var handle = this.find(req.method, sanitizeUrl(req.url), this.constraining.getConstraintsExtractor(req, ctx)) + var handle = this.find(req.method, sanitizeUrl(req.url), this.constraining.deriveConstraints(req, ctx)) if (handle === null) return this._defaultRoute(req, res, ctx) return ctx === undefined ? handle.handler(req, res, handle.params, handle.store) : handle.handler.call(ctx, req, res, handle.params, handle.store) } -Router.prototype.find = function find (method, path, constraintsExtractor) { +Router.prototype.find = function find (method, path, derivedConstraints) { if (path.charCodeAt(0) !== 47) { // 47 is '/' path = path.replace(FULL_PATH_REGEXP, '/') } @@ -391,7 +391,7 @@ Router.prototype.find = function find (method, path, constraintsExtractor) { var previousPath = path // found the route if (pathLen === 0 || path === prefix) { - var handle = currentNode.getMatchingHandler(constraintsExtractor, method) + var handle = currentNode.getMatchingHandler(derivedConstraints, method) if (handle !== null && handle !== undefined) { var paramsObj = {} if (handle.paramsLength > 0) { @@ -420,7 +420,7 @@ Router.prototype.find = function find (method, path, constraintsExtractor) { idxInOriginalPath += len } - var node = currentNode.findMatchingChild(constraintsExtractor, path, method) + var node = currentNode.findMatchingChild(derivedConstraints, path, method) if (node === null) { node = currentNode.parametricBrother diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 3957f9e4..8318dd7a 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -25,24 +25,25 @@ module.exports = (customStrategies) => { } return { - storage: ConstraintsStore.bind(null, instanciateStorage()), - getConstraintsExtractor: function (req, ctx) { - return function (kConstraints) { - const derivedConstraints = {} - kConstraints.forEach(key => { - var value = strategies[key].deriveConstraint(req, ctx) - if (value) derivedConstraints[key] = value - }) - return derivedConstraints + storage: function () { + const stores = {} + for (var i = 0; i < strategies.length; i++) { + stores[strategies[i].name] = strategies[i].storage() + } + return ConstraintsStore(stores) + }, + deriveConstraints: function (req, ctx) { + const derivedConstraints = {} + let value, hasConstraint = false + for (var i = 0; i < strategies.length; i++) { + value = strategies[i].deriveConstraint(req, ctx) + if (value) { + hasConstraint = true + derivedConstraints[strategies[i].name] = value + } } - } - } - function instanciateStorage () { - const result = {} - Object.keys(strategies).forEach(strategy => { - result[strategy] = strategies[strategy].storage() - }) - return result + return hasConstraint ? derivedConstraints : null + } } } diff --git a/node.js b/node.js index 5fd48ee7..8b17f282 100644 --- a/node.js +++ b/node.js @@ -117,21 +117,21 @@ Node.prototype.findByLabel = function (path) { return this.children[path[0]] } -Node.prototype.findMatchingChild = function (constraintsExtractor, path, method) { +Node.prototype.findMatchingChild = function (derivedConstraints, path, method) { var child = this.children[path[0]] - if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints, method) !== null)) { if (path.slice(0, child.prefix.length) === child.prefix) { return child } } child = this.children[':'] - if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints, method) !== null)) { return child } child = this.children['*'] - if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(constraintsExtractor, method) !== null)) { + if (child !== undefined && (child.numberOfChildren > 0 || child.getMatchingHandler(derivedConstraints, method) !== null)) { return child } @@ -183,11 +183,9 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { return handlers === null ? handlers : handlers[method] } -Node.prototype.getMatchingHandler = function (constraintsExtractor, method) { - var constraints = constraintsExtractor(this.kConstraints) - - if (Object.keys(constraints).length) { - var handler = this.getConstraintsHandler(constraints, method) +Node.prototype.getMatchingHandler = function (derivedConstraints, method) { + if (derivedConstraints) { + var handler = this.getConstraintsHandler(derivedConstraints, method) if (handler) return handler; } From c05c763e786efb84f97825ca3eb0ab7cbece9af3 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 13:29:58 +0100 Subject: [PATCH 19/41] fix assert import --- lib/constraints-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/constraints-store.js b/lib/constraints-store.js index 5c72b04c..f56e1434 100644 --- a/lib/constraints-store.js +++ b/lib/constraints-store.js @@ -1,6 +1,6 @@ 'use strict' -const { assert } = require('console') +const assert = require('assert') function ConstraintsStore (strategies) { if (!(this instanceof ConstraintsStore)) { From b9954dcd15bce15e8b204fc4857f361fb84a84e0 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 13:33:13 +0100 Subject: [PATCH 20/41] update constraintsStore to centralize store storage in a shared map --- lib/constraints-store.js | 48 ++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/constraints-store.js b/lib/constraints-store.js index f56e1434..1fca6701 100644 --- a/lib/constraints-store.js +++ b/lib/constraints-store.js @@ -2,12 +2,13 @@ const assert = require('assert') -function ConstraintsStore (strategies) { +function ConstraintsStore (stores) { if (!(this instanceof ConstraintsStore)) { - return new ConstraintsStore(strategies) + return new ConstraintsStore(stores) } - - this.strategies = strategies + this.storeIdCounter = 1 + this.stores = stores + this.storeMap = new Map() } ConstraintsStore.prototype.set = function (constraints, store) { @@ -16,10 +17,16 @@ ConstraintsStore.prototype.set = function (constraints, store) { throw new TypeError('Constraints should be an object') } - Object.keys(constraints).forEach(kConstraint => { - assert(this.strategies[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) - this.strategies[kConstraint].set(constraints[kConstraint], { store, constraints }) - }) + const storeId = this.storeIdCounter++ + this.storeMap.set(storeId, store) + + var kConstraint + const kConstraints = Object.keys(constraints) + for (var i = 0; i < kConstraints.length; i++) { + kConstraint = kConstraints[i] + assert(this.stores[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) + this.stores[kConstraint].set(constraints[kConstraint], storeId) + } return this } @@ -29,19 +36,22 @@ ConstraintsStore.prototype.get = function (constraints, method) { throw new TypeError('Constraints should be an object') } - var returnedStore = null - const keys = Object.keys(constraints) - for (var i = 0; i < keys.length; i++) { - const kConstraint = keys[i] - assert(this.strategies[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) - const storedObject = this.strategies[kConstraint].get(constraints[kConstraint]) - if (!storedObject || !storedObject.store || !storedObject.store[method]) return null - // TODO: Order of properties may result in inequality - if (JSON.stringify(constraints) !== JSON.stringify(storedObject.constraints)) return null - if (!returnedStore) returnedStore = storedObject.store + var tmpStoreId, storeId + const kConstraints = Object.keys(constraints) + for (var i = 0; i < kConstraints.length; i++) { + const kConstraint = kConstraints[i] + assert(this.stores[kConstraint] !== null, `No strategy available for handling the constraint '${kConstraint}'`) + tmpStoreId = this.stores[kConstraint].get(constraints[kConstraint]) + if (!tmpStoreId || (storeId && tmpStoreId !== storeId)) return null + else storeId = tmpStoreId + } + + if (storeId) { + const store = this.storeMap.get(storeId) + if (store && store[method]) return store } - return returnedStore + return null } module.exports = ConstraintsStore From 303f2f31c2dd210a430da357a945766fa5fb485f Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 14:47:42 +0100 Subject: [PATCH 21/41] update bench file to support new constraints format --- bench.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bench.js b/bench.js index 0d492848..e9cf5453 100644 --- a/bench.js +++ b/bench.js @@ -20,7 +20,9 @@ findMyWay.on('GET', '/user/:id/static', () => true) findMyWay.on('GET', '/customer/:name-:surname', () => true) findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', () => true) findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true) -findMyWay.on('GET', '/', { version: '1.2.0' }, () => true) +findMyWay.on('GET', '/', { constraints: { version: '1.2.0'} }, () => true) + +console.log('Routes registered successfully...') suite .add('lookup static route', function () { @@ -44,6 +46,9 @@ suite .add('lookup static versioned route', function () { findMyWay.lookup({ method: 'GET', url: '/', headers: { 'accept-version': '1.x' } }, null) }) + .add('lookup static constrained (version & host) route', function () { + findMyWay.lookup({ method: 'GET', url: '/', headers: { 'accept-version': '1.x', host: 'google.com' } }, null) + }) .add('find static route', function () { findMyWay.find('GET', '/', undefined) }) @@ -63,7 +68,10 @@ suite findMyWay.find('GET', '/user/qwertyuiopasdfghjklzxcvbnm/static', undefined) }) .add('find static versioned route', function () { - findMyWay.find('GET', '/', '1.x') + findMyWay.find('GET', '/', { version: '1.x'} ) + }) + .add('find static constrained (version & host) route', function () { + findMyWay.find('GET', '/', { version: '1.x', host: 'google.com'} ) }) .on('cycle', function (event) { console.log(String(event.target)) From 4aa5990d317673f3d57a7ebd2de8f9945fd19e34 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Mon, 12 Oct 2020 16:03:16 +0100 Subject: [PATCH 22/41] match first handler that passes constraints check --- node.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/node.js b/node.js index 8b17f282..c8c9d825 100644 --- a/node.js +++ b/node.js @@ -26,7 +26,7 @@ function Node(options) { this.wildcardChild = null this.parametricBrother = null // kConstraints allows us to know which constraints we need to extract from the request - this.kConstraints = new Set() + this.kConstraints = [] this.constraintsStorage = options.constraints } @@ -108,7 +108,7 @@ Node.prototype.reset = function (prefix, constraints) { this.numberOfChildren = 0 this.regex = null this.wildcardChild = null - this.kConstraints = new Set() + this.kConstraints = [] this.constraintsStorage = constraints return this } @@ -162,7 +162,8 @@ Node.prototype.setConstraintsHandler = function (constraints, method, handler, p assert(handlers[method] === null, `There is already a handler with constraints '${JSON.stringify(constraints)}' and method '${method}'`) // Update kConstraints with new constraint keys for this node - Object.keys(constraints).forEach(kConstraint => this.kConstraints.add(kConstraint)) + // Object.keys(constraints).forEach(kConstraint => this.kConstraints.add(kConstraint)) + this.kConstraints.push(Object.keys(constraints)) handlers[method] = { handler: handler, @@ -185,8 +186,15 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { Node.prototype.getMatchingHandler = function (derivedConstraints, method) { if (derivedConstraints) { - var handler = this.getConstraintsHandler(derivedConstraints, method) - if (handler) return handler; + var constraints + for (let i = 0; i < this.kConstraints.length; i++) { + constraints = {} + for (let j = 0; j < this.kConstraints[i].length; j++) { + constraints[this.kConstraints[i][j]] = derivedConstraints[this.kConstraints[i][j]] + } + var handler = this.getConstraintsHandler(constraints, method) + if (handler) return handler; + } } return this.getHandler(method) From c5ea6e27434f56888ed307a091534686a4231f3a Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Wed, 14 Oct 2020 10:29:00 +0100 Subject: [PATCH 23/41] replace let with var --- lib/accept-constraints.js | 4 ++-- node.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 8318dd7a..65b32277 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -14,7 +14,7 @@ module.exports = (customStrategies) => { ] if (customStrategies) { - for (let i = 0; i < customStrategies.length; i++) { + for (var i = 0; i < customStrategies.length; i++) { const strategy = new customStrategies[i]() if (DEFAULT_STRATEGIES_NAMES.indexOf(strategy.name) !== -1) { strategies[i] = strategy @@ -34,7 +34,7 @@ module.exports = (customStrategies) => { }, deriveConstraints: function (req, ctx) { const derivedConstraints = {} - let value, hasConstraint = false + var value, hasConstraint = false for (var i = 0; i < strategies.length; i++) { value = strategies[i].deriveConstraint(req, ctx) if (value) { diff --git a/node.js b/node.js index c8c9d825..718863f4 100644 --- a/node.js +++ b/node.js @@ -187,9 +187,9 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { Node.prototype.getMatchingHandler = function (derivedConstraints, method) { if (derivedConstraints) { var constraints - for (let i = 0; i < this.kConstraints.length; i++) { + for (var i = 0; i < this.kConstraints.length; i++) { constraints = {} - for (let j = 0; j < this.kConstraints[i].length; j++) { + for (var j = 0; j < this.kConstraints[i].length; j++) { constraints[this.kConstraints[i][j]] = derivedConstraints[this.kConstraints[i][j]] } var handler = this.getConstraintsHandler(constraints, method) From 2ac7cba05f5458cfc37c3c62d22a7d689c5acee1 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Wed, 14 Oct 2020 11:46:47 +0100 Subject: [PATCH 24/41] move variable declaration outside loop --- node.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/node.js b/node.js index 718863f4..fe6b6477 100644 --- a/node.js +++ b/node.js @@ -185,15 +185,14 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { } Node.prototype.getMatchingHandler = function (derivedConstraints, method) { - if (derivedConstraints) { - var constraints + var constraints, handler for (var i = 0; i < this.kConstraints.length; i++) { constraints = {} for (var j = 0; j < this.kConstraints[i].length; j++) { constraints[this.kConstraints[i][j]] = derivedConstraints[this.kConstraints[i][j]] } - var handler = this.getConstraintsHandler(constraints, method) - if (handler) return handler; + handler = this.getConstraintsHandler(constraints, method) + if (handler) return handler } } From 40a3c92eb1dde5b1b7209f097fe9ff2310dcfb65 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 15:18:58 +0100 Subject: [PATCH 25/41] add constraints existance check in getMatchingHandler --- node.js | 1 + 1 file changed, 1 insertion(+) diff --git a/node.js b/node.js index fe6b6477..1046b42e 100644 --- a/node.js +++ b/node.js @@ -185,6 +185,7 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { } Node.prototype.getMatchingHandler = function (derivedConstraints, method) { + if (derivedConstraints && this.kConstraints.length) { var constraints, handler for (var i = 0; i < this.kConstraints.length; i++) { constraints = {} From a1bf4963b14526d00c89a5c98f2fdee10a821767 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 15:19:14 +0100 Subject: [PATCH 26/41] remove comment --- node.js | 1 - 1 file changed, 1 deletion(-) diff --git a/node.js b/node.js index 1046b42e..3cf5ba2b 100644 --- a/node.js +++ b/node.js @@ -162,7 +162,6 @@ Node.prototype.setConstraintsHandler = function (constraints, method, handler, p assert(handlers[method] === null, `There is already a handler with constraints '${JSON.stringify(constraints)}' and method '${method}'`) // Update kConstraints with new constraint keys for this node - // Object.keys(constraints).forEach(kConstraint => this.kConstraints.add(kConstraint)) this.kConstraints.push(Object.keys(constraints)) handlers[method] = { From 1c49cac836bee8935d335f0ec886d5c34143353c Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:21:25 +0100 Subject: [PATCH 27/41] revert to previous strategy format & remove deriveConstraint functions --- lib/strategies/accept-host.js | 14 ++++---------- lib/strategies/accept-version.js | 13 ++++--------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js index 749922f7..f703f1f5 100644 --- a/lib/strategies/accept-host.js +++ b/lib/strategies/accept-host.js @@ -1,8 +1,5 @@ 'use strict' -// TODO: Add regex support -function acceptHost() {} - function HostStore() { let hosts = {} return { @@ -13,10 +10,7 @@ function HostStore() { } } -acceptHost.prototype.name = 'host' -acceptHost.prototype.storage = HostStore -acceptHost.prototype.deriveConstraint = function (req, ctx) { - return req.headers['host'] -} - -module.exports = acceptHost \ No newline at end of file +module.exports = { + name: 'host', + storage: HostStore, +} \ No newline at end of file diff --git a/lib/strategies/accept-version.js b/lib/strategies/accept-version.js index 2f055d41..0a3bea97 100644 --- a/lib/strategies/accept-version.js +++ b/lib/strategies/accept-version.js @@ -2,12 +2,7 @@ const SemVerStore = require('semver-store') -function acceptVersion() { } - -acceptVersion.prototype.name = 'version' -acceptVersion.prototype.storage = SemVerStore -acceptVersion.prototype.deriveConstraint = function (req, ctx) { - return req.headers['version'] -} - -module.exports = acceptVersion \ No newline at end of file +module.exports = { + name: 'version', + storage: SemVerStore, +} \ No newline at end of file From 6abe2792bdf97ac8a590719271505502c589fa12 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:21:44 +0100 Subject: [PATCH 28/41] add regex matching for host store --- lib/strategies/accept-host.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js index f703f1f5..441a3d5a 100644 --- a/lib/strategies/accept-host.js +++ b/lib/strategies/accept-host.js @@ -1,10 +1,29 @@ 'use strict' function HostStore() { - let hosts = {} + var hosts = {} + var regexHosts = [] return { - get: (host) => { return hosts[host] || null }, - set: (host, store) => { hosts[host] = store }, + get: (host) => { + var exact = hosts[host] + if (exact) { + return exact + } + var item + for (let i = 0; i < regexHosts.length; i++) { + item = regexHosts[i] + if (item.host.match(host)) { + return item.store + } + } + }, + set: (host, store) => { + if (typeof host === RegExp) { + regexHosts.push({ host, store }) + } else { + hosts[host] = store + } + }, del: (host) => { delete hosts[host] }, empty: () => { hosts = {} } } From 35e9da8b75cec406fd4781f7ccc5ed434e295594 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:22:16 +0100 Subject: [PATCH 29/41] add strategyObjectToPrototype function --- lib/accept-constraints.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 65b32277..f993eff3 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -47,3 +47,11 @@ module.exports = (customStrategies) => { } } } + +function strategyObjectToPrototype(strategy) { + const strategyPrototype = function() {} + strategyPrototype.prototype.name = strategy.name + strategyPrototype.prototype.storage = strategy.storage + strategyPrototype.prototype.deriveConstraint = strategy.deriveConstraint + return new strategyPrototype() +} From 65bfdcfee70dc6bb5f9babad22de9d82fbb84872 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:22:40 +0100 Subject: [PATCH 30/41] convert stratgies to prototype format --- lib/accept-constraints.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index f993eff3..7be503b8 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -8,22 +8,24 @@ const acceptHostStrategy = require('./strategies/accept-host') const DEFAULT_STRATEGIES_NAMES = ['version', 'host'] module.exports = (customStrategies) => { - const strategies = [ - new acceptVersionStrategy(), - new acceptHostStrategy() - ] + const strategiesObject = { + version: strategyObjectToPrototype(acceptVersionStrategy), + host: strategyObjectToPrototype(acceptHostStrategy), + } if (customStrategies) { - for (var i = 0; i < customStrategies.length; i++) { - const strategy = new customStrategies[i]() - if (DEFAULT_STRATEGIES_NAMES.indexOf(strategy.name) !== -1) { - strategies[i] = strategy - } else { - strategies.push(strategy) - } + var kCustomStrategies = Object.keys(customStrategies) + var strategy + for (var i = 0; i < kCustomStrategies.length; i++) { + strategy = strategyObjectToPrototype(customStrategies[kCustomStrategies[i]]) + strategy.isCustom = true + strategiesObject[strategy.name] = strategy } } + // Convert to array for faster processing inside deriveConstraints + const strategies = Object.values(strategiesObject) + return { storage: function () { const stores = {} From ce44dcb68af706d286532af8aec1bd382e63c89b Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:23:13 +0100 Subject: [PATCH 31/41] inline constraint derivation for default strategies inside deriveConstraints function for faster processing --- lib/accept-constraints.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 7be503b8..009d3440 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -35,9 +35,19 @@ module.exports = (customStrategies) => { return ConstraintsStore(stores) }, deriveConstraints: function (req, ctx) { + const version = req.headers['accept-version'] + const host = req.headers['host'] const derivedConstraints = {} - var value, hasConstraint = false - for (var i = 0; i < strategies.length; i++) { + + var hasConstraint = false + if (version) { + hasConstraint = true + derivedConstraints.version = version + } + if (host) { + hasConstraint = true + derivedConstraints.host = host + } value = strategies[i].deriveConstraint(req, ctx) if (value) { hasConstraint = true From 0e0407722841f6d67c651bd42c91d56afee3a6d1 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:24:18 +0100 Subject: [PATCH 32/41] support conditional constraints derivation for custom strategies only when provided for faster processing --- lib/accept-constraints.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 009d3440..484c7341 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -48,10 +48,17 @@ module.exports = (customStrategies) => { hasConstraint = true derivedConstraints.host = host } - value = strategies[i].deriveConstraint(req, ctx) - if (value) { - hasConstraint = true - derivedConstraints[strategies[i].name] = value + + if (customStrategies) { + var value + for (var i = 0; i < strategies.length; i++) { + if (strategies[i].isCustom) { + value = strategies[i].deriveConstraint(req, ctx) + if (value) { + hasConstraint = true + derivedConstraints[strategies[i].name] = value + } + } } } From 8c663096019c8dced1e2453be00a64a413738c07 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:24:34 +0100 Subject: [PATCH 33/41] remove unused array --- lib/accept-constraints.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 484c7341..264d8647 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -5,8 +5,6 @@ const ConstraintsStore = require('./constraints-store') const acceptVersionStrategy = require('./strategies/accept-version') const acceptHostStrategy = require('./strategies/accept-host') -const DEFAULT_STRATEGIES_NAMES = ['version', 'host'] - module.exports = (customStrategies) => { const strategiesObject = { version: strategyObjectToPrototype(acceptVersionStrategy), From ce0260da03dff751230ad26f0f2f5614051a2534 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:25:08 +0100 Subject: [PATCH 34/41] replace this.getHandler call for faster processing --- node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js b/node.js index 3cf5ba2b..fd4bff7b 100644 --- a/node.js +++ b/node.js @@ -196,7 +196,7 @@ Node.prototype.getMatchingHandler = function (derivedConstraints, method) { } } - return this.getHandler(method) + return this.handlers[method] } Node.prototype.prettyPrint = function (prefix, tail) { From 7d7e0f2f37c16a67fe22b616c387a39a4af23563 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Thu, 15 Oct 2020 16:25:31 +0100 Subject: [PATCH 35/41] replace let with var --- lib/strategies/accept-host.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js index 441a3d5a..4b296698 100644 --- a/lib/strategies/accept-host.js +++ b/lib/strategies/accept-host.js @@ -10,7 +10,7 @@ function HostStore() { return exact } var item - for (let i = 0; i < regexHosts.length; i++) { + for (var i = 0; i < regexHosts.length; i++) { item = regexHosts[i] if (item.host.match(host)) { return item.store From 52a3479993d8c39b3d04b7893d79b595d93bb9c8 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 18 Oct 2020 17:06:24 +0100 Subject: [PATCH 36/41] update bench.js file to use null instead of undefined --- bench.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bench.js b/bench.js index e9cf5453..a9227222 100644 --- a/bench.js +++ b/bench.js @@ -50,22 +50,22 @@ suite findMyWay.lookup({ method: 'GET', url: '/', headers: { 'accept-version': '1.x', host: 'google.com' } }, null) }) .add('find static route', function () { - findMyWay.find('GET', '/', undefined) + findMyWay.find('GET', '/', null) }) .add('find dynamic route', function () { - findMyWay.find('GET', '/user/tomas', undefined) + findMyWay.find('GET', '/user/tomas', null) }) .add('find dynamic multi-parametric route', function () { - findMyWay.find('GET', '/customer/john-doe', undefined) + findMyWay.find('GET', '/customer/john-doe', null) }) .add('find dynamic multi-parametric route with regex', function () { - findMyWay.find('GET', '/at/12h00m', undefined) + findMyWay.find('GET', '/at/12h00m', null) }) .add('find long static route', function () { - findMyWay.find('GET', '/abc/def/ghi/lmn/opq/rst/uvz', undefined) + findMyWay.find('GET', '/abc/def/ghi/lmn/opq/rst/uvz', null) }) .add('find long dynamic route', function () { - findMyWay.find('GET', '/user/qwertyuiopasdfghjklzxcvbnm/static', undefined) + findMyWay.find('GET', '/user/qwertyuiopasdfghjklzxcvbnm/static', null) }) .add('find static versioned route', function () { findMyWay.find('GET', '/', { version: '1.x'} ) From df674d0c191be0dd84bd26455a20173f29759f4e Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 18 Oct 2020 17:08:19 +0100 Subject: [PATCH 37/41] update Node.getMatchingHandler to add hasConstraint boolean --- node.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/node.js b/node.js index fd4bff7b..bb54f032 100644 --- a/node.js +++ b/node.js @@ -184,15 +184,23 @@ Node.prototype.getConstraintsHandler = function (constraints, method) { } Node.prototype.getMatchingHandler = function (derivedConstraints, method) { - if (derivedConstraints && this.kConstraints.length) { - var constraints, handler - for (var i = 0; i < this.kConstraints.length; i++) { - constraints = {} - for (var j = 0; j < this.kConstraints[i].length; j++) { - constraints[this.kConstraints[i][j]] = derivedConstraints[this.kConstraints[i][j]] + if (derivedConstraints) { + if (this.kConstraints.length) { + var constraints, handler, hasConstraint + for (var i = 0; i < this.kConstraints.length; i++) { + hasConstraint = false + constraints = {} + for (var j = 0; j < this.kConstraints[i].length; j++) { + if (derivedConstraints[this.kConstraints[i][j]]) { + hasConstraint = true + constraints[this.kConstraints[i][j]] = derivedConstraints[this.kConstraints[i][j]] + } + } + if (hasConstraint) { + handler = this.getConstraintsHandler(constraints, method) + if (handler) return handler + } } - handler = this.getConstraintsHandler(constraints, method) - if (handler) return handler } } From f809308e3df77df3c6be6509a3e12063a3b5b0d8 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 18 Oct 2020 17:18:21 +0100 Subject: [PATCH 38/41] update Node constructor options to support kConstraints --- index.js | 1 + node.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 048fc5c7..d42feab6 100644 --- a/index.js +++ b/index.js @@ -227,6 +227,7 @@ Router.prototype._insert = function _insert (method, path, kind, params, handler kind: currentNode.kind, handlers: new Node.Handlers(currentNode.handlers), regex: currentNode.regex, + kConstraints: currentNode.kConstraints, constraints: currentNode.constraintsStorage } ) diff --git a/node.js b/node.js index bb54f032..fd78e53e 100644 --- a/node.js +++ b/node.js @@ -26,7 +26,7 @@ function Node(options) { this.wildcardChild = null this.parametricBrother = null // kConstraints allows us to know which constraints we need to extract from the request - this.kConstraints = [] + this.kConstraints = options.kConstraints || [] this.constraintsStorage = options.constraints } From 159d94132f1769b4aafefed7857da5929129a53d Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 18 Oct 2020 17:20:35 +0100 Subject: [PATCH 39/41] validate custom strategies format --- lib/accept-constraints.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 264d8647..3e4f9fc9 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -15,6 +15,9 @@ module.exports = (customStrategies) => { var kCustomStrategies = Object.keys(customStrategies) var strategy for (var i = 0; i < kCustomStrategies.length; i++) { + assert(typeof strategy.name === 'string' && strategy.name !== '', `strategy.name is required.`) + assert(strategy.storage && typeof strategy.storage === 'function', `strategy.storage function is required.`) + assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', `strategy.deriveConstraint function is required.`) strategy = strategyObjectToPrototype(customStrategies[kCustomStrategies[i]]) strategy.isCustom = true strategiesObject[strategy.name] = strategy From 6f83041c3d283b4bab67d6693124eddbb16f2147 Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 18 Oct 2020 17:24:27 +0100 Subject: [PATCH 40/41] optimize performance by inlining default constraints derivation and dynamically building deriveConstraints function in case of custom strategies --- lib/accept-constraints.js | 45 +++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 3e4f9fc9..0b457b30 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -27,7 +27,7 @@ module.exports = (customStrategies) => { // Convert to array for faster processing inside deriveConstraints const strategies = Object.values(strategiesObject) - return { + const acceptConstraints = { storage: function () { const stores = {} for (var i = 0; i < strategies.length; i++) { @@ -35,37 +35,46 @@ module.exports = (customStrategies) => { } return ConstraintsStore(stores) }, - deriveConstraints: function (req, ctx) { - const version = req.headers['accept-version'] - const host = req.headers['host'] + deriveConstraints: function deriveConstraints(req, ctx) { const derivedConstraints = {} - var hasConstraint = false + + const version = req.headers['accept-version'] if (version) { hasConstraint = true derivedConstraints.version = version } + + const host = req.headers.host if (host) { hasConstraint = true derivedConstraints.host = host } - - if (customStrategies) { - var value - for (var i = 0; i < strategies.length; i++) { - if (strategies[i].isCustom) { - value = strategies[i].deriveConstraint(req, ctx) - if (value) { - hasConstraint = true - derivedConstraints[strategies[i].name] = value - } - } - } - } + // custom strategies insertion position return hasConstraint ? derivedConstraints : null } } + + if (customStrategies) { + var code = acceptConstraints.deriveConstraints.toString() + var customStrategiesCode = + `var value + for (var i = 0; i < strategies.length; i++) { + if (strategies[i].isCustom) { + value = strategies[i].deriveConstraint(req, ctx) + if (value) { + hasConstraint = true + derivedConstraints[strategies[i].name] = value + } + } + } + ` + code = code.replace('// custom strategies insertion position', customStrategiesCode) + acceptConstraints.deriveConstraints = new Function(code) // eslint-disable-line + } + + return acceptConstraints } function strategyObjectToPrototype(strategy) { From bf4a5e1187e5b42c91079d914479825d4ce9b72b Mon Sep 17 00:00:00 2001 From: Ayoub El Khattabi Date: Sun, 25 Oct 2020 13:57:24 +0100 Subject: [PATCH 41/41] lint code and update tests --- bench.js | 7 +- lib/accept-constraints.js | 30 +++--- lib/strategies/accept-host.js | 10 +- lib/strategies/accept-version.js | 4 +- node.js | 2 +- test/issue-154.test.js | 6 +- test/issue-93.test.js | 12 +-- test/server.test.js | 2 +- test/version.custom-versioning.test.js | 21 ++-- test/version.default-versioning.test.js | 122 ++++++++++++------------ 10 files changed, 110 insertions(+), 106 deletions(-) diff --git a/bench.js b/bench.js index a9227222..4df8f819 100644 --- a/bench.js +++ b/bench.js @@ -20,8 +20,9 @@ findMyWay.on('GET', '/user/:id/static', () => true) findMyWay.on('GET', '/customer/:name-:surname', () => true) findMyWay.on('GET', '/at/:hour(^\\d+)h:minute(^\\d+)m', () => true) findMyWay.on('GET', '/abc/def/ghi/lmn/opq/rst/uvz', () => true) -findMyWay.on('GET', '/', { constraints: { version: '1.2.0'} }, () => true) +findMyWay.on('GET', '/', { constraints: { version: '1.2.0' } }, () => true) +console.log(findMyWay.routes) console.log('Routes registered successfully...') suite @@ -68,10 +69,10 @@ suite findMyWay.find('GET', '/user/qwertyuiopasdfghjklzxcvbnm/static', null) }) .add('find static versioned route', function () { - findMyWay.find('GET', '/', { version: '1.x'} ) + findMyWay.find('GET', '/', { version: '1.x' }) }) .add('find static constrained (version & host) route', function () { - findMyWay.find('GET', '/', { version: '1.x', host: 'google.com'} ) + findMyWay.find('GET', '/', { version: '1.x', host: 'google.com' }) }) .on('cycle', function (event) { console.log(String(event.target)) diff --git a/lib/accept-constraints.js b/lib/accept-constraints.js index 0b457b30..594d3200 100644 --- a/lib/accept-constraints.js +++ b/lib/accept-constraints.js @@ -4,21 +4,23 @@ const ConstraintsStore = require('./constraints-store') const acceptVersionStrategy = require('./strategies/accept-version') const acceptHostStrategy = require('./strategies/accept-host') +const assert = require('assert') module.exports = (customStrategies) => { const strategiesObject = { version: strategyObjectToPrototype(acceptVersionStrategy), - host: strategyObjectToPrototype(acceptHostStrategy), + host: strategyObjectToPrototype(acceptHostStrategy) } if (customStrategies) { var kCustomStrategies = Object.keys(customStrategies) var strategy for (var i = 0; i < kCustomStrategies.length; i++) { - assert(typeof strategy.name === 'string' && strategy.name !== '', `strategy.name is required.`) - assert(strategy.storage && typeof strategy.storage === 'function', `strategy.storage function is required.`) - assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', `strategy.deriveConstraint function is required.`) - strategy = strategyObjectToPrototype(customStrategies[kCustomStrategies[i]]) + strategy = customStrategies[kCustomStrategies[i]] + assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.') + assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.') + assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.') + strategy = strategyObjectToPrototype(strategy) strategy.isCustom = true strategiesObject[strategy.name] = strategy } @@ -35,10 +37,10 @@ module.exports = (customStrategies) => { } return ConstraintsStore(stores) }, - deriveConstraints: function deriveConstraints(req, ctx) { + deriveConstraints: function deriveConstraints (req, ctx) { const derivedConstraints = {} var hasConstraint = false - + const version = req.headers['accept-version'] if (version) { hasConstraint = true @@ -58,7 +60,7 @@ module.exports = (customStrategies) => { if (customStrategies) { var code = acceptConstraints.deriveConstraints.toString() - var customStrategiesCode = + var customStrategiesCode = `var value for (var i = 0; i < strategies.length; i++) { if (strategies[i].isCustom) { @@ -77,10 +79,10 @@ module.exports = (customStrategies) => { return acceptConstraints } -function strategyObjectToPrototype(strategy) { - const strategyPrototype = function() {} - strategyPrototype.prototype.name = strategy.name - strategyPrototype.prototype.storage = strategy.storage - strategyPrototype.prototype.deriveConstraint = strategy.deriveConstraint - return new strategyPrototype() +function strategyObjectToPrototype (strategy) { + const StrategyPrototype = function () {} + StrategyPrototype.prototype.name = strategy.name + StrategyPrototype.prototype.storage = strategy.storage + StrategyPrototype.prototype.deriveConstraint = strategy.deriveConstraint + return new StrategyPrototype() } diff --git a/lib/strategies/accept-host.js b/lib/strategies/accept-host.js index 4b296698..c65ea224 100644 --- a/lib/strategies/accept-host.js +++ b/lib/strategies/accept-host.js @@ -1,6 +1,6 @@ 'use strict' -function HostStore() { +function HostStore () { var hosts = {} var regexHosts = [] return { @@ -10,7 +10,7 @@ function HostStore() { return exact } var item - for (var i = 0; i < regexHosts.length; i++) { + for (var i = 0; i < regexHosts.length; i++) { item = regexHosts[i] if (item.host.match(host)) { return item.store @@ -18,7 +18,7 @@ function HostStore() { } }, set: (host, store) => { - if (typeof host === RegExp) { + if (host instanceof RegExp) { regexHosts.push({ host, store }) } else { hosts[host] = store @@ -31,5 +31,5 @@ function HostStore() { module.exports = { name: 'host', - storage: HostStore, -} \ No newline at end of file + storage: HostStore +} diff --git a/lib/strategies/accept-version.js b/lib/strategies/accept-version.js index 0a3bea97..0b070f29 100644 --- a/lib/strategies/accept-version.js +++ b/lib/strategies/accept-version.js @@ -4,5 +4,5 @@ const SemVerStore = require('semver-store') module.exports = { name: 'version', - storage: SemVerStore, -} \ No newline at end of file + storage: SemVerStore +} diff --git a/node.js b/node.js index fd78e53e..f872cbd9 100644 --- a/node.js +++ b/node.js @@ -13,7 +13,7 @@ const types = { MULTI_PARAM: 4 } -function Node(options) { +function Node (options) { // former arguments order: prefix, children, kind, handlers, regex, constraints options = options || {} this.prefix = options.prefix || '/' diff --git a/test/issue-154.test.js b/test/issue-154.test.js index 8e7f8783..62bc1bbf 100644 --- a/test/issue-154.test.js +++ b/test/issue-154.test.js @@ -11,12 +11,12 @@ test('Should throw when not sending a string', t => { const findMyWay = FindMyWay() t.throws(() => { - findMyWay.on('GET', '/t1', { version: 42 }, noop) + findMyWay.on('GET', '/t1', { constraints: { version: 42 } }, noop) }) t.throws(() => { - findMyWay.on('GET', '/t2', { version: null }, noop) + findMyWay.on('GET', '/t2', { constraints: { version: null } }, noop) }) t.throws(() => { - findMyWay.on('GET', '/t2', { version: true }, noop) + findMyWay.on('GET', '/t2', { constraints: { version: true } }, noop) }) }) diff --git a/test/issue-93.test.js b/test/issue-93.test.js index c22aad55..3d019637 100644 --- a/test/issue-93.test.js +++ b/test/issue-93.test.js @@ -10,11 +10,11 @@ test('Should keep semver store when split node', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/t1', { version: '1.0.0' }, noop) - findMyWay.on('GET', '/t2', { version: '2.1.0' }, noop) + findMyWay.on('GET', '/t1', { constraints: { version: '1.0.0' } }, noop) + findMyWay.on('GET', '/t2', { constraints: { version: '2.1.0' } }, noop) - t.ok(findMyWay.find('GET', '/t1', '1.0.0')) - t.ok(findMyWay.find('GET', '/t2', '2.x')) - t.notOk(findMyWay.find('GET', '/t1', '2.x')) - t.notOk(findMyWay.find('GET', '/t2', '1.0.0')) + t.ok(findMyWay.find('GET', '/t1', { version: '1.0.0' })) + t.ok(findMyWay.find('GET', '/t2', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/t1', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/t2', { version: '1.0.0' })) }) diff --git a/test/server.test.js b/test/server.test.js index e033e41b..b7b3be98 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -270,7 +270,7 @@ test('versioned routes', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/test', { version: '1.2.3' }, (req, res, params) => { + findMyWay.on('GET', '/test', { constraints: { version: '1.2.3' } }, (req, res, params) => { res.end('ok') }) diff --git a/test/version.custom-versioning.test.js b/test/version.custom-versioning.test.js index d4b18e28..1feedeb5 100644 --- a/test/version.custom-versioning.test.js +++ b/test/version.custom-versioning.test.js @@ -3,9 +3,10 @@ const t = require('tap') const test = t.test const FindMyWay = require('../') -const noop = () => {} +const noop = () => { } const customVersioning = { + name: 'version', // storage factory storage: function () { let versions = {} @@ -16,7 +17,7 @@ const customVersioning = { empty: () => { versions = {} } } }, - deriveVersion: (req, ctx) => { + deriveConstraint: (req, ctx) => { return req.headers.accept } } @@ -24,14 +25,14 @@ const customVersioning = { test('A route could support multiple versions (find) / 1', t => { t.plan(5) - const findMyWay = FindMyWay({ versioning: customVersioning }) + const findMyWay = FindMyWay({ constrainingStrategies: { version: customVersioning } }) - findMyWay.on('GET', '/', { version: 'application/vnd.example.api+json;version=2' }, noop) - findMyWay.on('GET', '/', { version: 'application/vnd.example.api+json;version=3' }, noop) + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, noop) + findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, noop) - t.ok(findMyWay.find('GET', '/', 'application/vnd.example.api+json;version=2')) - t.ok(findMyWay.find('GET', '/', 'application/vnd.example.api+json;version=3')) - t.notOk(findMyWay.find('GET', '/', 'application/vnd.example.api+json;version=4')) - t.notOk(findMyWay.find('GET', '/', 'application/vnd.example.api+json;version=5')) - t.notOk(findMyWay.find('GET', '/', 'application/vnd.example.api+json;version=6')) + t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=2' })) + t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=3' })) + t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=4' })) + t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=5' })) + t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=6' })) }) diff --git a/test/version.default-versioning.test.js b/test/version.default-versioning.test.js index 497f641e..f2998c55 100644 --- a/test/version.default-versioning.test.js +++ b/test/version.default-versioning.test.js @@ -3,23 +3,23 @@ const t = require('tap') const test = t.test const FindMyWay = require('../') -const noop = () => {} +const noop = () => { } test('A route could support multiple versions (find) / 1', t => { t.plan(7) const findMyWay = FindMyWay() - findMyWay.on('GET', '/', { version: '1.2.3' }, noop) - findMyWay.on('GET', '/', { version: '3.2.0' }, noop) + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, noop) - t.ok(findMyWay.find('GET', '/', '1.x')) - t.ok(findMyWay.find('GET', '/', '1.2.3')) - t.ok(findMyWay.find('GET', '/', '3.x')) - t.ok(findMyWay.find('GET', '/', '3.2.0')) - t.notOk(findMyWay.find('GET', '/', '2.x')) - t.notOk(findMyWay.find('GET', '/', '2.3.4')) - t.notOk(findMyWay.find('GET', '/', '3.2.1')) + t.ok(findMyWay.find('GET', '/', { version: '1.x' })) + t.ok(findMyWay.find('GET', '/', { version: '1.2.3' })) + t.ok(findMyWay.find('GET', '/', { version: '3.x' })) + t.ok(findMyWay.find('GET', '/', { version: '3.2.0' })) + t.notOk(findMyWay.find('GET', '/', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/', { version: '2.3.4' })) + t.notOk(findMyWay.find('GET', '/', { version: '3.2.1' })) }) test('A route could support multiple versions (find) / 2', t => { @@ -27,16 +27,16 @@ test('A route could support multiple versions (find) / 2', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/test', { version: '1.2.3' }, noop) - findMyWay.on('GET', '/test', { version: '3.2.0' }, noop) + findMyWay.on('GET', '/test', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test', { constraints: { version: '3.2.0' } }, noop) - t.ok(findMyWay.find('GET', '/test', '1.x')) - t.ok(findMyWay.find('GET', '/test', '1.2.3')) - t.ok(findMyWay.find('GET', '/test', '3.x')) - t.ok(findMyWay.find('GET', '/test', '3.2.0')) - t.notOk(findMyWay.find('GET', '/test', '2.x')) - t.notOk(findMyWay.find('GET', '/test', '2.3.4')) - t.notOk(findMyWay.find('GET', '/test', '3.2.1')) + t.ok(findMyWay.find('GET', '/test', { version: '1.x' })) + t.ok(findMyWay.find('GET', '/test', { version: '1.2.3' })) + t.ok(findMyWay.find('GET', '/test', { version: '3.x' })) + t.ok(findMyWay.find('GET', '/test', { version: '3.2.0' })) + t.notOk(findMyWay.find('GET', '/test', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/test', { version: '2.3.4' })) + t.notOk(findMyWay.find('GET', '/test', { version: '3.2.1' })) }) test('A route could support multiple versions (find) / 3', t => { @@ -44,20 +44,20 @@ test('A route could support multiple versions (find) / 3', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/test/:id/hello', { version: '1.2.3' }, noop) - findMyWay.on('GET', '/test/:id/hello', { version: '3.2.0' }, noop) - findMyWay.on('GET', '/test/name/hello', { version: '4.0.0' }, noop) - - t.ok(findMyWay.find('GET', '/test/1234/hello', '1.x')) - t.ok(findMyWay.find('GET', '/test/1234/hello', '1.2.3')) - t.ok(findMyWay.find('GET', '/test/1234/hello', '3.x')) - t.ok(findMyWay.find('GET', '/test/1234/hello', '3.2.0')) - t.ok(findMyWay.find('GET', '/test/name/hello', '4.x')) - t.ok(findMyWay.find('GET', '/test/name/hello', '3.x')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '2.x')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '2.3.4')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '3.2.1')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '4.x')) + findMyWay.on('GET', '/test/:id/hello', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test/:id/hello', { constraints: { version: '3.2.0' } }, noop) + findMyWay.on('GET', '/test/name/hello', { constraints: { version: '4.0.0' } }, noop) + + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.x' })) + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.2.3' })) + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '3.x' })) + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '3.2.0' })) + t.ok(findMyWay.find('GET', '/test/name/hello', { version: '4.x' })) + t.ok(findMyWay.find('GET', '/test/name/hello', { version: '3.x' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '2.3.4' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '3.2.1' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '4.x' })) }) test('A route could support multiple versions (find) / 4', t => { @@ -65,17 +65,17 @@ test('A route could support multiple versions (find) / 4', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/test/*', { version: '1.2.3' }, noop) - findMyWay.on('GET', '/test/hello', { version: '3.2.0' }, noop) - - t.ok(findMyWay.find('GET', '/test/1234/hello', '1.x')) - t.ok(findMyWay.find('GET', '/test/1234/hello', '1.2.3')) - t.ok(findMyWay.find('GET', '/test/hello', '3.x')) - t.ok(findMyWay.find('GET', '/test/hello', '3.2.0')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '3.2.0')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '3.x')) - t.notOk(findMyWay.find('GET', '/test/1234/hello', '2.x')) - t.notOk(findMyWay.find('GET', '/test/hello', '2.x')) + findMyWay.on('GET', '/test/*', { constraints: { version: '1.2.3' } }, noop) + findMyWay.on('GET', '/test/hello', { constraints: { version: '3.2.0' } }, noop) + + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.x' })) + t.ok(findMyWay.find('GET', '/test/1234/hello', { version: '1.2.3' })) + t.ok(findMyWay.find('GET', '/test/hello', { version: '3.x' })) + t.ok(findMyWay.find('GET', '/test/hello', { version: '3.2.0' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '3.2.0' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '3.x' })) + t.notOk(findMyWay.find('GET', '/test/1234/hello', { version: '2.x' })) + t.notOk(findMyWay.find('GET', '/test/hello', { version: '2.x' })) }) test('A route could support multiple versions (find) / 5', t => { @@ -83,10 +83,10 @@ test('A route could support multiple versions (find) / 5', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/', { version: '1.2.3' }, () => false) - findMyWay.on('GET', '/', { version: '3.2.0' }, () => true) + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, () => false) + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, () => true) - t.ok(findMyWay.find('GET', '/', '*').handler()) + t.ok(findMyWay.find('GET', '/', { version: '*' }).handler()) }) test('Find with a version but without versioned routes', t => { @@ -96,7 +96,7 @@ test('Find with a version but without versioned routes', t => { findMyWay.on('GET', '/', noop) - t.notOk(findMyWay.find('GET', '/', '1.x')) + t.notOk(findMyWay.find('GET', '/', { version: '1.x' })) }) test('A route could support multiple versions (lookup)', t => { @@ -109,12 +109,12 @@ test('A route could support multiple versions (lookup)', t => { } }) - findMyWay.on('GET', '/', { version: '1.2.3' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, (req, res) => { const versions = ['1.x', '1.2.3'] t.ok(versions.indexOf(req.headers['accept-version']) > -1) }) - findMyWay.on('GET', '/', { version: '3.2.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, (req, res) => { const versions = ['3.x', '3.2.0'] t.ok(versions.indexOf(req.headers['accept-version']) > -1) }) @@ -167,31 +167,31 @@ test('It should always choose the highest version of a route', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/', { version: '2.3.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '2.3.0' } }, (req, res) => { t.fail('We should not be here') }) - findMyWay.on('GET', '/', { version: '2.4.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '2.4.0' } }, (req, res) => { t.pass('Yeah!') }) - findMyWay.on('GET', '/', { version: '3.3.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '3.3.0' } }, (req, res) => { t.pass('Yeah!') }) - findMyWay.on('GET', '/', { version: '3.2.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '3.2.0' } }, (req, res) => { t.fail('We should not be here') }) - findMyWay.on('GET', '/', { version: '3.2.2' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '3.2.2' } }, (req, res) => { t.fail('We should not be here') }) - findMyWay.on('GET', '/', { version: '4.4.0' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '4.4.0' } }, (req, res) => { t.fail('We should not be here') }) - findMyWay.on('GET', '/', { version: '4.3.2' }, (req, res) => { + findMyWay.on('GET', '/', { constraints: { version: '4.3.2' } }, (req, res) => { t.pass('Yeah!') }) @@ -220,7 +220,7 @@ test('Declare the same route with and without version', t => { const findMyWay = FindMyWay() findMyWay.on('GET', '/', noop) - findMyWay.on('GET', '/', { version: '1.2.0' }, noop) + findMyWay.on('GET', '/', { constraints: { version: '1.2.0' } }, noop) t.ok(findMyWay.find('GET', '/', '1.x')) t.ok(findMyWay.find('GET', '/')) @@ -231,12 +231,12 @@ test('It should throw if you declare multiple times the same route', t => { const findMyWay = FindMyWay() - findMyWay.on('GET', '/', { version: '1.2.3' }, noop) + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) try { - findMyWay.on('GET', '/', { version: '1.2.3' }, noop) + findMyWay.on('GET', '/', { constraints: { version: '1.2.3' } }, noop) t.fail('It should throw') } catch (err) { - t.is(err.message, 'Method \'GET\' already declared for route \'/\' version \'1.2.3\'') + t.is(err.message, 'Method \'GET\' already declared for route \'/\' with constraints \'{"version":"1.2.3"}\'') } })