Matching multiple params is the last thing

This commit is contained in:
thosakwe 2016-10-14 20:45:22 -04:00
parent b718b0bddb
commit 2143d6c955
4 changed files with 140 additions and 49 deletions

View file

@ -51,9 +51,11 @@ class Route {
String _method; String _method;
String _name; String _name;
Route _parent; Route _parent;
RegExp _parentResolver;
String _path; String _path;
String _pathified; String _pathified;
RegExp _resolver; RegExp _resolver;
String _stub;
List<Route> get children => new List.unmodifiable(_children); List<Route> get children => new List.unmodifiable(_children);
List get handlers => new List.unmodifiable(_handlers); List get handlers => new List.unmodifiable(_handlers);
RegExp get matcher => _matcher; RegExp get matcher => _matcher;
@ -113,6 +115,8 @@ class Route {
path.toString().replaceAll(_straySlashes, ''), path.toString().replaceAll(_straySlashes, ''),
expand: false)); expand: false));
} }
_parentResolver = new RegExp(_matcher.pattern.replaceAll(_rgxEnd, ''));
} }
/// Splits a route path into a list of segments, and then /// Splits a route path into a list of segments, and then
@ -131,18 +135,19 @@ class Route {
method: "GET", method: "GET",
String name: null}) { String name: null}) {
final segments = path.toString().split('/').where((str) => str.isNotEmpty); final segments = path.toString().split('/').where((str) => str.isNotEmpty);
print('Seg: $segments');
Route result; Route result;
for (String segment in segments) { if (segments.isEmpty) {
print('SEGGG: $segment'); return new Route('/',
if (result == null) children: children, handlers: handlers, method: method, name: name);
result = new Route(segment);
else
result = result.child(segment);
} }
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._children.addAll(children);
result._handlers.addAll(handlers); result._handlers.addAll(handlers);
@ -189,7 +194,6 @@ class Route {
Route addChild(Route route, {bool join: true}) { Route addChild(Route route, {bool join: true}) {
Route created = join ? new Route.join(this, route) : route.._parent = this; Route created = join ? new Route.join(this, route) : route.._parent = this;
_children.add(created);
return created; return created;
} }
@ -230,7 +234,7 @@ class Route {
_parseParameters(requestPath.replaceAll(_straySlashes, '')); _parseParameters(requestPath.replaceAll(_straySlashes, ''));
Iterable<Match> matches = Iterable<Match> matches =
_param.allMatches(_pathified.replaceAll(new RegExp('\/'), r'\/')); _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); Match match = matches.elementAt(i);
String paramName = match.group(1); String paramName = match.group(1);
String value = values.elementAt(i); String value = values.elementAt(i);
@ -246,11 +250,15 @@ class Route {
_parseParameters(String requestPath) sync* { _parseParameters(String requestPath) sync* {
Match routeMatch = matcher.firstMatch(requestPath); 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 _filter = filter ?? (_) => true;
final _fullPath = fullPath ?? path;
if ((path.isEmpty || path == '.') && _filter(this)) { if ((path.isEmpty || path == '.') && _filter(this)) {
return this; return this;
@ -274,7 +282,8 @@ class Route {
path.length > 1 && path.length > 1 &&
path[1] != '/' && path[1] != '/' &&
absoluteParent != null) { 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, '')) || } else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) ||
_resolver.hasMatch(path.replaceAll(_straySlashes, ''))) { _resolver.hasMatch(path.replaceAll(_straySlashes, ''))) {
return this; return this;
@ -283,41 +292,107 @@ class Route {
if (segments[0] == '..') { if (segments[0] == '..') {
if (parent != null) if (parent != null)
return parent.resolve(segments.skip(1).join('/'), _filter); return parent.resolve(segments.skip(1).join('/'),
filter: _filter, fullPath: _fullPath);
else else
throw new RoutingException.orphan(); throw new RoutingException.orphan();
} else if (segments[0] == '.') { } 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) { for (Route route in children) {
final subPath = '${this.path}/${segments[0]}'; final subPath = '${this.path}/${segments[0]}';
print('Subpath for $path on ${this.path} = $subPath');
if (route.match(subPath) != null || if (route.match(subPath) != null ||
route._resolver.firstMatch(subPath) != null) { route._resolver.firstMatch(subPath) != null) {
if (segments.length == 1 && _filter(route)) if (segments.length == 1 && _filter(route))
return route; return route;
else { 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 // Try to match the whole route, if nothing else works
for (Route route in children) { for (Route route in children) {
print( print(
'Full path is $path, path is ${route.path}, matcher: ${route.matcher.pattern}, resolver: ${route._resolver.pattern}'); 'Trying to match full $_fullPath for ${route.path} on ${this.path}');
if ((route.match(path) != null || if ((route.match(_fullPath) != null ||
route._resolver.firstMatch(path) != null) && route._resolver.firstMatch(_fullPath) != null) &&
_filter(route)) _filter(route))
return route; return route;
else if ((route.match('/$path') != null || else if ((route.match(_fullPath) != null ||
route._resolver.firstMatch('/$path') != null) && route._resolver.firstMatch(_fullPath) != null) &&
_filter(route)) return route; _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; return null;
} }
} }
@override
String toString() => "$method '$path' => ${handlers.length} handler(s)";
} }

View file

@ -100,7 +100,7 @@ class Router extends Extensible {
/// You can pass an additional filter to determine which /// You can pass an additional filter to determine which
/// routes count as matches. /// routes count as matches.
Route resolve(String path, [bool filter(Route route)]) => 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. /// Incorporates another [Router]'s routes into this one's.
/// ///

View file

@ -2,22 +2,24 @@ import 'package:angel_route/angel_route.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
main() { main() {
final foo = new Route.build('/foo/:id([0-9]+)', handlers: ['bar']); final fooById = new Route.build('/foo/:id([0-9]+)', handlers: ['bar']);
final bar = foo.child('/bar'); final foo = fooById.parent;
final bar = fooById.child('bar');
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']); final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
final bazById = baz.child(':bazId');
new Router(foo).dumpTree(); new Router(foo).dumpTree();
test('matching', () { test('matching', () {
expect(foo.children.length, equals(1)); expect(fooById.children.length, equals(1));
expect(foo.handlers.length, equals(1)); expect(fooById.handlers.length, equals(1));
expect(foo.handlerSequence.length, equals(1)); expect(fooById.handlerSequence.length, equals(1));
expect(foo.path, equals('foo/:id')); expect(fooById.path, equals('foo/:id'));
expect(foo.match('/foo/2'), isNotNull); expect(fooById.match('/foo/2'), isNotNull);
expect(foo.match('/foo/aaa'), isNull); expect(fooById.match('/foo/aaa'), isNull);
expect(foo.match('/bar'), isNull); expect(fooById.match('/bar'), isNull);
expect(foo.match('/foolish'), isNull); expect(fooById.match('/foolish'), isNull);
expect(foo.parent, equals(base)); expect(fooById.parent, equals(foo));
expect(foo.absoluteParent, equals(base)); expect(fooById.absoluteParent, equals(foo));
expect(bar.path, equals('foo/:id/bar')); expect(bar.path, equals('foo/:id/bar'));
expect(bar.children.length, equals(1)); expect(bar.children.length, equals(1));
@ -26,10 +28,10 @@ main() {
expect(bar.match('/foo/2/bar'), isNotNull); expect(bar.match('/foo/2/bar'), isNotNull);
expect(bar.match('/bar'), isNull); expect(bar.match('/bar'), isNull);
expect(bar.match('/foo/a/bar'), isNull); expect(bar.match('/foo/a/bar'), isNull);
expect(bar.parent, equals(foo)); expect(bar.parent, equals(fooById));
expect(baz.absoluteParent, equals(base)); expect(baz.absoluteParent, equals(foo));
expect(baz.children, isEmpty); expect(baz.children.length, equals(1));
expect(baz.handlers.length, equals(2)); expect(baz.handlers.length, equals(2));
expect(baz.handlerSequence.length, equals(3)); expect(baz.handlerSequence.length, equals(3));
expect(baz.path, equals('foo/:id/bar/baz')); expect(baz.path, equals('foo/:id/bar/baz'));
@ -39,26 +41,37 @@ main() {
expect(baz.match('/foo/bat/baz'), isNull); expect(baz.match('/foo/bat/baz'), isNull);
expect(baz.match('/foo/bar/baz/1'), isNull); expect(baz.match('/foo/bar/baz/1'), isNull);
expect(baz.parent, equals(bar)); expect(baz.parent, equals(bar));
expect(baz.absoluteParent, equals(base)); expect(baz.absoluteParent, equals(foo));
}); });
test('hierarchy', () { 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(fooById.resolve('/foo/2/bar'), equals(bar));
expect(foo.resolve('/foo/bar'), isNull); expect(fooById.resolve('/foo/bar'), isNull);
expect(foo.resolve('foo/1337/bar/baz'), equals(baz)); expect(fooById.resolve('/foo/a/bar'), isNull);
expect(fooById.resolve('foo/1337/bar/baz'), equals(baz));
expect(bar.resolve('..'), equals(foo)); expect(bar.resolve('..'), equals(fooById));
expect(bar.resolve('/bar/baz'), equals(baz));
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(bar.resolve('../bar'), equals(bar));
expect(baz.resolve('..'), 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('../baz'), equals(baz));
expect(baz.resolve('../../bar'), equals(bar)); expect(baz.resolve('../../bar'), equals(bar));
expect(baz.resolve('../../bar/baz'), equals(baz)); expect(baz.resolve('../../bar/baz'), equals(baz));
expect(baz.resolve('/bar'), equals(bar)); expect(baz.resolve('/2/bar'), equals(bar));
expect(baz.resolve('/bar/baz'), equals(baz)); 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);
}); });
} }

View file

@ -19,6 +19,8 @@ main() {
.child('/upper', handlers: [(String id) => id.toUpperCase()[0]]); .child('/upper', handlers: [(String id) => id.toUpperCase()[0]]);
}); });
final fizz = router.post('/user/fizz', null);
router.dumpTree(); router.dumpTree();
test('extensible', () { test('extensible', () {
@ -33,6 +35,7 @@ main() {
expect(lower.parent.path, equals('letter/:id')); expect(lower.parent.path, equals('letter/:id'));
expect(lower.resolve('../upper').path, equals('letter/:id/upper')); expect(lower.resolve('../upper').path, equals('letter/:id/upper'));
expect(lower.resolve('/user/34/detail'), equals(userById)); expect(lower.resolve('/user/34/detail'), equals(userById));
expect(userById.resolve('../fizz'), equals(fizz));
}); });
test('resolve', () { test('resolve', () {