diff --git a/.idea/runConfigurations/All_Route_Tests.xml b/.idea/runConfigurations/All_Route_Tests.xml
index cc751b12..2dda0404 100644
--- a/.idea/runConfigurations/All_Route_Tests.xml
+++ b/.idea/runConfigurations/All_Route_Tests.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/All_Router_Tests.xml b/.idea/runConfigurations/All_Router_Tests.xml
index a1920311..78458c1b 100644
--- a/.idea/runConfigurations/All_Router_Tests.xml
+++ b/.idea/runConfigurations/All_Router_Tests.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/All_Server_Tests.xml b/.idea/runConfigurations/All_Server_Tests.xml
index 8a55853b..5388f331 100644
--- a/.idea/runConfigurations/All_Server_Tests.xml
+++ b/.idea/runConfigurations/All_Server_Tests.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml
new file mode 100644
index 00000000..ac11209e
--- /dev/null
+++ b/.idea/runConfigurations/All_Tests.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Chain.xml b/.idea/runConfigurations/Chain.xml
new file mode 100644
index 00000000..38a60eea
--- /dev/null
+++ b/.idea/runConfigurations/Chain.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Method_Tests.xml b/.idea/runConfigurations/Method_Tests.xml
index 436da978..dd37c59e 100644
--- a/.idea/runConfigurations/Method_Tests.xml
+++ b/.idea/runConfigurations/Method_Tests.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ae6be82f..2bfc485f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# angel_route
-![version 1.0.0-dev+8](https://img.shields.io/badge/version-1.0.0--dev+8-red.svg)
+![version 1.0.0-dev+9](https://img.shields.io/badge/version-1.0.0--dev+9-red.svg)
![build status](https://travis-ci.org/angel-dart/route.svg)
A powerful, isomorphic routing library for Dart.
diff --git a/lib/src/route.dart b/lib/src/route.dart
index 4f29330c..a11786bf 100644
--- a/lib/src/route.dart
+++ b/lib/src/route.dart
@@ -177,7 +177,8 @@ class Route {
Route result;
if (path is RegExp) {
- result = new Route(path, debug: debug, handlers: handlers, method: method, name: name);
+ result = new Route(path,
+ debug: debug, handlers: handlers, method: method, name: name);
} else {
final segments = path
.toString()
@@ -253,7 +254,9 @@ class Route {
parent._children.add(route
.._matcher = new RegExp('$pattern1$separator$pattern2')
- .._head = new RegExp(_matcherify('$path1/$path2'.replaceAll(_straySlashes, '')).replaceAll(_rgxEnd, ''))
+ .._head = new RegExp(
+ _matcherify('$path1/$path2'.replaceAll(_straySlashes, ''))
+ .replaceAll(_rgxEnd, ''))
.._parent = parent
.._stub = child.matcher);
@@ -300,6 +303,24 @@ class Route {
return addChild(route);
}
+ Route clone() {
+ final Route route = new Route('');
+
+ return route
+ .._children.addAll(children)
+ .._handlers.addAll(handlers)
+ .._head = _head
+ .._matcher = _matcher
+ .._method = _method
+ .._name = name
+ .._parent = _parent
+ .._parentResolver = _parentResolver
+ .._pathified = _pathified
+ .._resolver = _resolver
+ .._stub = _stub
+ ..state.properties.addAll(state.properties);
+ }
+
/// Generates a URI to this route with the given parameters.
String makeUri([Map params]) {
String result = _pathify(path);
@@ -324,12 +345,13 @@ class Route {
Iterable values =
_parseParameters(requestPath.replaceAll(_straySlashes, ''));
- _printDebug('Searched request path $requestPath and found these values: $values');
+ _printDebug(
+ 'Searched request path $requestPath and found these values: $values');
final pathString = _pathify(path).replaceAll(new RegExp('\/'), r'\/');
- Iterable matches =
- _param.allMatches(pathString);
- _printDebug('All param names parsed in $pathString: ${matches.map((m) => m.group(0))}');
+ Iterable matches = _param.allMatches(pathString);
+ _printDebug(
+ 'All param names parsed in $pathString: ${matches.map((m) => m.group(0))}');
for (int i = 0; i < matches.length && i < values.length; i++) {
Match match = matches.elementAt(i);
diff --git a/lib/src/router.dart b/lib/src/router.dart
index a7164782..130e685b 100644
--- a/lib/src/router.dart
+++ b/lib/src/router.dart
@@ -113,6 +113,24 @@ class Router extends Extensible {
} */
}
+ /// Returns a [Router] with a duplicated version of this tree.
+ Router clone({bool normalize: true}) {
+ final router = new Router(debug: debug);
+
+ _copy(Route route, Route parent) {
+ final r = route.clone();
+ parent._children.add(r.._parent = parent);
+
+ route.children.forEach((child) => _copy(child, r));
+ }
+
+ root.children.forEach((child) => _copy(child, router.root));
+
+ if (normalize) router.normalize();
+
+ return router;
+ }
+
/// Creates a visual representation of the route hierarchy and
/// passes it to a callback. If none is provided, `print` is called.
void dumpTree(
@@ -151,8 +169,7 @@ class Router extends Extensible {
}
tabs++;
- route.children
- .forEach((r) => dumpRoute(r, replace: route.path));
+ route.children.forEach((r) => dumpRoute(r, replace: route.path));
tabs--;
}
@@ -210,6 +227,30 @@ class Router extends Extensible {
return _resolve(root, _path, method, segments.first, segments.skip(1));
}
+ /// Finds every possible [Route] that matches the given path,
+ /// with the given method.
+ ///
+ /// This is preferable to [resolve].
+ /// Keep in mind that this function uses either a [linearClone] or a [clone], and thus
+ /// will not return the same exact routes from the original tree.
+ Iterable resolveAll(String path,
+ {bool linear: true, String method: 'GET', bool normalizeClone: true}) {
+ final router = linear
+ ? linearClone(normalize: normalizeClone)
+ : clone(normalize: normalizeClone);
+ final routes = [];
+ var resolved = router.resolve(path, method: method);
+
+ while (resolved != null) {
+ routes.add(resolved);
+ router.root._children.remove(resolved);
+
+ resolved = router.resolve(path, method: method);
+ }
+
+ return routes.where((route) => route != null);
+ }
+
_validHead(RegExp rgx) {
return !rgx.hasMatch('');
}
@@ -298,10 +339,16 @@ class Router extends Extensible {
}
}
- /// Flattens the route tree into a linear list.
+ /// Flattens the route tree into a linear list, in-place.
void flatten() {
+ _root = linearClone().root;
+ }
+
+ /// Returns a [Router] with a linear version of this tree.
+ Router linearClone({bool normalize: true}) {
final router = new Router(debug: debug);
- normalize();
+
+ if (normalize) this.normalize();
_flatten(Route parent, Route route) {
// if (route.children.isNotEmpty && route.method == '*') return;
@@ -315,8 +362,7 @@ class Router extends Extensible {
.._method = route.method
.._name = route.name
.._parent = route.parent // router.root
- .._path = route
- .path; //'${parent.path}/${route.path}'.replaceAll(_straySlashes, '');
+ .._path = route.path;
// New matcher
final part1 = parent.matcher.pattern
@@ -340,7 +386,7 @@ class Router extends Extensible {
}
root._children.forEach((child) => _flatten(root, child));
- _root = router.root;
+ return router;
}
/// Incorporates another [Router]'s routes into this one's.
@@ -431,10 +477,11 @@ class Router extends Extensible {
if (merge) {
_printDebug('Erasing this route: $route');
- route.parent._handlers.addAll(route.handlers);
+ // route.parent._handlers.addAll(route.handlers);
for (Route child in route.children) {
route.parent._children.insert(index, child.._parent = route.parent);
+ child._handlers.insertAll(0, route.handlers);
}
route.parent._children.remove(route);
diff --git a/test/all_test.dart b/test/all_test.dart
deleted file mode 100644
index 3a09d0a9..00000000
--- a/test/all_test.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-import 'package:test/test.dart';
-import 'method/all_tests.dart' as method;
-import 'route/all_tests.dart' as route;
-import 'router/all_tests.dart' as router;
-import 'server/all_tests.dart' as server;
-
-main() {
- group('method', method.main);
- group('route', route.main);
- group('router', router.main);
- group('server', server.main);
-}
\ No newline at end of file
diff --git a/test/all_tests.browser.dart b/test/all_tests.browser.dart
index e4514b2d..fab39268 100644
--- a/test/all_tests.browser.dart
+++ b/test/all_tests.browser.dart
@@ -1,6 +1,6 @@
import 'package:test/test.dart';
-import 'route/all_tests.dart' as route;
-import 'router/all_tests.dart' as router;
+import 'route/all_test.dart' as route;
+import 'router/all_test.dart' as router;
main() {
group('route', route.main);
diff --git a/test/chain/all_test.dart b/test/chain/all_test.dart
new file mode 100644
index 00000000..f5161fa7
--- /dev/null
+++ b/test/chain/all_test.dart
@@ -0,0 +1,177 @@
+import 'dart:io';
+import 'package:angel_route/angel_route.dart';
+import 'package:http/http.dart' as http;
+import 'package:test/test.dart';
+
+main() {
+ http.Client client;
+ Router router;
+ HttpServer server;
+ String url;
+
+ setUp(() async {
+ client = new http.Client();
+ router = new Router();
+ server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0);
+ url = 'http://${server.address.address}:${server.port}';
+
+ router.get('/hello', (req) {
+ req.response.write('world');
+ });
+
+ router.get('/sandwich', (req) {
+ req.response.write('pb');
+ return true;
+ });
+
+ router.all('/sandwich', (req) {
+ req.response.write('&j');
+ return false;
+ });
+
+ router.all('/chain', (req) {
+ req.response.write('PassTo');
+ return true;
+ });
+
+ router.group('/group/:id', (router) {
+ router.get('/fun', (req) {
+ req.response.write(' and fun!');
+ return false;
+ }, middleware: [
+ (req) {
+ req.response.write(' is cool');
+ return true;
+ }
+ ]);
+ }, middleware: [
+ (req) {
+ req.response.write('Dart');
+ return true;
+ }
+ ]);
+
+ final beatles = new Router();
+
+ beatles.get('/come-together', (req) {
+ req.response.write('spinal');
+ return true;
+ });
+
+ beatles.all('*', (req) {
+ req.response.write('-clacker');
+ return !req.uri.toString().contains('come-together');
+ });
+
+ router.mount('/beatles', beatles);
+
+ router.all('*', (req) {
+ req.response.write('Fallback');
+ return false;
+ });
+
+ router
+ ..normalize()
+ ..dumpTree(showMatchers: true);
+
+ server.listen((request) async {
+ final resolved =
+ router.resolveAll(request.uri.path, method: request.method);
+
+ if (resolved.isEmpty) {
+ request.response.statusCode = 404;
+ request.response.write('404 Not Found');
+ await request.response.close();
+ } else {
+ print('Resolved ${request.uri} => $resolved');
+
+ // Easy middleware pipeline
+ final pipeline = [];
+
+ for (Route route in resolved) {
+ pipeline.addAll(route.handlerSequence);
+ }
+
+ print('Pipeline: ${pipeline.length} handler(s)');
+
+ for (final handler in pipeline) {
+ if (handler(request) != true) break;
+ }
+
+ await request.response.close();
+ }
+ });
+ });
+
+ tearDown(() async {
+ client.close();
+ client = null;
+ router = null;
+ url = null;
+ await server.close();
+ });
+
+ test('hello', () async {
+ final response = await client.get('$url/hello');
+ print('Response: ${response.body}');
+ expect(response.body, equals('world'));
+ });
+
+ test('sandwich', () async {
+ final response = await client.get('$url/sandwich');
+ print('Response: ${response.body}');
+ expect(response.body, equals('pb&j'));
+ });
+
+ test('chain', () async {
+ final response = await client.get('$url/chain');
+ print('Response: ${response.body}');
+ expect(response.body, equals('PassToFallback'));
+ });
+
+ test('fallback', () async {
+ final response = await client.get('$url/fallback');
+ print('Response: ${response.body}');
+ expect(response.body, equals('Fallback'));
+ });
+
+ group('group', () {
+ test('fun', () async {
+ final response = await client.get('$url/group/abc/fun');
+ print('Response: ${response.body}');
+ expect(response.body, equals('Dart is cool and fun!'));
+ });
+
+ test('fallback', () async {
+ final response = await client.get('$url/group/abc');
+ print('Response: ${response.body}');
+ expect(response.body, equals('Fallback'));
+ });
+ });
+
+ group('beatles', () {
+ test('spinal clacker', () async {
+ final response = await client.get('$url/beatles/come-together');
+ print('Response: ${response.body}');
+ expect(response.body, equals('spinal-clacker'));
+ });
+
+ group('fallback', () {
+ setUp(() {
+ router.linearClone().dumpTree(header: 'LINEAR', showMatchers: true);
+ });
+
+ test('non-existent', () async {
+ var response = await client.get('$url/beatles/ringo-starr');
+ print('Response: ${response.body}');
+ expect(response.body, equals('-clackerFallback'));
+ });
+
+ test('root', () async {
+ var response = await client.get('$url/beatles');
+ print('Response: ${response.body}');
+ expect(response.body, equals('Fallback'));
+ });
+ });
+ });
+}
diff --git a/test/method/all_tests.dart b/test/method/all_test.dart
similarity index 100%
rename from test/method/all_tests.dart
rename to test/method/all_test.dart
diff --git a/test/route/all_tests.dart b/test/route/all_test.dart
similarity index 100%
rename from test/route/all_tests.dart
rename to test/route/all_test.dart
diff --git a/test/router/all_tests.dart b/test/router/all_test.dart
similarity index 100%
rename from test/router/all_tests.dart
rename to test/router/all_test.dart
diff --git a/test/server/all_tests.dart b/test/server/all_test.dart
similarity index 90%
rename from test/server/all_tests.dart
rename to test/server/all_test.dart
index 34e16dd6..7758e791 100644
--- a/test/server/all_tests.dart
+++ b/test/server/all_test.dart
@@ -22,11 +22,7 @@ main() {
url = 'http://${server.address.address}:${server.port}';
server.listen((request) async {
- final resolved = router.resolveOnRoot(request.uri.toString(), filter: (route) {
- print(
- '$route matches ${request.method} ${request.uri}? ${route.method == request.method || route.method == '*'}');
- return route.method == request.method || route.method == '*';
- });
+ final resolved = router.resolve(request.uri.path, method: request.method);
if (resolved == null) {
request.response.statusCode = 404;