From 4a8a41f6b69da992261db8e52d45edb25213ed80 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 5 Oct 2017 22:28:53 +0200 Subject: [PATCH] feat: treat repeating slashes as pathless paths (#13) --- dist/amd/route-node.js | 23 ++++++++++++++++------- dist/commonjs/route-node.js | 23 ++++++++++++++++------- dist/umd/route-node.js | 23 ++++++++++++++++------- modules/RouteNode.js | 28 +++++++++++++++++++--------- test/main.js | 32 ++++++++++++++++++++++++++++++-- 5 files changed, 97 insertions(+), 32 deletions(-) diff --git a/dist/amd/route-node.js b/dist/amd/route-node.js index f9f209c..a384fa6 100644 --- a/dist/amd/route-node.js +++ b/dist/amd/route-node.js @@ -761,7 +761,7 @@ var RouteNode = function () { strictQueryParams = options.strictQueryParams, strongMatching = options.strongMatching; - var matchChildren = function matchChildren(nodes, pathSegment, segments) { + var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) { var isRoot = nodes.length === 1 && nodes[0].name === ''; // for (child of node.children) { @@ -771,13 +771,20 @@ var RouteNode = function () { // Partially match path var match = void 0; var remainingPath = void 0; + var segment = pathSegment; + + if (consumedBefore === '/' && child.path === '/') { + // when we encounter repeating slashes we add the slash + // back to the URL to make it de facto pathless + segment = '/' + pathSegment; + } if (!child.children.length) { - match = child.parser.test(pathSegment, { trailingSlash: trailingSlash }); + match = child.parser.test(segment, { trailingSlash: trailingSlash }); } if (!match) { - match = child.parser.partialTest(pathSegment, { delimiter: strongMatching }); + match = child.parser.partialTest(segment, { delimiter: strongMatching }); } if (match) { @@ -787,13 +794,13 @@ var RouteNode = function () { consumedPath = consumedPath.replace(/\/$/, ''); } - remainingPath = pathSegment.replace(consumedPath, ''); + remainingPath = segment.replace(consumedPath, ''); if (trailingSlash && !child.children.length) { remainingPath = remainingPath.replace(/^\/\?/, '?'); } - var search = omit(getSearch(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); + var search = omit(getSearch(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); remainingPath = getPath(remainingPath) + (search ? '?' + search : ''); if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) { remainingPath = ''; @@ -833,7 +840,7 @@ var RouteNode = function () { } // Else: remaining path and children return { - v: matchChildren(children, remainingPath, segments) + v: matchChildren(children, remainingPath, segments, consumedPath) }; } }; @@ -920,7 +927,9 @@ var RouteNode = function () { var segmentPath = segment.parser.build(params, { ignoreSearch: true }); return segment.absolute ? segmentPath : path + segmentPath; - }, ''); + }, '') + // remove repeated slashes + .replace(/\/\/{1,}/g, '/'); var finalPath = path; diff --git a/dist/commonjs/route-node.js b/dist/commonjs/route-node.js index f62e7ca..19ae850 100644 --- a/dist/commonjs/route-node.js +++ b/dist/commonjs/route-node.js @@ -246,7 +246,7 @@ var RouteNode = function () { strictQueryParams = options.strictQueryParams, strongMatching = options.strongMatching; - var matchChildren = function matchChildren(nodes, pathSegment, segments) { + var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) { var isRoot = nodes.length === 1 && nodes[0].name === ''; // for (child of node.children) { @@ -256,13 +256,20 @@ var RouteNode = function () { // Partially match path var match = void 0; var remainingPath = void 0; + var segment = pathSegment; + + if (consumedBefore === '/' && child.path === '/') { + // when we encounter repeating slashes we add the slash + // back to the URL to make it de facto pathless + segment = '/' + pathSegment; + } if (!child.children.length) { - match = child.parser.test(pathSegment, { trailingSlash: trailingSlash }); + match = child.parser.test(segment, { trailingSlash: trailingSlash }); } if (!match) { - match = child.parser.partialTest(pathSegment, { delimiter: strongMatching }); + match = child.parser.partialTest(segment, { delimiter: strongMatching }); } if (match) { @@ -272,13 +279,13 @@ var RouteNode = function () { consumedPath = consumedPath.replace(/\/$/, ''); } - remainingPath = pathSegment.replace(consumedPath, ''); + remainingPath = segment.replace(consumedPath, ''); if (trailingSlash && !child.children.length) { remainingPath = remainingPath.replace(/^\/\?/, '?'); } - var search = (0, _searchParams.omit)((0, _searchParams.getSearch)(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); + var search = (0, _searchParams.omit)((0, _searchParams.getSearch)(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); remainingPath = (0, _searchParams.getPath)(remainingPath) + (search ? '?' + search : ''); if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) { remainingPath = ''; @@ -318,7 +325,7 @@ var RouteNode = function () { } // Else: remaining path and children return { - v: matchChildren(children, remainingPath, segments) + v: matchChildren(children, remainingPath, segments, consumedPath) }; } }; @@ -405,7 +412,9 @@ var RouteNode = function () { var segmentPath = segment.parser.build(params, { ignoreSearch: true }); return segment.absolute ? segmentPath : path + segmentPath; - }, ''); + }, '') + // remove repeated slashes + .replace(/\/\/{1,}/g, '/'); var finalPath = path; diff --git a/dist/umd/route-node.js b/dist/umd/route-node.js index 3bd9ea1..4e5889a 100644 --- a/dist/umd/route-node.js +++ b/dist/umd/route-node.js @@ -765,7 +765,7 @@ var RouteNode = function () { strictQueryParams = options.strictQueryParams, strongMatching = options.strongMatching; - var matchChildren = function matchChildren(nodes, pathSegment, segments) { + var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) { var isRoot = nodes.length === 1 && nodes[0].name === ''; // for (child of node.children) { @@ -775,13 +775,20 @@ var RouteNode = function () { // Partially match path var match = void 0; var remainingPath = void 0; + var segment = pathSegment; + + if (consumedBefore === '/' && child.path === '/') { + // when we encounter repeating slashes we add the slash + // back to the URL to make it de facto pathless + segment = '/' + pathSegment; + } if (!child.children.length) { - match = child.parser.test(pathSegment, { trailingSlash: trailingSlash }); + match = child.parser.test(segment, { trailingSlash: trailingSlash }); } if (!match) { - match = child.parser.partialTest(pathSegment, { delimiter: strongMatching }); + match = child.parser.partialTest(segment, { delimiter: strongMatching }); } if (match) { @@ -791,13 +798,13 @@ var RouteNode = function () { consumedPath = consumedPath.replace(/\/$/, ''); } - remainingPath = pathSegment.replace(consumedPath, ''); + remainingPath = segment.replace(consumedPath, ''); if (trailingSlash && !child.children.length) { remainingPath = remainingPath.replace(/^\/\?/, '?'); } - var search = omit(getSearch(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); + var search = omit(getSearch(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr)); remainingPath = getPath(remainingPath) + (search ? '?' + search : ''); if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) { remainingPath = ''; @@ -837,7 +844,7 @@ var RouteNode = function () { } // Else: remaining path and children return { - v: matchChildren(children, remainingPath, segments) + v: matchChildren(children, remainingPath, segments, consumedPath) }; } }; @@ -924,7 +931,9 @@ var RouteNode = function () { var segmentPath = segment.parser.build(params, { ignoreSearch: true }); return segment.absolute ? segmentPath : path + segmentPath; - }, ''); + }, '') + // remove repeated slashes + .replace(/\/\/{1,}/g, '/'); var finalPath = path; diff --git a/modules/RouteNode.js b/modules/RouteNode.js index a220db0..b549293 100644 --- a/modules/RouteNode.js +++ b/modules/RouteNode.js @@ -186,22 +186,30 @@ export default class RouteNode { getSegmentsMatchingPath(path, options) { const { trailingSlash, strictQueryParams, strongMatching } = options; - let matchChildren = (nodes, pathSegment, segments) => { + let matchChildren = (nodes, pathSegment, segments, consumedBefore) => { const isRoot = nodes.length === 1 && nodes[0].name === ''; // for (child of node.children) { for (let i = 0; i < nodes.length; i += 1) { const child = nodes[i]; + // Partially match path let match; let remainingPath; + let segment = pathSegment; + + if (consumedBefore === '/' && child.path === '/') { + // when we encounter repeating slashes we add the slash + // back to the URL to make it de facto pathless + segment = '/' + pathSegment; + } if (!child.children.length) { - match = child.parser.test(pathSegment, { trailingSlash }); + match = child.parser.test(segment, { trailingSlash }); } if (!match) { - match = child.parser.partialTest(pathSegment, { delimiter: strongMatching }); + match = child.parser.partialTest(segment, { delimiter: strongMatching }); } if (match) { @@ -211,14 +219,14 @@ export default class RouteNode { consumedPath = consumedPath.replace(/\/$/, ''); } - remainingPath = pathSegment.replace(consumedPath, ''); - + remainingPath = segment.replace(consumedPath, ''); + if (trailingSlash && !child.children.length) { remainingPath = remainingPath.replace(/^\/\?/, '?'); } - + const search = omit( - getSearch(pathSegment.replace(consumedPath, '')), + getSearch(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr) ); remainingPath = getPath(remainingPath) + (search ? `?${search}` : ''); @@ -245,7 +253,7 @@ export default class RouteNode { return null; } // Else: remaining path and children - return matchChildren(children, remainingPath, segments); + return matchChildren(children, remainingPath, segments, consumedPath); } } @@ -326,7 +334,9 @@ export default class RouteNode { const segmentPath = segment.parser.build(params, {ignoreSearch: true}); return segment.absolute ? segmentPath : path + segmentPath; - }, ''); + }, '') + // remove repeated slashes + .replace(/\/\/{1,}/g, '/'); let finalPath = path; diff --git a/test/main.js b/test/main.js index 581c88c..7608e85 100644 --- a/test/main.js +++ b/test/main.js @@ -313,7 +313,6 @@ describe('RouteNode', function () { withoutMeta(rootNode.matchPath('/users/list', { trailingSlash: true })).should.eql({name: 'users.list', params: {}}); withoutMeta(rootNode.matchPath('/users/list')).should.eql({name: 'users.list', params: {}}); withoutMeta(rootNode.matchPath('/users/list/', { trailingSlash: true })).should.eql({name: 'users.list', params: {}}); - should.not.exists(rootNode.matchPath('/users/list//', { trailingSlash: true })); rootNode = getRoutes(true); should.not.exists(rootNode.matchPath('/users/list')); @@ -323,7 +322,6 @@ describe('RouteNode', function () { withoutMeta(rootNode.matchPath('/')).should.eql({name: 'default', params: {}}); withoutMeta(rootNode.matchPath('', { trailingSlash: true })).should.eql({name: 'default', params: {}}); should.not.exists(rootNode.matchPath('', { trailingSlash: false })); - should.not.exists(rootNode.matchPath('/users/list//', { trailingSlash: true })); }); it('should match paths with optional trailing slashes and a non-empty root node', function () { @@ -527,6 +525,36 @@ describe('RouteNode', function () { node.buildPath('c', { c: 1 }, { trailingSlash: false }).should.eql('/?c=1'); }); + it('should remove repeated slashes when building paths', ( ) => { + + const node = new RouteNode('', '', [ + new RouteNode('a', '/', [ + new RouteNode('b', '/', [ + new RouteNode('c', '/') + ]) + ]) + ]); + + node.buildPath('a.b', {}).should.eql('/'); + node.buildPath('a.b.c', {}).should.eql('/'); + + }); + + it('should match paths with repeating slashes', ( ) => { + + const node = new RouteNode('', '', [ + new RouteNode('a', '/', [ + new RouteNode('b', '/', [ + new RouteNode('c', ':bar') + ]) + ]) + ]); + + withoutMeta(node.matchPath('/')).should.eql({ name: 'a.b', params: {}}); + withoutMeta(node.matchPath('/foo')).should.eql({ name: 'a.b.c', params: { bar: 'foo' }}); + + }); + });