Matching multiple params is the last thing
This commit is contained in:
parent
b718b0bddb
commit
2143d6c955
4 changed files with 140 additions and 49 deletions
|
@ -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)";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', () {
|
||||||
|
|
Loading…
Reference in a new issue