diff --git a/HISTORY.md b/HISTORY.md index cdf557a..973d02a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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 2.0.0-alpha.1 / 2018-07-27 ========================== diff --git a/index.js b/index.js index 7c68516..b8f43d7 100644 --- a/index.js +++ b/index.js @@ -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 @@ -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 @@ -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) } @@ -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) { diff --git a/lib/layer.js b/lib/layer.js index e6d8576..e9e64b2 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -14,6 +14,7 @@ var pathRegexp = require('path-to-regexp') var debug = require('debug')('router:layer') +var flatten = require('array-flatten') /** * Module variables. @@ -33,18 +34,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) } } } @@ -54,11 +55,22 @@ function Layer(p, options, fn) { this.name = fn.name || '' 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] : flatten(paths)).map(function (path) { + var keys = [] + var pathObj = { + path: path, + keys: keys, + regexp: pathRegexp(path, keys, opts) + } + + return pathObj + }) } /** @@ -137,29 +149,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 } @@ -172,7 +194,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]) diff --git a/test/router.js b/test/router.js index 7c8f74d..02e56c0 100644 --- a/test/router.js +++ b/test/router.js @@ -1246,6 +1246,203 @@ 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() + }) + }) + }) + + it('should support routes defined with nested 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() + }) + }) + }) + + it('should support sibling routes at same path', function (done) { + var router = new Router() + var server = createServer(router) + var matchedRoutes1 + var matchedRoutes2 + + router.get('/foo', function (req, res, next) { + matchedRoutes1 = req.matchedRoutes + next() + }) + router.get('/foo', function (req, res, next) { + matchedRoutes2 = 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(matchedRoutes1, ['/foo']) + assert.deepEqual(matchedRoutes2, ['/foo']) + + done() + }) + }) + }) }) function helloWorld(req, res) {