Skip to content

Commit

Permalink
feat: store matched routes in request
Browse files Browse the repository at this point in the history
  • Loading branch information
ajfranzoia authored and wesleytodd committed Jan 5, 2020
1 parent c512eec commit 5e8a56e
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 16 deletions.
1 change: 1 addition & 0 deletions HISTORY.md
Expand Up @@ -8,6 +8,7 @@ unreleased
* deps: path-to-regexp@3.0.0
* deps: eslint@6.8.0
* deps: eslint-plugin-markdown@1.0.1
* Store matched routes in request #34 #86

2.0.0-alpha.1 / 2018-07-27
==========================
Expand Down
11 changes: 9 additions & 2 deletions index.js
Expand Up @@ -166,7 +166,8 @@ Router.prototype.handle = function handle(req, res, callback) {
// manage inter-router variables
var parentParams = req.params
var parentUrl = req.baseUrl || ''
var done = restore(callback, req, 'baseUrl', 'next', 'params')
var parentMatchedRoutes = req.matchedRoutes
var done = restore(callback, req, 'baseUrl', 'next', 'params', 'matchedRoutes')

// setup next layer
req.next = next
Expand Down Expand Up @@ -199,6 +200,7 @@ Router.prototype.handle = function handle(req, res, callback) {
req.baseUrl = parentUrl
req.url = protohost + removed + req.url.substr(protohost.length)
removed = ''
req.matchedRoutes = parentMatchedRoutes
}

// signal to exit router
Expand Down Expand Up @@ -287,6 +289,11 @@ Router.prototype.handle = function handle(req, res, callback) {
return next(layerError || err)
}

if (layer.path) {
req.matchedRoutes = req.matchedRoutes || []
req.matchedRoutes.push(layer.matchedPath.path)
}

if (route) {
return layer.handle_request(req, res, next)
}
Expand Down Expand Up @@ -342,7 +349,7 @@ Router.prototype.process_params = function process_params(layer, called, req, re
var params = this.params

// captured parameters from the layer, keys and values
var keys = layer.keys
var keys = layer.matchedPath && layer.matchedPath.keys

// fast track
if (!keys || keys.length === 0) {
Expand Down
49 changes: 35 additions & 14 deletions lib/layer.js
Expand Up @@ -33,18 +33,18 @@ function Layer(p, options, fn) {
return new Layer(p, options, fn)
}

debug('new %o', path)
debug('new %o', paths)
var opts = options || {}

// If not in strict allow both with or without trailing slash
var path = p
var paths = p
if (!opts.strict) {
if (!Array.isArray(path) && path !== '/' && path[path.length - 1] === '/') {
path = path.substr(0, path.length - 1)
if (!Array.isArray(paths) && paths !== '/' && paths[paths.length - 1] === '/') {
paths = paths.substr(0, paths.length - 1)
} else {
for (var i = 0; i < path.length; i++) {
if (path[i] !== '/' && path[i][path[i].length - 1] === '/') {
path[i] = path[i].substr(0, path[i].length - 1)
for (var i = 0; i < paths.length; i++) {
if (paths[i] !== '/' && paths[i][paths[i].length - 1] === '/') {
paths[i] = paths[i].substr(0, paths[i].length - 1)
}
}
}
Expand All @@ -54,11 +54,22 @@ function Layer(p, options, fn) {
this.name = fn.name || '<anonymous>'
this.params = undefined
this.path = undefined
this.regexp = pathRegexp(path, this.keys = [], opts)
this.matchedPath = undefined

// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
this.fastStar = paths === '*'
this.fastSlash = paths === '/' && opts.end === false

this.paths = (!Array.isArray(paths) ? [paths] : paths).map(function (path) {
var keys = []
var pathObj = {
path: path,
keys: keys,
regexp: pathRegexp(path, keys, opts)
}

return pathObj
})
}

/**
Expand Down Expand Up @@ -137,29 +148,39 @@ Layer.prototype.handle_request = function handle(req, res, next) {

Layer.prototype.match = function match(path) {
var match
var checkPath

if (path != null) {
// fast path non-ending match for / (any path matches)
if (this.regexp.fast_slash) {
if (this.fastSlash) {
this.params = {}
this.path = ''
this.matchedPath = this.paths[0]
return true
}

// fast path for * (everything matched in a param)
if (this.regexp.fast_star) {
if (this.fastStar) {
this.params = {'0': decode_param(path)}
this.path = path
this.matchedPath = this.paths[0]
return true
}

// match the path
match = this.regexp.exec(path)
for (var i = 0; i < this.paths.length; i++) {
checkPath = this.paths[i]
if (match = checkPath.regexp.exec(path)) {
this.matchedPath = checkPath
break
}
}
}

if (!match) {
this.params = undefined
this.path = undefined
this.matchedPath = undefined
return false
}

Expand All @@ -172,7 +193,7 @@ Layer.prototype.match = function match(path) {
var params = this.params

for (var i = 1; i < match.length; i++) {
var key = keys[i - 1]
var key = this.matchedPath.keys[i - 1]
var prop = key.name
var val = decode_param(match[i])

Expand Down
137 changes: 137 additions & 0 deletions test/router.js
Expand Up @@ -1246,6 +1246,143 @@ describe('Router', function () {
.expect(200, 'saw GET /bar', done)
})
})

describe('req.matchedRoutes', function () {
it('should be set if there is a match', function (done) {
var router = new Router()
var server = createServer(router)
var matchedRoutes

router.get('/foo', function (req, res, next) {
matchedRoutes = req.matchedRoutes
next()
})
router.use(saw)

request(server)
.get('/foo')
.expect(200, 'saw GET /foo', function (err, res) {
assert.deepEqual(matchedRoutes, ['/foo'])
done(err)
})
})

it('should be undefined if there is not a match', function (done) {
var router = new Router()
var server = createServer(router)
var matchedRoutes

router.use(function (req, res, next) {
matchedRoutes = req.matchedRoutes
next()
})

request(server)
.get('/foo')
.expect(404, function (err, res) {
assert.strictEqual(matchedRoutes, undefined)
done(err)
})
})

it('should work with sub-routers', function (done) {
var router = new Router()
var fooRouter = new Router()
var server = createServer(router)
var matchedFooRoutes
var matchedBarRoutes

router.use('/foo', function (req, res, next) {
matchedFooRoutes = req.matchedRoutes
next()
}, fooRouter)
fooRouter.get('/bar', function (req, res, next) {
matchedBarRoutes = req.matchedRoutes
next()
})
router.use(saw)

request(server)
.get('/foo/bar')
.expect(200, 'saw GET /foo/bar', function (err, res) {
assert.deepEqual(matchedFooRoutes, ['/foo'])
assert.deepEqual(matchedBarRoutes, ['/foo', '/bar'])
done(err)
})
})

it('should be undefined if sub-router did not match', function (done) {
var router = new Router()
var fooRouter = new Router()
var server = createServer(router)
var matchedRoutes

router.use('/foo', fooRouter)
fooRouter.get('/bar', function (req, res, next) {
matchedRoutes = req.matchedRoutes
next()
})
router.use(saw)

request(server)
.get('/foo/baz')
.expect(200, 'saw GET /foo/baz', function (err, res) {
assert.strictEqual(matchedRoutes, undefined)
done(err)
})
})

it('should work with regexp-defined routes', function (done) {
var router = new Router()
var server = createServer(router)
var matchedRoutes
var regexp = /fo+/

router.get(regexp, function (req, res, next) {
matchedRoutes = req.matchedRoutes
next()
})
router.use(saw)

request(server)
.get('/foo')
.expect(200, 'saw GET /foo', function (err, res) {
assert.deepEqual(matchedRoutes, [regexp])
done(err)
})
})

it('should support routes defined with arrays of paths', function (done) {
var router = new Router()
var server = createServer(router)
var matchedRoutes

router.get(['/foo', '/bar/:id'], function (req, res, next) {
matchedRoutes = req.matchedRoutes
next()
})
router.use(saw)

request(server)
.get('/foo')
.expect(200, 'saw GET /foo', function (err, res) {
if (err) {
return done(err)
}
assert.deepEqual(matchedRoutes, ['/foo'])

request(server)
.get('/bar/1')
.expect(200, 'saw GET /bar/1', function (err, res) {
if (err) {
return done(err)
}
assert.deepEqual(matchedRoutes, ['/bar/:id'])
done()
})
})
})
})
})

function helloWorld(req, res) {
Expand Down

0 comments on commit 5e8a56e

Please sign in to comment.