Skip to content

Commit

Permalink
Ensure constrained routes are matched before unconstrained routes reg…
Browse files Browse the repository at this point in the history
…ardless of insertion order
  • Loading branch information
airhorns committed Feb 1, 2021
1 parent d413ebc commit f993daa
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 16 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ findMyWay.on('GET', '/', { constraints: { host: 'example.com' } }, (req, res) =>

Constraints can be combined, and route handlers will only match if __all__ of the constraints for the handler match the request. `find-my-way` does a boolean AND with each route constraint, not an OR.

`find-my-way` will try to match the most constrained handlers first before handler with fewer or no constraints.

<a name="custom-constraint-strategies"></a>
### Custom Constraint Strategies
Expand Down Expand Up @@ -306,6 +307,8 @@ and the URL of the incoming request is /33/foo/bar,
the second route will be matched because the first chunk (33) matches the static chunk.
If the URL would have been /32/foo/bar, the first route would have been matched.

Once a url has been matched, `find-my-way` will figure out which handler registered for that path matches the request if there are any constraints. `find-my-way` will check the most constrained handlers first, which means the handlers with the most keys in the `constraints` object.

<a name="supported-methods"></a>
##### Supported methods
The router is able to route all HTTP methods defined by [`http` core module](https://nodejs.org/api/http.html#http_http_methods).
Expand Down
2 changes: 2 additions & 0 deletions node.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ Node.prototype.addHandler = function (handler, params, store, constraints) {
}

this.handlers.push(handlerObject)
// Sort the most constrained handlers to the front of the list of handlers so they are tested first.
this.handlers.sort((a, b) => Object.keys(a.constraints).length - Object.keys(b.constraints).length)

if (Object.keys(constraints).length > 0) {
this.hasConstraints = true
Expand Down
16 changes: 0 additions & 16 deletions test/constraint.host.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,6 @@ test('A route supports wildcard host constraints', t => {
t.notOk(findMyWay.find('GET', '/', { host: 'example.com' }))
})

test('A route could support multiple host constraints while versioned', t => {
t.plan(6)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.1.0' } }, beta)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '2.1.0' } }, gamma)

t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.x' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.1.x' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.x' }).handler, gamma)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.1.x' }).handler, gamma)
t.notOk(findMyWay.find('GET', '/', { host: 'fastify.io', version: '3.x' }))
t.notOk(findMyWay.find('GET', '/', { host: 'something-else.io', version: '1.x' }))
})

test('A route supports multiple host constraints (lookup)', t => {
t.plan(4)

Expand Down
90 changes: 90 additions & 0 deletions test/constraints.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict'

const t = require('tap')
const test = t.test
const FindMyWay = require('..')
const alpha = () => { }
const beta = () => { }
const gamma = () => { }

test('A route could support multiple host constraints while versioned', t => {
t.plan(6)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.1.0' } }, beta)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '2.1.0' } }, gamma)

t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.x' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.1.x' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.x' }).handler, gamma)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.1.x' }).handler, gamma)
t.notOk(findMyWay.find('GET', '/', { host: 'fastify.io', version: '3.x' }))
t.notOk(findMyWay.find('GET', '/', { host: 'something-else.io', version: '1.x' }))
})

test('Constrained routes are matched before unconstrainted routes when the constrained route is added last', t => {
t.plan(3)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', {}, alpha)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta)

t.strictEqual(findMyWay.find('GET', '/', {}).handler, alpha)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'example.com' }).handler, alpha)
})

test('Constrained routes are matched before unconstrainted routes when the constrained route is added first', t => {
t.plan(3)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, beta)
findMyWay.on('GET', '/', {}, alpha)

t.strictEqual(findMyWay.find('GET', '/', {}).handler, alpha)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'example.com' }).handler, alpha)
})

test('Routes with multiple constraints are matched before routes with one constraint when the doubly-constrained route is added last', t => {
t.plan(3)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta)

t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, alpha)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
})

test('Routes with multiple constraints are matched before routes with one constraint when the doubly-constrained route is added first', t => {
t.plan(3)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha)

t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io' }).handler, alpha)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
})

test('Routes with multiple constraints are matched before routes with one constraint before unconstrained routes', t => {
t.plan(3)

const findMyWay = FindMyWay()

findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, beta)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, alpha)
findMyWay.on('GET', '/', { constraints: {} }, gamma)

t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.strictEqual(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
t.strictEqual(findMyWay.find('GET', '/', { host: 'example.io' }).handler, gamma)
})

0 comments on commit f993daa

Please sign in to comment.