Fallback :)

This commit is contained in:
thosakwe 2016-11-23 13:58:34 -05:00
parent d138a7dd19
commit b9e0371a3a
16 changed files with 281 additions and 38 deletions

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Route Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Route Tests" singleton="true"> <configuration default="false" name="All Route Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Route Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/route/all_tests.dart" /> <option name="filePath" value="$PROJECT_DIR$/test/route/all_test.dart" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Router Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true"> <configuration default="false" name="All Router Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/router/all_tests.dart" /> <option name="filePath" value="$PROJECT_DIR$/test/router/all_test.dart" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Server Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Server Tests" singleton="true"> <configuration default="false" name="All Server Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Server Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/server/all_tests.dart" /> <option name="filePath" value="$PROJECT_DIR$/test/server/all_test.dart" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test" />
<option name="scope" value="FOLDER" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Chain" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/chain/all_test.dart" />
<method />
</configuration>
</component>

View file

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Method Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true"> <configuration default="false" name="Method Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/method/all_tests.dart" /> <option name="filePath" value="$PROJECT_DIR$/test/method/all_test.dart" />
<method /> <method />
</configuration> </configuration>
</component> </component>

View file

@ -1,6 +1,6 @@
# angel_route # 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) ![build status](https://travis-ci.org/angel-dart/route.svg)
A powerful, isomorphic routing library for Dart. A powerful, isomorphic routing library for Dart.

View file

@ -177,7 +177,8 @@ class Route {
Route result; Route result;
if (path is RegExp) { 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 { } else {
final segments = path final segments = path
.toString() .toString()
@ -253,7 +254,9 @@ class Route {
parent._children.add(route parent._children.add(route
.._matcher = new RegExp('$pattern1$separator$pattern2') .._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 .._parent = parent
.._stub = child.matcher); .._stub = child.matcher);
@ -300,6 +303,24 @@ class Route {
return addChild(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. /// Generates a URI to this route with the given parameters.
String makeUri([Map<String, dynamic> params]) { String makeUri([Map<String, dynamic> params]) {
String result = _pathify(path); String result = _pathify(path);
@ -324,12 +345,13 @@ class Route {
Iterable<String> values = Iterable<String> values =
_parseParameters(requestPath.replaceAll(_straySlashes, '')); _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'\/'); final pathString = _pathify(path).replaceAll(new RegExp('\/'), r'\/');
Iterable<Match> matches = Iterable<Match> matches = _param.allMatches(pathString);
_param.allMatches(pathString); _printDebug(
_printDebug('All param names parsed in $pathString: ${matches.map((m) => m.group(0))}'); 'All param names parsed in $pathString: ${matches.map((m) => m.group(0))}');
for (int i = 0; i < matches.length && i < values.length; i++) { for (int i = 0; i < matches.length && i < values.length; i++) {
Match match = matches.elementAt(i); Match match = matches.elementAt(i);

View file

@ -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 /// Creates a visual representation of the route hierarchy and
/// passes it to a callback. If none is provided, `print` is called. /// passes it to a callback. If none is provided, `print` is called.
void dumpTree( void dumpTree(
@ -151,8 +169,7 @@ class Router extends Extensible {
} }
tabs++; tabs++;
route.children route.children.forEach((r) => dumpRoute(r, replace: route.path));
.forEach((r) => dumpRoute(r, replace: route.path));
tabs--; tabs--;
} }
@ -210,6 +227,30 @@ class Router extends Extensible {
return _resolve(root, _path, method, segments.first, segments.skip(1)); 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<Route> 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) { _validHead(RegExp rgx) {
return !rgx.hasMatch(''); 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() { 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); final router = new Router(debug: debug);
normalize();
if (normalize) this.normalize();
_flatten(Route parent, Route route) { _flatten(Route parent, Route route) {
// if (route.children.isNotEmpty && route.method == '*') return; // if (route.children.isNotEmpty && route.method == '*') return;
@ -315,8 +362,7 @@ class Router extends Extensible {
.._method = route.method .._method = route.method
.._name = route.name .._name = route.name
.._parent = route.parent // router.root .._parent = route.parent // router.root
.._path = route .._path = route.path;
.path; //'${parent.path}/${route.path}'.replaceAll(_straySlashes, '');
// New matcher // New matcher
final part1 = parent.matcher.pattern final part1 = parent.matcher.pattern
@ -340,7 +386,7 @@ class Router extends Extensible {
} }
root._children.forEach((child) => _flatten(root, child)); root._children.forEach((child) => _flatten(root, child));
_root = router.root; return router;
} }
/// Incorporates another [Router]'s routes into this one's. /// Incorporates another [Router]'s routes into this one's.
@ -431,10 +477,11 @@ class Router extends Extensible {
if (merge) { if (merge) {
_printDebug('Erasing this route: $route'); _printDebug('Erasing this route: $route');
route.parent._handlers.addAll(route.handlers); // route.parent._handlers.addAll(route.handlers);
for (Route child in route.children) { for (Route child in route.children) {
route.parent._children.insert(index, child.._parent = route.parent); route.parent._children.insert(index, child.._parent = route.parent);
child._handlers.insertAll(0, route.handlers);
} }
route.parent._children.remove(route); route.parent._children.remove(route);

View file

@ -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);
}

View file

@ -1,6 +1,6 @@
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'route/all_tests.dart' as route; import 'route/all_test.dart' as route;
import 'router/all_tests.dart' as router; import 'router/all_test.dart' as router;
main() { main() {
group('route', route.main); group('route', route.main);

177
test/chain/all_test.dart Normal file
View file

@ -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'));
});
});
});
}

View file

@ -22,11 +22,7 @@ main() {
url = 'http://${server.address.address}:${server.port}'; url = 'http://${server.address.address}:${server.port}';
server.listen((request) async { server.listen((request) async {
final resolved = router.resolveOnRoot(request.uri.toString(), filter: (route) { final resolved = router.resolve(request.uri.path, method: request.method);
print(
'$route matches ${request.method} ${request.uri}? ${route.method == request.method || route.method == '*'}');
return route.method == request.method || route.method == '*';
});
if (resolved == null) { if (resolved == null) {
request.response.statusCode = 404; request.response.statusCode = 404;