Skip to content

Commit 4a8a41f

Browse files
dgieselaartroch
authored andcommitted
feat: treat repeating slashes as pathless paths (#13)
1 parent 99f9e71 commit 4a8a41f

5 files changed

Lines changed: 97 additions & 32 deletions

File tree

dist/amd/route-node.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ var RouteNode = function () {
761761
strictQueryParams = options.strictQueryParams,
762762
strongMatching = options.strongMatching;
763763

764-
var matchChildren = function matchChildren(nodes, pathSegment, segments) {
764+
var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) {
765765
var isRoot = nodes.length === 1 && nodes[0].name === '';
766766
// for (child of node.children) {
767767

@@ -771,13 +771,20 @@ var RouteNode = function () {
771771
// Partially match path
772772
var match = void 0;
773773
var remainingPath = void 0;
774+
var segment = pathSegment;
775+
776+
if (consumedBefore === '/' && child.path === '/') {
777+
// when we encounter repeating slashes we add the slash
778+
// back to the URL to make it de facto pathless
779+
segment = '/' + pathSegment;
780+
}
774781

775782
if (!child.children.length) {
776-
match = child.parser.test(pathSegment, { trailingSlash: trailingSlash });
783+
match = child.parser.test(segment, { trailingSlash: trailingSlash });
777784
}
778785

779786
if (!match) {
780-
match = child.parser.partialTest(pathSegment, { delimiter: strongMatching });
787+
match = child.parser.partialTest(segment, { delimiter: strongMatching });
781788
}
782789

783790
if (match) {
@@ -787,13 +794,13 @@ var RouteNode = function () {
787794
consumedPath = consumedPath.replace(/\/$/, '');
788795
}
789796

790-
remainingPath = pathSegment.replace(consumedPath, '');
797+
remainingPath = segment.replace(consumedPath, '');
791798

792799
if (trailingSlash && !child.children.length) {
793800
remainingPath = remainingPath.replace(/^\/\?/, '?');
794801
}
795802

796-
var search = omit(getSearch(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
803+
var search = omit(getSearch(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
797804
remainingPath = getPath(remainingPath) + (search ? '?' + search : '');
798805
if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) {
799806
remainingPath = '';
@@ -833,7 +840,7 @@ var RouteNode = function () {
833840
}
834841
// Else: remaining path and children
835842
return {
836-
v: matchChildren(children, remainingPath, segments)
843+
v: matchChildren(children, remainingPath, segments, consumedPath)
837844
};
838845
}
839846
};
@@ -920,7 +927,9 @@ var RouteNode = function () {
920927
var segmentPath = segment.parser.build(params, { ignoreSearch: true });
921928

922929
return segment.absolute ? segmentPath : path + segmentPath;
923-
}, '');
930+
}, '')
931+
// remove repeated slashes
932+
.replace(/\/\/{1,}/g, '/');
924933

925934
var finalPath = path;
926935

dist/commonjs/route-node.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ var RouteNode = function () {
246246
strictQueryParams = options.strictQueryParams,
247247
strongMatching = options.strongMatching;
248248

249-
var matchChildren = function matchChildren(nodes, pathSegment, segments) {
249+
var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) {
250250
var isRoot = nodes.length === 1 && nodes[0].name === '';
251251
// for (child of node.children) {
252252

@@ -256,13 +256,20 @@ var RouteNode = function () {
256256
// Partially match path
257257
var match = void 0;
258258
var remainingPath = void 0;
259+
var segment = pathSegment;
260+
261+
if (consumedBefore === '/' && child.path === '/') {
262+
// when we encounter repeating slashes we add the slash
263+
// back to the URL to make it de facto pathless
264+
segment = '/' + pathSegment;
265+
}
259266

260267
if (!child.children.length) {
261-
match = child.parser.test(pathSegment, { trailingSlash: trailingSlash });
268+
match = child.parser.test(segment, { trailingSlash: trailingSlash });
262269
}
263270

264271
if (!match) {
265-
match = child.parser.partialTest(pathSegment, { delimiter: strongMatching });
272+
match = child.parser.partialTest(segment, { delimiter: strongMatching });
266273
}
267274

268275
if (match) {
@@ -272,13 +279,13 @@ var RouteNode = function () {
272279
consumedPath = consumedPath.replace(/\/$/, '');
273280
}
274281

275-
remainingPath = pathSegment.replace(consumedPath, '');
282+
remainingPath = segment.replace(consumedPath, '');
276283

277284
if (trailingSlash && !child.children.length) {
278285
remainingPath = remainingPath.replace(/^\/\?/, '?');
279286
}
280287

281-
var search = (0, _searchParams.omit)((0, _searchParams.getSearch)(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
288+
var search = (0, _searchParams.omit)((0, _searchParams.getSearch)(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
282289
remainingPath = (0, _searchParams.getPath)(remainingPath) + (search ? '?' + search : '');
283290
if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) {
284291
remainingPath = '';
@@ -318,7 +325,7 @@ var RouteNode = function () {
318325
}
319326
// Else: remaining path and children
320327
return {
321-
v: matchChildren(children, remainingPath, segments)
328+
v: matchChildren(children, remainingPath, segments, consumedPath)
322329
};
323330
}
324331
};
@@ -405,7 +412,9 @@ var RouteNode = function () {
405412
var segmentPath = segment.parser.build(params, { ignoreSearch: true });
406413

407414
return segment.absolute ? segmentPath : path + segmentPath;
408-
}, '');
415+
}, '')
416+
// remove repeated slashes
417+
.replace(/\/\/{1,}/g, '/');
409418

410419
var finalPath = path;
411420

dist/umd/route-node.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ var RouteNode = function () {
765765
strictQueryParams = options.strictQueryParams,
766766
strongMatching = options.strongMatching;
767767

768-
var matchChildren = function matchChildren(nodes, pathSegment, segments) {
768+
var matchChildren = function matchChildren(nodes, pathSegment, segments, consumedBefore) {
769769
var isRoot = nodes.length === 1 && nodes[0].name === '';
770770
// for (child of node.children) {
771771

@@ -775,13 +775,20 @@ var RouteNode = function () {
775775
// Partially match path
776776
var match = void 0;
777777
var remainingPath = void 0;
778+
var segment = pathSegment;
779+
780+
if (consumedBefore === '/' && child.path === '/') {
781+
// when we encounter repeating slashes we add the slash
782+
// back to the URL to make it de facto pathless
783+
segment = '/' + pathSegment;
784+
}
778785

779786
if (!child.children.length) {
780-
match = child.parser.test(pathSegment, { trailingSlash: trailingSlash });
787+
match = child.parser.test(segment, { trailingSlash: trailingSlash });
781788
}
782789

783790
if (!match) {
784-
match = child.parser.partialTest(pathSegment, { delimiter: strongMatching });
791+
match = child.parser.partialTest(segment, { delimiter: strongMatching });
785792
}
786793

787794
if (match) {
@@ -791,13 +798,13 @@ var RouteNode = function () {
791798
consumedPath = consumedPath.replace(/\/$/, '');
792799
}
793800

794-
remainingPath = pathSegment.replace(consumedPath, '');
801+
remainingPath = segment.replace(consumedPath, '');
795802

796803
if (trailingSlash && !child.children.length) {
797804
remainingPath = remainingPath.replace(/^\/\?/, '?');
798805
}
799806

800-
var search = omit(getSearch(pathSegment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
807+
var search = omit(getSearch(segment.replace(consumedPath, '')), child.parser.queryParams.concat(child.parser.queryParamsBr));
801808
remainingPath = getPath(remainingPath) + (search ? '?' + search : '');
802809
if (trailingSlash && !isRoot && remainingPath === '/' && !/\/$/.test(consumedPath)) {
803810
remainingPath = '';
@@ -837,7 +844,7 @@ var RouteNode = function () {
837844
}
838845
// Else: remaining path and children
839846
return {
840-
v: matchChildren(children, remainingPath, segments)
847+
v: matchChildren(children, remainingPath, segments, consumedPath)
841848
};
842849
}
843850
};
@@ -924,7 +931,9 @@ var RouteNode = function () {
924931
var segmentPath = segment.parser.build(params, { ignoreSearch: true });
925932

926933
return segment.absolute ? segmentPath : path + segmentPath;
927-
}, '');
934+
}, '')
935+
// remove repeated slashes
936+
.replace(/\/\/{1,}/g, '/');
928937

929938
var finalPath = path;
930939

modules/RouteNode.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,22 +186,30 @@ export default class RouteNode {
186186

187187
getSegmentsMatchingPath(path, options) {
188188
const { trailingSlash, strictQueryParams, strongMatching } = options;
189-
let matchChildren = (nodes, pathSegment, segments) => {
189+
let matchChildren = (nodes, pathSegment, segments, consumedBefore) => {
190190
const isRoot = nodes.length === 1 && nodes[0].name === '';
191191
// for (child of node.children) {
192192
for (let i = 0; i < nodes.length; i += 1) {
193193
const child = nodes[i];
194+
194195

195196
// Partially match path
196197
let match;
197198
let remainingPath;
199+
let segment = pathSegment;
200+
201+
if (consumedBefore === '/' && child.path === '/') {
202+
// when we encounter repeating slashes we add the slash
203+
// back to the URL to make it de facto pathless
204+
segment = '/' + pathSegment;
205+
}
198206

199207
if (!child.children.length) {
200-
match = child.parser.test(pathSegment, { trailingSlash });
208+
match = child.parser.test(segment, { trailingSlash });
201209
}
202210

203211
if (!match) {
204-
match = child.parser.partialTest(pathSegment, { delimiter: strongMatching });
212+
match = child.parser.partialTest(segment, { delimiter: strongMatching });
205213
}
206214

207215
if (match) {
@@ -211,14 +219,14 @@ export default class RouteNode {
211219
consumedPath = consumedPath.replace(/\/$/, '');
212220
}
213221

214-
remainingPath = pathSegment.replace(consumedPath, '');
215-
222+
remainingPath = segment.replace(consumedPath, '');
223+
216224
if (trailingSlash && !child.children.length) {
217225
remainingPath = remainingPath.replace(/^\/\?/, '?');
218226
}
219-
227+
220228
const search = omit(
221-
getSearch(pathSegment.replace(consumedPath, '')),
229+
getSearch(segment.replace(consumedPath, '')),
222230
child.parser.queryParams.concat(child.parser.queryParamsBr)
223231
);
224232
remainingPath = getPath(remainingPath) + (search ? `?${search}` : '');
@@ -245,7 +253,7 @@ export default class RouteNode {
245253
return null;
246254
}
247255
// Else: remaining path and children
248-
return matchChildren(children, remainingPath, segments);
256+
return matchChildren(children, remainingPath, segments, consumedPath);
249257
}
250258
}
251259

@@ -326,7 +334,9 @@ export default class RouteNode {
326334
const segmentPath = segment.parser.build(params, {ignoreSearch: true});
327335

328336
return segment.absolute ? segmentPath : path + segmentPath;
329-
}, '');
337+
}, '')
338+
// remove repeated slashes
339+
.replace(/\/\/{1,}/g, '/');
330340

331341
let finalPath = path;
332342

test/main.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ describe('RouteNode', function () {
313313
withoutMeta(rootNode.matchPath('/users/list', { trailingSlash: true })).should.eql({name: 'users.list', params: {}});
314314
withoutMeta(rootNode.matchPath('/users/list')).should.eql({name: 'users.list', params: {}});
315315
withoutMeta(rootNode.matchPath('/users/list/', { trailingSlash: true })).should.eql({name: 'users.list', params: {}});
316-
should.not.exists(rootNode.matchPath('/users/list//', { trailingSlash: true }));
317316

318317
rootNode = getRoutes(true);
319318
should.not.exists(rootNode.matchPath('/users/list'));
@@ -323,7 +322,6 @@ describe('RouteNode', function () {
323322
withoutMeta(rootNode.matchPath('/')).should.eql({name: 'default', params: {}});
324323
withoutMeta(rootNode.matchPath('', { trailingSlash: true })).should.eql({name: 'default', params: {}});
325324
should.not.exists(rootNode.matchPath('', { trailingSlash: false }));
326-
should.not.exists(rootNode.matchPath('/users/list//', { trailingSlash: true }));
327325
});
328326

329327
it('should match paths with optional trailing slashes and a non-empty root node', function () {
@@ -527,6 +525,36 @@ describe('RouteNode', function () {
527525
node.buildPath('c', { c: 1 }, { trailingSlash: false }).should.eql('/?c=1');
528526
});
529527

528+
it('should remove repeated slashes when building paths', ( ) => {
529+
530+
const node = new RouteNode('', '', [
531+
new RouteNode('a', '/', [
532+
new RouteNode('b', '/', [
533+
new RouteNode('c', '/')
534+
])
535+
])
536+
]);
537+
538+
node.buildPath('a.b', {}).should.eql('/');
539+
node.buildPath('a.b.c', {}).should.eql('/');
540+
541+
});
542+
543+
it('should match paths with repeating slashes', ( ) => {
544+
545+
const node = new RouteNode('', '', [
546+
new RouteNode('a', '/', [
547+
new RouteNode('b', '/', [
548+
new RouteNode('c', ':bar')
549+
])
550+
])
551+
]);
552+
553+
withoutMeta(node.matchPath('/')).should.eql({ name: 'a.b', params: {}});
554+
withoutMeta(node.matchPath('/foo')).should.eql({ name: 'a.b.c', params: { bar: 'foo' }});
555+
556+
});
557+
530558
});
531559

532560

0 commit comments

Comments
 (0)