From 2143d6c9554f6ff40d31acb9eb63adf28f68bf94 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 14 Oct 2016 20:45:22 -0400 Subject: [PATCH] Matching multiple params is the last thing --- lib/src/route.dart | 121 +++++++++++++++++++++++++++++------- lib/src/router.dart | 2 +- test/route/with_params.dart | 63 +++++++++++-------- test/router/all_tests.dart | 3 + 4 files changed, 140 insertions(+), 49 deletions(-) diff --git a/lib/src/route.dart b/lib/src/route.dart index 3b91587c..12704061 100644 --- a/lib/src/route.dart +++ b/lib/src/route.dart @@ -51,9 +51,11 @@ class Route { String _method; String _name; Route _parent; + RegExp _parentResolver; String _path; String _pathified; RegExp _resolver; + String _stub; List get children => new List.unmodifiable(_children); List get handlers => new List.unmodifiable(_handlers); RegExp get matcher => _matcher; @@ -113,6 +115,8 @@ class Route { path.toString().replaceAll(_straySlashes, ''), expand: false)); } + + _parentResolver = new RegExp(_matcher.pattern.replaceAll(_rgxEnd, '')); } /// Splits a route path into a list of segments, and then @@ -131,18 +135,19 @@ class Route { method: "GET", String name: null}) { final segments = path.toString().split('/').where((str) => str.isNotEmpty); - print('Seg: $segments'); Route result; - for (String segment in segments) { - print('SEGGG: $segment'); - if (result == null) - result = new Route(segment); - else - result = result.child(segment); + if (segments.isEmpty) { + return new Route('/', + children: children, handlers: handlers, method: method, name: name); } - print('result: ${result.path}'); + for (final segment in segments) { + if (result == null) { + result = new Route(segment); + } else + result = result.child(segment); + } result._children.addAll(children); result._handlers.addAll(handlers); @@ -189,7 +194,6 @@ class Route { Route addChild(Route route, {bool join: true}) { Route created = join ? new Route.join(this, route) : route.._parent = this; - _children.add(created); return created; } @@ -230,7 +234,7 @@ class Route { _parseParameters(requestPath.replaceAll(_straySlashes, '')); Iterable matches = _param.allMatches(_pathified.replaceAll(new RegExp('\/'), r'\/')); - for (int i = 0; i < matches.length; i++) { + for (int i = 0; i < matches.length && i < values.length; i++) { Match match = matches.elementAt(i); String paramName = match.group(1); String value = values.elementAt(i); @@ -246,11 +250,15 @@ class Route { _parseParameters(String requestPath) sync* { Match routeMatch = matcher.firstMatch(requestPath); - for (int i = 1; i <= routeMatch.groupCount; i++) yield routeMatch.group(i); + + if (routeMatch != null) + for (int i = 1; i <= routeMatch.groupCount; i++) + yield routeMatch.group(i); } - Route resolve(String path, [bool filter(Route route)]) { + Route resolve(String path, {bool filter(Route route), String fullPath}) { final _filter = filter ?? (_) => true; + final _fullPath = fullPath ?? path; if ((path.isEmpty || path == '.') && _filter(this)) { return this; @@ -274,7 +282,8 @@ class Route { path.length > 1 && path[1] != '/' && absoluteParent != null) { - return absoluteParent.resolve(path.substring(1), _filter); + return absoluteParent.resolve(path.substring(1), + filter: _filter, fullPath: _fullPath); } else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) || _resolver.hasMatch(path.replaceAll(_straySlashes, ''))) { return this; @@ -283,41 +292,107 @@ class Route { if (segments[0] == '..') { if (parent != null) - return parent.resolve(segments.skip(1).join('/'), _filter); + return parent.resolve(segments.skip(1).join('/'), + filter: _filter, fullPath: _fullPath); else throw new RoutingException.orphan(); } else if (segments[0] == '.') { - return resolve(segments.skip(1).join('/'), _filter); + return resolve(segments.skip(1).join('/'), + filter: _filter, fullPath: _fullPath); } for (Route route in children) { final subPath = '${this.path}/${segments[0]}'; - print('Subpath for $path on ${this.path} = $subPath'); if (route.match(subPath) != null || route._resolver.firstMatch(subPath) != null) { if (segments.length == 1 && _filter(route)) return route; else { - return route.resolve(segments.skip(1).join('/'), _filter); + return route.resolve(segments.skip(1).join('/'), + filter: _filter, + fullPath: this.path.replaceAll(_straySlashes, '') + + '/' + + _fullPath.replaceAll(_straySlashes, '')); } } } + // Try to match "subdirectory" + for (Route route in children) { + print( + 'Trying to match subdir for $path; child ${route.path} on ${this.path}'); + final match = route._parentResolver.firstMatch(path); + + if (match != null) { + final subPath = + path.replaceFirst(match[0], '').replaceAll(_straySlashes, ''); + print("Subdir path: $subPath"); + + for (Route child in route.children) { + final testPath = child.path + .replaceFirst(route.path, '') + .replaceAll(_straySlashes, ''); + + if (subPath == testPath && + (child.match(_fullPath) != null || + child._resolver.firstMatch(_fullPath) != null) && + _filter(child)) { + return child; + } + } + } else + print('Nope: $_parentResolver'); + } + + // Try to fill params + for (Route route in children) { + final params = parseParameters(_fullPath); + final _filledPath = makeUri(params); + print( + 'Trying to match filled $_filledPath for ${route.path} on ${this.path}'); + if ((route.match(_filledPath) != null || + route._resolver.firstMatch(_filledPath) != null) && + _filter(route)) + return route; + else if ((route.match(_filledPath) != null || + route._resolver.firstMatch(_filledPath) != null) && + _filter(route)) + return route; + else if ((route.match('/$_filledPath') != null || + route._resolver.firstMatch('/$_filledPath') != null) && + _filter(route)) + return route; + else { + print('Failed for ${route.matcher} when given $_filledPath'); + } + } + // Try to match the whole route, if nothing else works for (Route route in children) { print( - 'Full path is $path, path is ${route.path}, matcher: ${route.matcher.pattern}, resolver: ${route._resolver.pattern}'); - if ((route.match(path) != null || - route._resolver.firstMatch(path) != null) && + 'Trying to match full $_fullPath for ${route.path} on ${this.path}'); + if ((route.match(_fullPath) != null || + route._resolver.firstMatch(_fullPath) != null) && _filter(route)) return route; - else if ((route.match('/$path') != null || - route._resolver.firstMatch('/$path') != null) && - _filter(route)) return route; + else if ((route.match(_fullPath) != null || + route._resolver.firstMatch(_fullPath) != null) && + _filter(route)) + return route; + else if ((route.match('/$_fullPath') != null || + route._resolver.firstMatch('/$_fullPath') != null) && + _filter(route)) + return route; + else { + print('Failed for ${route.matcher} when given $_fullPath'); + } } return null; } } + + @override + String toString() => "$method '$path' => ${handlers.length} handler(s)"; } diff --git a/lib/src/router.dart b/lib/src/router.dart index 6dc20d5d..2c840166 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -100,7 +100,7 @@ class Router extends Extensible { /// You can pass an additional filter to determine which /// routes count as matches. Route resolve(String path, [bool filter(Route route)]) => - root.resolve(path, filter); + root.resolve(path, filter: filter); /// Incorporates another [Router]'s routes into this one's. /// diff --git a/test/route/with_params.dart b/test/route/with_params.dart index e0de1de8..24909c76 100644 --- a/test/route/with_params.dart +++ b/test/route/with_params.dart @@ -2,22 +2,24 @@ import 'package:angel_route/angel_route.dart'; import 'package:test/test.dart'; main() { - final foo = new Route.build('/foo/:id([0-9]+)', handlers: ['bar']); - final bar = foo.child('/bar'); + final fooById = new Route.build('/foo/:id([0-9]+)', handlers: ['bar']); + final foo = fooById.parent; + final bar = fooById.child('bar'); final baz = bar.child('//////baz//////', handlers: ['hello', 'world']); + final bazById = baz.child(':bazId'); new Router(foo).dumpTree(); test('matching', () { - expect(foo.children.length, equals(1)); - expect(foo.handlers.length, equals(1)); - expect(foo.handlerSequence.length, equals(1)); - expect(foo.path, equals('foo/:id')); - expect(foo.match('/foo/2'), isNotNull); - expect(foo.match('/foo/aaa'), isNull); - expect(foo.match('/bar'), isNull); - expect(foo.match('/foolish'), isNull); - expect(foo.parent, equals(base)); - expect(foo.absoluteParent, equals(base)); + expect(fooById.children.length, equals(1)); + expect(fooById.handlers.length, equals(1)); + expect(fooById.handlerSequence.length, equals(1)); + expect(fooById.path, equals('foo/:id')); + expect(fooById.match('/foo/2'), isNotNull); + expect(fooById.match('/foo/aaa'), isNull); + expect(fooById.match('/bar'), isNull); + expect(fooById.match('/foolish'), isNull); + expect(fooById.parent, equals(foo)); + expect(fooById.absoluteParent, equals(foo)); expect(bar.path, equals('foo/:id/bar')); expect(bar.children.length, equals(1)); @@ -26,10 +28,10 @@ main() { expect(bar.match('/foo/2/bar'), isNotNull); expect(bar.match('/bar'), isNull); expect(bar.match('/foo/a/bar'), isNull); - expect(bar.parent, equals(foo)); - expect(baz.absoluteParent, equals(base)); + expect(bar.parent, equals(fooById)); + expect(baz.absoluteParent, equals(foo)); - expect(baz.children, isEmpty); + expect(baz.children.length, equals(1)); expect(baz.handlers.length, equals(2)); expect(baz.handlerSequence.length, equals(3)); expect(baz.path, equals('foo/:id/bar/baz')); @@ -39,26 +41,37 @@ main() { expect(baz.match('/foo/bat/baz'), isNull); expect(baz.match('/foo/bar/baz/1'), isNull); expect(baz.parent, equals(bar)); - expect(baz.absoluteParent, equals(base)); + expect(baz.absoluteParent, equals(foo)); }); test('hierarchy', () { - expect(foo.resolve('/foo/2'), equals(foo)); + expect(fooById.resolve('/foo/2'), equals(fooById)); - expect(foo.resolve('/foo/2/bar'), equals(bar)); - expect(foo.resolve('/foo/bar'), isNull); - expect(foo.resolve('foo/1337/bar/baz'), equals(baz)); + expect(fooById.resolve('/foo/2/bar'), equals(bar)); + expect(fooById.resolve('/foo/bar'), isNull); + expect(fooById.resolve('/foo/a/bar'), isNull); + expect(fooById.resolve('foo/1337/bar/baz'), equals(baz)); - expect(bar.resolve('..'), equals(foo)); - expect(bar.resolve('/bar/baz'), equals(baz)); + expect(bar.resolve('..'), equals(fooById)); + + new Router(bar.parent).dumpTree(header: "POOP"); + expect(bar.parent.resolve('bar/baz'), equals(baz)); + expect(bar.resolve('/2/bar/baz'), equals(baz)); expect(bar.resolve('../bar'), equals(bar)); expect(baz.resolve('..'), equals(bar)); - expect(baz.resolve('../..'), equals(foo)); + expect(baz.resolve('../..'), equals(fooById)); expect(baz.resolve('../baz'), equals(baz)); expect(baz.resolve('../../bar'), equals(bar)); expect(baz.resolve('../../bar/baz'), equals(baz)); - expect(baz.resolve('/bar'), equals(bar)); - expect(baz.resolve('/bar/baz'), equals(baz)); + expect(baz.resolve('/2/bar'), equals(bar)); + expect(baz.resolve('/1337/bar/baz'), equals(baz)); + + expect(bar.resolve('/2/baz/e'), equals(bazById)); + expect(bar.resolve('baz/e'), equals(bazById)); + expect(bar.resolve('baz/e'), isNull); + expect(fooById.resolve('/foo/2/baz/e'), equals(bazById)); + expect(fooById.resolve('/foo/2/baz/2'), isNull); + expect(fooById.resolve('/foo/2a/baz/e'), isNull); }); } diff --git a/test/router/all_tests.dart b/test/router/all_tests.dart index fa157791..28649bc0 100644 --- a/test/router/all_tests.dart +++ b/test/router/all_tests.dart @@ -19,6 +19,8 @@ main() { .child('/upper', handlers: [(String id) => id.toUpperCase()[0]]); }); + final fizz = router.post('/user/fizz', null); + router.dumpTree(); test('extensible', () { @@ -33,6 +35,7 @@ main() { expect(lower.parent.path, equals('letter/:id')); expect(lower.resolve('../upper').path, equals('letter/:id/upper')); expect(lower.resolve('/user/34/detail'), equals(userById)); + expect(userById.resolve('../fizz'), equals(fizz)); }); test('resolve', () {