diff --git a/HISTORY.md b/HISTORY.md index 1e1e91c..2f7125f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,11 @@ This incorporates all changes after 1.3.5 up to 1.3.7. * Add support for returned, rejected Promises to `router.param` + * deps: path-to-regexp@6.2.0 + - Support for **named capturing groups** in paths using `RegExp`, i.e.: `/\/(?.+)/` would result in `req.params.group` being populated. + - Custom **prefix and suffix groups** using `{}`, e.g.: `/:entity{-:action}?` would match `/user` and `/user-delete`. + - Unbalanced patterns now produce an error: `/test(foo` previously worked, but now it expects `(` to be escaped for the previous behavior, e.g. `/test\\(foo`. + - Just like with parentheses since `2.0.0-beta.1`, bracket literals now require escaping, i.e.: `/user{:id}` no longer matches `/user{42}` as before unless written as `/user\\{:id\\}`. 2.0.0-beta.1 / 2020-03-29 ========================= diff --git a/lib/layer.js b/lib/layer.js index 73f7d69..b514344 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -13,7 +13,7 @@ */ var isPromise = require('is-promise') -var pathRegexp = require('path-to-regexp') +var pathToRegexp = require('path-to-regexp').pathToRegexp /** * Module variables. @@ -41,7 +41,7 @@ function Layer (path, options, fn) { this.name = fn.name || '' this.params = undefined this.path = undefined - this.regexp = pathRegexp((opts.strict ? path : loosen(path)), this.keys, opts) + this.regexp = pathToRegexp((opts.strict ? path : loosen(path)), this.keys, opts) // set fast path flags this.regexp._slash = path === '/' && opts.end === false diff --git a/package.json b/package.json index b18ef68..ba33d14 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "is-promise": "4.0.0", "methods": "~1.1.2", "parseurl": "~1.3.3", - "path-to-regexp": "3.2.0", + "path-to-regexp": "6.2.0", "setprototypeof": "1.2.0", "utils-merge": "1.0.1" }, diff --git a/test/route.js b/test/route.js index 5bfe75b..ae67fda 100644 --- a/test/route.js +++ b/test/route.js @@ -14,6 +14,7 @@ var shouldHitHandle = utils.shouldHitHandle var shouldNotHaveBody = utils.shouldNotHaveBody var shouldNotHitHandle = utils.shouldNotHitHandle +var describeNamedCaptureGroups = supportsRegExp('(?.)') ? describe : describe.skip var describePromises = global.Promise ? describe : describe.skip describe('Router', function () { @@ -687,7 +688,7 @@ describe('Router', function () { .expect(200, { 0: 's', user: 'tj', op: 'edit' }, cb) }) - it('should work inside literal paranthesis', function (done) { + it('should work inside literal parenthesis', function (done) { var router = new Router() var route = router.route('/:user\\(:op\\)') var server = createServer(router) @@ -699,6 +700,27 @@ describe('Router', function () { .expect(200, { user: 'tj', op: 'edit' }, done) }) + it('should allow matching literal parenthesis within a group', function (done) { + var cb = after(3, done) + var router = new Router() + var route = router.route('/:user([a-z\\(\\)]+)') + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/1234') + .expect(404, cb) + + request(server) + .get('/foo') + .expect(200, { user: 'foo' }, cb) + + request(server) + .get('/(foo)') + .expect(200, { user: '(foo)' }, cb) + }) + it('should work within arrays', function (done) { var cb = after(2, done) var router = new Router() @@ -860,6 +882,54 @@ describe('Router', function () { }) }) + describe('using "{:name}"', function () { + it('should allow defining custom prefixes', function (done) { + var cb = after(3, done) + var router = new Router() + var route = router.route('/{$:foo}{$:bar}?') + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/bar') + .expect(404, cb) + + request(server) + .get('/$bar') + .expect(200, { foo: 'bar' }, cb) + + request(server) + .get('/$bar$fizz') + .expect(200, { foo: 'bar', bar: 'fizz' }, cb) + }) + + it('should work in any segment', function (done) { + var cb = after(4, done) + var router = new Router() + var route = router.route('/user/:foo?{-:bar}?{-:baz}?') + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/user') + .expect(200, cb) + + request(server) + .get('/user/delete') + .expect(200, { foo: 'delete' }, cb) + + request(server) + .get('/user/delete-fizz') + .expect(200, { foo: 'delete', bar: 'fizz' }, cb) + + request(server) + .get('/user/delete-fizz-buzz') + .expect(200, { foo: 'delete', bar: 'fizz', baz: 'buzz' }, cb) + }) + }) + describe('using "(regexp)"', function () { it('should add capture group using regexp', function (done) { var cb = after(2, done) @@ -898,6 +968,108 @@ describe('Router', function () { .get('/foo:n42') .expect(200, { 0: 'foo:n42' }, cb) }) + + it('should allow optional capturing group', function (done) { + var cb = after(4, done) + var router = new Router() + var route = router.route('/user(s)?/:foo') + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/user') + .expect(404, cb) + + request(server) + .get('/users') + .expect(404, cb) + + request(server) + .get('/user/bar') + .expect(200, { foo: 'bar' }, cb) + + request(server) + .get('/users/bar') + .expect(200, { 0: 's', foo: 'bar' }, cb) + }) + + it('should work in any segment', function (done) { + var cb = after(4, done) + var router = new Router() + var route = router.route('/users/image(s)?/:foo') + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/users/image') + .expect(404, cb) + + request(server) + .get('/users/images') + .expect(404, cb) + + request(server) + .get('/users/image/1234') + .expect(200, { foo: '1234' }, cb) + + request(server) + .get('/users/images/1234') + .expect(200, { 0: 's', foo: '1234' }, cb) + }) + }) + + describeNamedCaptureGroups('using "(?)"', function () { + it('should allow defining capturing groups using regexps', function (done) { + var cb = after(3, done) + var router = new Router() + var route = router.route(/\/(?.+)/) + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/') + .expect(404, cb) + + request(server) + .get('/foo/bar') + .expect(200, { name: 'foo/bar' }, cb) + + request(server) + .get('/foo') + .expect(200, { name: 'foo' }, cb) + }) + + it('should work with multiple named groups in the same segment', function (done) { + var cb = after(5, done) + var router = new Router() + var route = router.route(/\/(?.*).(?html|pdf|json)/) + var server = createServer(router) + + route.all(sendParams) + + request(server) + .get('/foo') + .expect(404, cb) + + request(server) + .get('/foo.xml') + .expect(404, cb) + + request(server) + .get('/foo.html') + .expect(200, { filename: 'foo', ext: 'html' }, cb) + + request(server) + .get('/bar.pdf') + .expect(200, { filename: 'bar', ext: 'pdf' }, cb) + + request(server) + .get('/baz.json') + .expect(200, { filename: 'baz', ext: 'json' }, cb) + }) }) }) }) @@ -921,3 +1093,11 @@ function sendParams (req, res) { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(req.params)) } + +function supportsRegExp (source) { + try { + return RegExp(source).source !== '' + } catch (e) { + return false + } +}