Fallback :)
This commit is contained in:
parent
d138a7dd19
commit
b9e0371a3a
16 changed files with 281 additions and 38 deletions
|
@ -1,6 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<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 />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<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 />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<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 />
|
||||
</configuration>
|
||||
</component>
|
7
.idea/runConfigurations/All_Tests.xml
Normal file
7
.idea/runConfigurations/All_Tests.xml
Normal 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>
|
6
.idea/runConfigurations/Chain.xml
Normal file
6
.idea/runConfigurations/Chain.xml
Normal 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>
|
|
@ -1,6 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<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 />
|
||||
</configuration>
|
||||
</component>
|
|
@ -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.
|
||||
|
|
|
@ -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<String, dynamic> params]) {
|
||||
String result = _pathify(path);
|
||||
|
@ -324,12 +345,13 @@ class Route {
|
|||
Iterable<String> 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<Match> matches =
|
||||
_param.allMatches(pathString);
|
||||
_printDebug('All param names parsed in $pathString: ${matches.map((m) => m.group(0))}');
|
||||
Iterable<Match> 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);
|
||||
|
|
|
@ -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<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) {
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
177
test/chain/all_test.dart
Normal file
177
test/chain/all_test.dart
Normal 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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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;
|
Loading…
Reference in a new issue