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">
|
<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>
|
|
@ -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>
|
|
@ -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>
|
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">
|
<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>
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 '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
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}';
|
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;
|
Loading…
Reference in a new issue