Need to fix clone...
This commit is contained in:
parent
5897a9839b
commit
5d5ac7bef9
38 changed files with 465 additions and 1390 deletions
|
@ -1,6 +0,0 @@
|
|||
<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_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<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_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<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_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<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 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Fallback" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/router/fallback.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<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_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
.idea/runConfigurations/Navigate_Tests.xml
Normal file
6
.idea/runConfigurations/Navigate_Tests.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Navigate Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/navigate_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="No Params" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Route Tests" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/route/no_params.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Parse Params" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Route Tests" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/route/parse_params.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
.idea/runConfigurations/Server_Tests.xml
Normal file
6
.idea/runConfigurations/Server_Tests.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Server Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/server_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Use" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Router Tests" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/use.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="With Params" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Route Tests" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/route/with_params.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,5 +1,6 @@
|
|||
/// A powerful, isomorphic routing library for Dart.
|
||||
library angel_route;
|
||||
|
||||
export 'src/extensible.dart';
|
||||
export 'src/middleware_pipeline.dart';
|
||||
export 'src/router.dart';
|
||||
export 'src/routing_exception.dart';
|
138
lib/browser.dart
138
lib/browser.dart
|
@ -1,138 +0,0 @@
|
|||
import 'dart:async' show Stream, StreamController;
|
||||
import 'dart:html' show AnchorElement, window;
|
||||
import 'angel_route.dart';
|
||||
|
||||
final RegExp _hash = new RegExp(r'^#/');
|
||||
|
||||
/// A variation of the [Router] support both hash routing and push state.
|
||||
abstract class BrowserRouter extends Router {
|
||||
/// Fires whenever the active route changes. Fires `null` if none is selected (404).
|
||||
Stream<Route> get onRoute;
|
||||
|
||||
/// Set `hash` to true to use hash routing instead of push state.
|
||||
/// `listen` as `true` will call `listen` after initialization.
|
||||
factory BrowserRouter({bool hash: false, bool listen: true, Route root}) {
|
||||
return hash
|
||||
? new _HashRouter(listen: listen, root: root)
|
||||
: new _PushStateRouter(listen: listen, root: root);
|
||||
}
|
||||
|
||||
BrowserRouter._([Route root]) : super(root: root);
|
||||
|
||||
/// Calls `goTo` on the [Route] matching `path`.
|
||||
void go(String path, [Map params]);
|
||||
|
||||
/// Navigates to the given route.
|
||||
void goTo(Route route, [Map params]);
|
||||
|
||||
/// Begins listen for location changes.
|
||||
void listen();
|
||||
}
|
||||
|
||||
class _BrowserRouterImpl extends Router implements BrowserRouter {
|
||||
Route _current;
|
||||
StreamController<Route> _onRoute = new StreamController<Route>();
|
||||
Route get currentRoute => _current;
|
||||
|
||||
@override
|
||||
Stream<Route> get onRoute => _onRoute.stream;
|
||||
|
||||
_BrowserRouterImpl({bool listen, Route root}) : super(root: root) {
|
||||
if (listen) this.listen();
|
||||
prepareAnchors();
|
||||
}
|
||||
|
||||
@override
|
||||
void go(String path, [Map params]) {
|
||||
final resolved = resolve(path);
|
||||
|
||||
if (resolved != null)
|
||||
goTo(resolved, params);
|
||||
else
|
||||
throw new RoutingException.noSuchRoute(path);
|
||||
}
|
||||
|
||||
@override
|
||||
void listen() {
|
||||
normalize();
|
||||
}
|
||||
|
||||
void prepareAnchors() {
|
||||
final anchors = window.document.querySelectorAll('a:not([dynamic])');
|
||||
|
||||
for (final AnchorElement $a in anchors) {
|
||||
if ($a.attributes.containsKey('href') &&
|
||||
!$a.attributes.containsKey('download') &&
|
||||
!$a.attributes.containsKey('target') &&
|
||||
$a.attributes['rel'] != 'external') {
|
||||
$a.onClick.listen((e) {
|
||||
e.preventDefault();
|
||||
go($a.attributes['href']);
|
||||
});
|
||||
}
|
||||
|
||||
$a.attributes['dynamic'] = 'true';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HashRouter extends _BrowserRouterImpl {
|
||||
_HashRouter({bool listen, Route root}) : super(listen: listen, root: root) {
|
||||
if (listen) this.listen();
|
||||
}
|
||||
|
||||
@override
|
||||
void goTo(Route route, [Map params]) {
|
||||
route.state.properties.addAll(params ?? {});
|
||||
window.location.hash = '#/${route.makeUri(params)}';
|
||||
_onRoute.add(route);
|
||||
}
|
||||
|
||||
@override
|
||||
void listen() {
|
||||
super.listen();
|
||||
window.onHashChange.listen((_) {
|
||||
final path = window.location.hash.replaceAll(_hash, '');
|
||||
final resolved = resolveOnRoot(path);
|
||||
|
||||
if (resolved == null || (path.isEmpty && resolved == root)) {
|
||||
_onRoute.add(_current = null);
|
||||
} else if (resolved != null && resolved != _current) {
|
||||
goTo(resolved);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _PushStateRouter extends _BrowserRouterImpl {
|
||||
_PushStateRouter({bool listen, Route root})
|
||||
: super(listen: listen, root: root) {
|
||||
if (listen) this.listen();
|
||||
}
|
||||
|
||||
@override
|
||||
void goTo(Route route, [Map params]) {
|
||||
window.history.pushState(
|
||||
{'path': route.path, 'params': params ?? {}, 'properties': properties},
|
||||
route.name ?? route.path,
|
||||
route.makeUri(params));
|
||||
_onRoute.add(_current = route..state.properties.addAll(params ?? {}));
|
||||
}
|
||||
|
||||
@override
|
||||
void listen() {
|
||||
super.listen();
|
||||
window.onPopState.listen((e) {
|
||||
if (e.state is Map && e.state.containsKey('path')) {
|
||||
final resolved = resolve(e.state['path']);
|
||||
|
||||
if (resolved != _current) {
|
||||
properties.addAll(e.state['properties'] ?? {});
|
||||
_onRoute.add(_current = resolved
|
||||
..state.properties.addAll(e.state['params'] ?? {}));
|
||||
}
|
||||
} else
|
||||
_onRoute.add(_current = null);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -28,4 +28,4 @@ class Extensible {
|
|||
|
||||
super.noSuchMethod(invocation);
|
||||
}
|
||||
}
|
||||
}
|
17
lib/src/middleware_pipeline.dart
Normal file
17
lib/src/middleware_pipeline.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'router.dart';
|
||||
|
||||
class MiddlewarePipeline {
|
||||
final List<RoutingResult> routingResults;
|
||||
|
||||
List get handlers {
|
||||
final handlers = [];
|
||||
|
||||
for (RoutingResult result in routingResults) {
|
||||
handlers.addAll(result.allHandlers);
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
MiddlewarePipeline(this.routingResults);
|
||||
}
|
|
@ -343,7 +343,7 @@ class Route {
|
|||
Map result = {};
|
||||
|
||||
Iterable<String> values =
|
||||
_parseParameters(requestPath.replaceAll(_straySlashes, ''));
|
||||
_parseParameters(requestPath.replaceAll(_straySlashes, ''));
|
||||
|
||||
_printDebug(
|
||||
'Searched request path $requestPath and found these values: $values');
|
||||
|
@ -351,7 +351,7 @@ class Route {
|
|||
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))}');
|
||||
'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);
|
||||
|
@ -487,7 +487,7 @@ class Route {
|
|||
|
||||
if (match != null) {
|
||||
final subPath =
|
||||
path.replaceFirst(match[0], '').replaceAll(_straySlashes, '');
|
||||
path.replaceFirst(match[0], '').replaceAll(_straySlashes, '');
|
||||
_printDebug("Subdir path: $subPath");
|
||||
|
||||
for (Route child in route.children) {
|
||||
|
@ -510,12 +510,12 @@ class Route {
|
|||
_printDebug(
|
||||
'Trying to match full $_fullPath for ${route.path} on ${this.path}');
|
||||
if ((route.match(_fullPath) != null ||
|
||||
route._resolver.firstMatch(_fullPath) != null) &&
|
||||
route._resolver.firstMatch(_fullPath) != null) &&
|
||||
_filter(route)) {
|
||||
_printDebug('Matched full path!');
|
||||
return route.resolve('');
|
||||
} else if ((route.match('/$_fullPath') != null ||
|
||||
route._resolver.firstMatch('/$_fullPath') != null) &&
|
||||
route._resolver.firstMatch('/$_fullPath') != null) &&
|
||||
_filter(route)) {
|
||||
_printDebug('Matched full path (with a leading slash!)');
|
||||
return route.resolve('');
|
||||
|
@ -534,4 +534,4 @@ class Route {
|
|||
|
||||
@override
|
||||
String toString() => "$method '$path' => ${handlers.length} handler(s)";
|
||||
}
|
||||
}
|
|
@ -2,8 +2,9 @@ library angel_route.src.router;
|
|||
|
||||
import 'extensible.dart';
|
||||
import 'routing_exception.dart';
|
||||
|
||||
part 'symlink_route.dart';
|
||||
part 'route.dart';
|
||||
part 'routing_result.dart';
|
||||
|
||||
final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
|
||||
final RegExp _rgxEnd = new RegExp(r'\$+$');
|
||||
|
@ -14,23 +15,27 @@ final RegExp _slashDollar = new RegExp(r'/+\$');
|
|||
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||
|
||||
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
|
||||
class Router extends Extensible {
|
||||
Route _root;
|
||||
class Router {
|
||||
final List _middleware = [];
|
||||
final Map<Pattern, Router> _mounted = {};
|
||||
final List<Route> _routes = [];
|
||||
|
||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
||||
bool debug = false;
|
||||
|
||||
List get middleware => new List.unmodifiable(_middleware);
|
||||
|
||||
Map<Pattern, Router> get mounted =>
|
||||
new Map<Pattern, Router>.unmodifiable(_mounted);
|
||||
|
||||
/// Additional filters to be run on designated requests.
|
||||
Map<String, dynamic> requestMiddleware = {};
|
||||
|
||||
/// The single [Route] that serves as the root of the hierarchy.
|
||||
Route get root => _root;
|
||||
List<Route> get routes => new List<Route>.unmodifiable(_routes);
|
||||
|
||||
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
||||
/// Not recommended.
|
||||
Router({this.debug: false, Route root}) {
|
||||
_root = (_root = root ?? new _RootRoute())..debug = debug;
|
||||
}
|
||||
Router({this.debug: false});
|
||||
|
||||
void _printDebug(msg) {
|
||||
if (debug == true) print(msg);
|
||||
|
@ -40,142 +45,79 @@ class Router extends Extensible {
|
|||
/// for requests with the given method (case-insensitive).
|
||||
/// Provide '*' as the method to respond to all methods.
|
||||
Route addRoute(String method, Pattern path, Object handler,
|
||||
{List middleware}) {
|
||||
List handlers = [];
|
||||
{List middleware: const []}) {
|
||||
// Check if any mounted routers can match this
|
||||
final handlers = [handler];
|
||||
|
||||
handlers
|
||||
..addAll(middleware ?? [])
|
||||
..add(handler);
|
||||
if (middleware != null) handlers.addAll(middleware);
|
||||
|
||||
if (path is RegExp) {
|
||||
return root.child(path, debug: debug, handlers: handlers, method: method);
|
||||
} else {
|
||||
// if (path.toString().replaceAll(_straySlashes, '').isEmpty || true) {
|
||||
return root.child(path.toString(),
|
||||
debug: debug, handlers: handlers, method: method);
|
||||
}
|
||||
/* else {
|
||||
var segments = path
|
||||
.toString()
|
||||
.split('/')
|
||||
.where((str) => str.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
Route result;
|
||||
|
||||
if (segments.isEmpty) {
|
||||
return new Route('/', debug: debug, handlers: handlers, method: method)
|
||||
..debug = debug;
|
||||
} else {
|
||||
result = resolveOnRoot(segments[0],
|
||||
filter: (route) => route.method == method || route.method == '*');
|
||||
|
||||
if (result != null) {
|
||||
if (segments.length > 1) {
|
||||
_printDebug('Resolved: ${result} for "${segments[0]}"');
|
||||
segments = segments.skip(1).toList(growable: false);
|
||||
|
||||
Route existing;
|
||||
|
||||
do {
|
||||
existing = result.resolve(segments[0],
|
||||
filter: (route) =>
|
||||
route.method == method || route.method == '*');
|
||||
|
||||
if (existing != null) {
|
||||
result = existing;
|
||||
}
|
||||
} while (existing != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
final segment = segments[i];
|
||||
|
||||
if (i == segments.length - 1) {
|
||||
if (result == null) {
|
||||
result = root.child(segment,
|
||||
debug: debug, handlers: handlers, method: method);
|
||||
} else {
|
||||
result = result.child(segment,
|
||||
debug: debug, handlers: handlers, method: method);
|
||||
}
|
||||
} else {
|
||||
if (result == null) {
|
||||
result = root.child(segment, debug: debug, method: "*");
|
||||
} else {
|
||||
result = result.child(segment, debug: debug, method: "*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result..debug = debug;
|
||||
} */
|
||||
final route =
|
||||
new Route(path, debug: debug, method: method, handlers: handlers);
|
||||
_routes.add(route);
|
||||
return route;
|
||||
}
|
||||
|
||||
/// Returns a [Router] with a duplicated version of this tree.
|
||||
Router clone({bool normalize: true}) {
|
||||
Router clone() {
|
||||
final router = new Router(debug: debug);
|
||||
final newMounted = new Map.from(mounted);
|
||||
|
||||
_copy(Route route, Route parent) {
|
||||
final r = route.clone();
|
||||
parent._children.add(r.._parent = parent);
|
||||
|
||||
route.children.forEach((child) => _copy(child, r));
|
||||
for (Route route in routes) {
|
||||
if (route is! SymlinkRoute) {
|
||||
router._routes.add(route.clone());
|
||||
} else if (route is SymlinkRoute) {
|
||||
router._routes.add(new SymlinkRoute(route.path, route.pattern,
|
||||
newMounted[route.pattern] = route.router.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
root.children.forEach((child) => _copy(child, router.root));
|
||||
|
||||
if (normalize) router.normalize();
|
||||
|
||||
return router;
|
||||
return router.._mounted.addAll(newMounted);
|
||||
}
|
||||
|
||||
/// Creates a visual representation of the route hierarchy and
|
||||
/// passes it to a callback. If none is provided, `print` is called.
|
||||
void dumpTree(
|
||||
{callback(String tree),
|
||||
header: 'Dumping route tree:',
|
||||
tab: ' ',
|
||||
showMatchers: false}) {
|
||||
var tabs = 0;
|
||||
String header: 'Dumping route tree:',
|
||||
String tab: ' ',
|
||||
bool showMatchers: false}) {
|
||||
final buf = new StringBuffer();
|
||||
int tabs = 0;
|
||||
|
||||
void dumpRoute(Route route, {Pattern replace: null}) {
|
||||
for (var i = 0; i < tabs; i++) buf.write(tab);
|
||||
if (header != null && header.isNotEmpty) {
|
||||
buf.writeln(header);
|
||||
}
|
||||
|
||||
if (route == root)
|
||||
buf.writeln('(root)');
|
||||
else {
|
||||
buf.write('- ${route.method} ');
|
||||
indent() {
|
||||
for (int i = 0; i < tabs; i++) buf.write(tab);
|
||||
}
|
||||
|
||||
var p =
|
||||
replace != null ? route.path.replaceAll(replace, '') : route.path;
|
||||
p = p.replaceAll(_straySlashes, '');
|
||||
dumpRouter(Router router) {
|
||||
indent();
|
||||
buf.writeln('- <root>');
|
||||
tabs++;
|
||||
|
||||
if (p.isEmpty)
|
||||
buf.write("'/'");
|
||||
else
|
||||
buf.write("'${p.replaceAll(_straySlashes, '')}'");
|
||||
for (Route route in router.routes) {
|
||||
indent();
|
||||
buf.write('- ${route.path.isNotEmpty ? route.path : '/'}');
|
||||
|
||||
if (showMatchers) {
|
||||
buf.write(' (matcher: ${route.matcher.pattern})');
|
||||
}
|
||||
|
||||
if (route.handlers.isNotEmpty)
|
||||
buf.writeln(' => ${route.handlers.length} handler(s)');
|
||||
else
|
||||
if (route is SymlinkRoute) {
|
||||
buf.writeln();
|
||||
tabs++;
|
||||
dumpRouter(route.router);
|
||||
tabs--;
|
||||
} else {
|
||||
if (showMatchers) buf.write(' (${route.matcher.pattern})');
|
||||
|
||||
buf.writeln(' => ${route.handlers.length} handler(s)');
|
||||
}
|
||||
}
|
||||
|
||||
tabs++;
|
||||
route.children.forEach((r) => dumpRoute(r, replace: route.path));
|
||||
tabs--;
|
||||
}
|
||||
|
||||
if (header != null && header.isNotEmpty) buf.writeln(header);
|
||||
dumpRouter(this);
|
||||
|
||||
dumpRoute(root);
|
||||
(callback ?? print)(buf.toString());
|
||||
}
|
||||
|
||||
|
@ -184,26 +126,104 @@ class Router extends Extensible {
|
|||
///
|
||||
/// Returns the created route.
|
||||
/// You can also register middleware within the router.
|
||||
Route group(Pattern path, void callback(Router router),
|
||||
SymlinkRoute group(Pattern path, void callback(Router router),
|
||||
{Iterable middleware: const [],
|
||||
String method: "*",
|
||||
String name: null,
|
||||
String namespace: null}) {
|
||||
final route =
|
||||
root.child(path, handlers: middleware, method: method, name: name);
|
||||
final router = new Router(root: route);
|
||||
final router = new Router(debug: debug).._middleware.addAll(middleware);
|
||||
callback(router);
|
||||
|
||||
// Let's copy middleware, heeding the optional middleware namespace.
|
||||
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
||||
return mount(path, router, namespace: namespace).._name = name;
|
||||
}
|
||||
|
||||
Map copiedMiddleware = new Map.from(router.requestMiddleware);
|
||||
for (String middlewareName in copiedMiddleware.keys) {
|
||||
requestMiddleware["$middlewarePrefix$middlewareName"] =
|
||||
copiedMiddleware[middlewareName];
|
||||
/// Generates a URI string based on the given input.
|
||||
/// Handy when you have named routes.
|
||||
///
|
||||
/// Each item in `linkParams` should be a [Route],
|
||||
/// `String` or `Map<String, dynamic>`.
|
||||
///
|
||||
/// Strings should be route names, namespaces, or paths.
|
||||
/// Maps should be parameters, which will be filled
|
||||
/// into the previous route.
|
||||
///
|
||||
/// Paths and segments should correspond to the way
|
||||
/// you declared them.
|
||||
///
|
||||
/// For example, if you declared a route group on
|
||||
/// `'users/:id'`, it would not be resolved if you
|
||||
/// passed `'users'` in [linkParams].
|
||||
///
|
||||
/// Leading and trailing slashes are automatically
|
||||
/// removed.
|
||||
///
|
||||
/// Set [absolute] to `true` to insert a forward slash
|
||||
/// before the generated path.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// router.navigate(['users/:id', {'id': '1337'}, 'profile']);
|
||||
/// ```
|
||||
String navigate(List linkParams, {bool absolute: true}) {
|
||||
final List<String> segments = [];
|
||||
Router search = this;
|
||||
Route lastRoute;
|
||||
|
||||
for (final param in linkParams) {
|
||||
bool resolved = false;
|
||||
|
||||
if (param is String) {
|
||||
// Search by name
|
||||
for (Route route in search.routes) {
|
||||
if (route.name == param) {
|
||||
segments.add(route.path.replaceAll(_straySlashes, ''));
|
||||
lastRoute = route;
|
||||
|
||||
if (route is SymlinkRoute) {
|
||||
search = route.router;
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Search by path
|
||||
for (Route route in search.routes) {
|
||||
if (route.match(param) != null) {
|
||||
segments.add(route.path.replaceAll(_straySlashes, ''));
|
||||
lastRoute = route;
|
||||
|
||||
if (route is SymlinkRoute) {
|
||||
search = route.router;
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
throw new RoutingException(
|
||||
'Cannot resolve route for link param "$param".');
|
||||
}
|
||||
} else if (param is Route) {
|
||||
segments.add(param.path.replaceAll(_straySlashes, ''));
|
||||
} else if (param is Map<String, dynamic>) {
|
||||
if (lastRoute == null) {
|
||||
throw new RoutingException(
|
||||
'Maps in link params must be preceded by a Route or String.');
|
||||
} else {
|
||||
segments.removeLast();
|
||||
segments.add(lastRoute.makeUri(param).replaceAll(_straySlashes, ''));
|
||||
}
|
||||
} else
|
||||
throw new RoutingException(
|
||||
'Link param $param is not Route, String, or Map<String, dynamic>.');
|
||||
}
|
||||
|
||||
return route;
|
||||
return absolute
|
||||
? '/${segments.join('/').replaceAll(_straySlashes, '')}'
|
||||
: segments.join('/');
|
||||
}
|
||||
|
||||
/// Assigns a middleware to a name for convenience.
|
||||
|
@ -211,188 +231,68 @@ class Router extends Extensible {
|
|||
requestMiddleware[name] = middleware;
|
||||
}
|
||||
|
||||
/// Finds the first [Route] that matches the given path.
|
||||
///
|
||||
/// You can pass an additional filter to determine which
|
||||
/// routes count as matches.
|
||||
Route resolveOnRoot(String path, {bool filter(Route route)}) =>
|
||||
root.resolve(path, filter: filter);
|
||||
|
||||
/// Finds the first [Route] that matches the given path,
|
||||
/// with the given method.
|
||||
Route resolve(String path, {String method: 'GET'}) {
|
||||
final String _path = path.replaceAll(_straySlashes, '');
|
||||
final segments = _path.split('/').where((str) => str.isNotEmpty);
|
||||
_printDebug('Segments: $segments');
|
||||
return _resolve(root, _path, method, segments.isNotEmpty ? segments.first : '', segments.skip(1));
|
||||
RoutingResult resolve(String fullPath, String path, {String method: 'GET'}) {
|
||||
final cleanFullPath = fullPath.replaceAll(_straySlashes, '');
|
||||
final cleanPath = path.replaceAll(_straySlashes, '');
|
||||
|
||||
for (Route route in routes) {
|
||||
if (route is SymlinkRoute && route._head != null) {
|
||||
final match = route._head.firstMatch(cleanFullPath);
|
||||
|
||||
if (match != null) {
|
||||
final tail = cleanPath
|
||||
.replaceFirst(match[0], '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
_printDebug('Matched head "${match[0]}" to $route. Tail: "$tail"');
|
||||
final nested =
|
||||
route.router.resolve(cleanFullPath, tail, method: method);
|
||||
return new RoutingResult(
|
||||
match: match,
|
||||
nested: nested,
|
||||
params: route.parseParameters(cleanPath),
|
||||
sourceRoute: route,
|
||||
sourceRouter: this,
|
||||
tail: tail);
|
||||
}
|
||||
} else if (route.method == '*' || route.method == method) {
|
||||
final match = route.match(cleanPath);
|
||||
|
||||
if (match != null) {
|
||||
return new RoutingResult(
|
||||
match: match,
|
||||
params: route.parseParameters(cleanPath),
|
||||
sourceRoute: route,
|
||||
sourceRouter: this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
Iterable<RoutingResult> resolveAll(String fullPath, String path,
|
||||
{String method: 'GET'}) {
|
||||
final router = clone();
|
||||
final List<RoutingResult> results = [];
|
||||
var result = router.resolve(fullPath, path, method: method);
|
||||
|
||||
while (resolved != null) {
|
||||
try {
|
||||
routes.add(resolved);
|
||||
router.root._children.remove(resolved);
|
||||
|
||||
resolved = router.resolve(path, method: method);
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
while (result != null) {
|
||||
results.add(result);
|
||||
result.deepestRouter._routes.remove(result.deepestRoute);
|
||||
result = router.resolve(fullPath, path, method: method);
|
||||
}
|
||||
|
||||
return routes.where((route) => route != null);
|
||||
return results;
|
||||
}
|
||||
|
||||
_validHead(RegExp rgx) {
|
||||
return !rgx.hasMatch('');
|
||||
}
|
||||
|
||||
_resolve(Route ref, String fullPath, String method, String head,
|
||||
Iterable<String> tail) {
|
||||
_printDebug('$method $fullPath on $ref: head: $head, tail: ${tail.join(
|
||||
'/')}');
|
||||
|
||||
// Does the index route match?
|
||||
if (ref.matcher.hasMatch(fullPath)) {
|
||||
final index = ref.indexRoute;
|
||||
|
||||
for (Route child in ref.allIndices) {
|
||||
_printDebug('Possible index: $child');
|
||||
|
||||
if (child == child.indexRoute && ['*', method].contains(child.method)) {
|
||||
_printDebug('Possible index was exact match: $child');
|
||||
return child;
|
||||
}
|
||||
|
||||
final resolved = _resolve(child, fullPath, method, head, tail);
|
||||
|
||||
if (resolved != null) {
|
||||
_printDebug('Resolved from possible index: $resolved');
|
||||
return resolved;
|
||||
} else
|
||||
_printDebug('Possible index returned null: $child');
|
||||
}
|
||||
|
||||
if (['*', method].contains(index.method)) {
|
||||
return index;
|
||||
}
|
||||
} else {
|
||||
// Now, let's check if any route's head matches the
|
||||
// given head. If so, we try to resolve with that
|
||||
// given head. If so, we try to resolve with that
|
||||
// route, using a head corresponding to the one we
|
||||
// matched.
|
||||
for (Route child in ref.children) {
|
||||
if (child._head != null &&
|
||||
child._head.hasMatch(fullPath) &&
|
||||
_validHead(child._head)) {
|
||||
final newHead = child._head
|
||||
.firstMatch(fullPath)
|
||||
.group(0)
|
||||
.replaceAll(_straySlashes, '');
|
||||
final newTail = fullPath
|
||||
.replaceAll(child._head, '')
|
||||
.replaceAll(_straySlashes, '')
|
||||
.split('/')
|
||||
.where((str) => str.isNotEmpty);
|
||||
final resolved = _resolve(child, fullPath, method, newHead, newTail);
|
||||
|
||||
if (resolved != null) {
|
||||
_printDebug(
|
||||
'Head match: $resolved from head: ${child._head.pattern}');
|
||||
return resolved;
|
||||
}
|
||||
} else if (child._head != null) {
|
||||
_printDebug(
|
||||
'Head ${child._head.pattern} on $child failed to match $fullPath');
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match children by full path
|
||||
for (Route child in ref.children) {
|
||||
if (child.matcher.hasMatch(fullPath)) {
|
||||
final resolved = _resolve(child, fullPath, method, head, tail);
|
||||
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
} else {
|
||||
_printDebug(
|
||||
'Could not match full path $fullPath to matcher ${child.matcher.pattern}.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tail.isEmpty)
|
||||
return null;
|
||||
else {
|
||||
return _resolve(
|
||||
ref, fullPath, method, head + '/' + tail.first, tail.skip(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
if (normalize) this.normalize();
|
||||
|
||||
_flatten(Route parent, Route route) {
|
||||
// if (route.children.isNotEmpty && route.method == '*') return;
|
||||
|
||||
final r = new Route._base();
|
||||
|
||||
r
|
||||
.._handlers.addAll(route.handlerSequence)
|
||||
.._head = route._head
|
||||
.._matcher = route.matcher
|
||||
.._method = route.method
|
||||
.._name = route.name
|
||||
.._parent = route.parent // router.root
|
||||
.._path = route.path;
|
||||
|
||||
// New matcher
|
||||
final part1 = parent.matcher.pattern
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
final part2 = route.matcher.pattern
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
|
||||
final m = '$part1\\/$part2'.replaceAll(_rgxStraySlashes, '');
|
||||
|
||||
// r._matcher = new RegExp('^$m\$');
|
||||
_printDebug('Matcher of flattened route: ${r.matcher.pattern}');
|
||||
|
||||
router.root._children.add(r);
|
||||
route.children.forEach((child) => _flatten(route, child));
|
||||
}
|
||||
|
||||
root._children.forEach((child) => _flatten(root, child));
|
||||
return router;
|
||||
}
|
||||
|
||||
/// Incorporates another [Router]'s routes into this one's.
|
||||
///
|
||||
/// If `hooked` is set to `true` and a [Service] is provided,
|
||||
|
@ -403,7 +303,7 @@ class Router extends Extensible {
|
|||
/// For example, if the [Router] has a middleware 'y', and the `namespace`
|
||||
/// is 'x', then that middleware will be available as 'x.y' in the main router.
|
||||
/// These namespaces can be nested.
|
||||
void mount(Pattern path, Router router,
|
||||
SymlinkRoute mount(Pattern path, Router router,
|
||||
{bool hooked: true, String namespace: null}) {
|
||||
// Let's copy middleware, heeding the optional middleware namespace.
|
||||
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
||||
|
@ -414,91 +314,11 @@ class Router extends Extensible {
|
|||
copiedMiddleware[middlewareName];
|
||||
}
|
||||
|
||||
// final route = root.addChild(router.root, join: false);
|
||||
final route = root.child(path, debug: debug).addChild(router.root);
|
||||
route.debug = debug;
|
||||
final route = new SymlinkRoute(path, path, _mounted[path] = router);
|
||||
_routes.add(route);
|
||||
route._head = new RegExp(route.matcher.pattern.replaceAll(_rgxEnd, ''));
|
||||
|
||||
if (path is! RegExp) {
|
||||
// Correct mounted path manually...
|
||||
final clean = route.matcher.pattern
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '');
|
||||
route._matcher = new RegExp('^$clean\$');
|
||||
|
||||
final _path = path.toString().replaceAll(_straySlashes, '');
|
||||
|
||||
_migrateRoute(Route r) {
|
||||
r._path = '$_path/${r.path}'.replaceAll(_straySlashes, '');
|
||||
var m = r.matcher.pattern
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
|
||||
final m1 = _matcherify(_path)
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
|
||||
m = '$m1/$m'
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll(_straySlashes, '');
|
||||
|
||||
r._matcher = new RegExp('^$m\$');
|
||||
_printDebug(
|
||||
'New matcher on route in mounted router: ${r.matcher.pattern}');
|
||||
|
||||
if (r._head != null) {
|
||||
final head = r._head.pattern
|
||||
.replaceAll(_rgxStart, '')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, '')
|
||||
.replaceAll('\\/', '/')
|
||||
.replaceAll(_straySlashes, '');
|
||||
r._head = new RegExp(_matcherify('$_path/$head')
|
||||
.replaceAll(_rgxEnd, '')
|
||||
.replaceAll(_rgxStraySlashes, ''));
|
||||
_printDebug('Head of migrated route: ${r._head.pattern}');
|
||||
}
|
||||
|
||||
r.children.forEach(_migrateRoute);
|
||||
}
|
||||
|
||||
route.children.forEach(_migrateRoute);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes empty routes that could complicate route resolution.
|
||||
void normalize() {
|
||||
_printDebug('Normalizing route tree...');
|
||||
|
||||
_normalize(Route route, int index) {
|
||||
var merge = route.path.replaceAll(_straySlashes, '').isEmpty &&
|
||||
route.children.isNotEmpty;
|
||||
merge = merge || route.children.length == 1;
|
||||
|
||||
if (merge) {
|
||||
_printDebug('Erasing this route: $route');
|
||||
// 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);
|
||||
}
|
||||
|
||||
for (int i = 0; i < route.children.length; i++) {
|
||||
_normalize(route.children[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < root.children.length; i++) {
|
||||
_normalize(root.children[i], i);
|
||||
}
|
||||
return route.._name = namespace;
|
||||
}
|
||||
|
||||
/// Adds a route that responds to any request matching the given path.
|
||||
|
@ -541,10 +361,3 @@ class Router extends Extensible {
|
|||
return addRoute('PUT', path, handler, middleware: middleware);
|
||||
}
|
||||
}
|
||||
|
||||
class _RootRoute extends Route {
|
||||
_RootRoute() : super("/", method: '*', name: "<root>");
|
||||
|
||||
@override
|
||||
String toString() => "ROOT";
|
||||
}
|
||||
|
|
47
lib/src/routing_result.dart
Normal file
47
lib/src/routing_result.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
part of angel_route.src.router;
|
||||
|
||||
class RoutingResult {
|
||||
final Match match;
|
||||
final RoutingResult nested;
|
||||
final Map<String, dynamic> params = {};
|
||||
final Route sourceRoute;
|
||||
final Router sourceRouter;
|
||||
final String tail;
|
||||
|
||||
RoutingResult get deepest {
|
||||
var search = this;
|
||||
|
||||
while (search.nested != null) search = search.nested;
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
Route get deepestRoute => deepest.sourceRoute;
|
||||
Router get deepestRouter => deepest.sourceRouter;
|
||||
|
||||
List get handlers {
|
||||
return []..addAll(sourceRouter.middleware)..addAll(sourceRoute.handlers);
|
||||
}
|
||||
|
||||
List get allHandlers {
|
||||
final handlers = [];
|
||||
var search = this;
|
||||
|
||||
while (search != null) {
|
||||
handlers.addAll(search.handlers);
|
||||
search = search.nested;
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
RoutingResult(
|
||||
{this.match,
|
||||
Map<String, dynamic> params: const {},
|
||||
this.nested,
|
||||
this.sourceRoute,
|
||||
this.sourceRouter,
|
||||
this.tail}) {
|
||||
this.params.addAll(params ?? {});
|
||||
}
|
||||
}
|
10
lib/src/symlink_route.dart
Normal file
10
lib/src/symlink_route.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
part of angel_route.src.router;
|
||||
|
||||
/// Placeholder [Route] to serve as a symbolic link
|
||||
/// to a mounted [Router].
|
||||
class SymlinkRoute extends Route {
|
||||
final Pattern pattern;
|
||||
final Router router;
|
||||
|
||||
SymlinkRoute(Pattern path, this.pattern, this.router) : super(path);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'route/all_test.dart' as route;
|
||||
import 'router/all_test.dart' as router;
|
||||
|
||||
main() {
|
||||
group('route', route.main);
|
||||
group('router', router.main);
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
var router = new Router(debug: true);
|
||||
final getFoo = router.get('/foo', 'GET');
|
||||
final postFoo = router.post('/foo', 'POST');
|
||||
|
||||
Route getFooBar, postFooBar, patchFooBarId;
|
||||
|
||||
router.group('/foo/bar', (router) {
|
||||
getFooBar = router.get('/', 'GET');
|
||||
postFooBar = router.post('/', 'POST');
|
||||
patchFooBarId = router.patch('/:id([0-9]+)', 'PATCH');
|
||||
});
|
||||
|
||||
final Router books = new Router();
|
||||
|
||||
final getBooks = books.get('/', 'GET');
|
||||
final postBooks = books.post('/', 'POST');
|
||||
final getBooksFoo = books.get('/foo', 'GET');
|
||||
final postBooksFoo = books.post('/foo', 'POST');
|
||||
|
||||
Route getBooksChapters,
|
||||
postBooksChapters,
|
||||
getBooksChaptersReviews,
|
||||
postBooksChaptersReviews;
|
||||
|
||||
books.group('/:id/chapters', (router) {
|
||||
getBooksChapters = router.get('/', 'GET');
|
||||
postBooksChapters = router.post('/', 'POST');
|
||||
|
||||
router.group('/:id([A-Za-z]+)/reviews', (router) {
|
||||
getBooksChaptersReviews = router.get('/', 'GET');
|
||||
postBooksChaptersReviews = router.post('/', 'POST');
|
||||
});
|
||||
});
|
||||
|
||||
router.mount('/books', books);
|
||||
router.normalize();
|
||||
|
||||
group('top level', () {
|
||||
test('get', () => expect(router.resolve('/foo'), equals(getFoo)));
|
||||
|
||||
test('post', () {
|
||||
router.dumpTree();
|
||||
expect(router.resolve('/foo', method: 'POST'), equals(postFoo));
|
||||
});
|
||||
});
|
||||
|
||||
group('group', () {
|
||||
test('get', () {
|
||||
expect(router.resolve('/foo/bar'), equals(getFooBar));
|
||||
});
|
||||
|
||||
test('post', () {
|
||||
expect(router.resolve('/foo/bar', method: 'POST'), equals(postFooBar));
|
||||
});
|
||||
|
||||
test('patch+id', () {
|
||||
router.dumpTree();
|
||||
expect(
|
||||
router.resolve('/foo/bar/2', method: 'PATCH'), equals(patchFooBarId));
|
||||
});
|
||||
|
||||
test('404', () {
|
||||
expect(router.resolve('/foo/bar/A', method: 'PATCH'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('mount', () {
|
||||
group('no params', () {
|
||||
test('get', () {
|
||||
expect(router.resolve('/books'), equals(getBooks));
|
||||
expect(router.resolve('/books/foo'), equals(getBooksFoo));
|
||||
});
|
||||
|
||||
test('post', () {
|
||||
expect(router.resolve('/books', method: 'POST'), equals(postBooks));
|
||||
expect(
|
||||
router.resolve('/books/foo', method: 'POST'), equals(postBooksFoo));
|
||||
});
|
||||
});
|
||||
|
||||
group('with params', () {
|
||||
test('1 param', () {
|
||||
expect(router.resolve('/books/abc/chapters'), equals(getBooksChapters));
|
||||
expect(router.resolve('/books/abc/chapters', method: 'POST'),
|
||||
equals(postBooksChapters));
|
||||
});
|
||||
|
||||
group('2 params', () {
|
||||
setUp(router.dumpTree);
|
||||
|
||||
test('get', () {
|
||||
expect(router.resolve('/books/abc/chapters/ABC/reviews'),
|
||||
equals(getBooksChaptersReviews));
|
||||
});
|
||||
|
||||
test('post', () {
|
||||
expect(
|
||||
router.resolve('/books/abc/chapters/ABC/reviews', method: 'POST'),
|
||||
equals(postBooksChaptersReviews));
|
||||
});
|
||||
|
||||
test('404', () {
|
||||
expect(router.resolve('/books/abc/chapters/1'), isNull);
|
||||
expect(router.resolve('/books/abc/chapters/12'), isNull);
|
||||
expect(router.resolve('/books/abc/chapters/13.!'), isNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('flatten', () {
|
||||
router.dumpTree(header: 'BEFORE FLATTENING:');
|
||||
final flat = router..flatten();
|
||||
|
||||
for (Route route in flat.root.children) {
|
||||
print('${route.method} ${route.path} => ${route.matcher.pattern}');
|
||||
}
|
||||
});
|
||||
}
|
44
test/navigate_test.dart
Normal file
44
test/navigate_test.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
final router = new Router();
|
||||
|
||||
router.get('/', 'GET').as('root');
|
||||
router.get('/user/:id', 'GET');
|
||||
router.get('/first/:first/last/:last', 'GET').as('full_name');
|
||||
|
||||
navigate(params) {
|
||||
final uri = router.navigate(params);
|
||||
print('Uri: $uri');
|
||||
return uri;
|
||||
}
|
||||
|
||||
router.dumpTree(showMatchers: true);
|
||||
|
||||
group('top-level', () {
|
||||
test('named', () {
|
||||
expect(navigate(['root']), equals('/'));
|
||||
});
|
||||
|
||||
test('params', () {
|
||||
expect(
|
||||
navigate([
|
||||
'user/:id',
|
||||
{'id': 1337}
|
||||
]),
|
||||
equals('/user/1337'));
|
||||
|
||||
expect(
|
||||
navigate([
|
||||
'full_name',
|
||||
{'first': 'John', 'last': 'Smith'}
|
||||
]),
|
||||
equals('/first/John/last/Smith'));
|
||||
});
|
||||
|
||||
test('root', () {
|
||||
expect(navigate(['/']), equals('/'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'no_params.dart' as no_params;
|
||||
import 'parse_params.dart' as parse_params;
|
||||
import 'with_params.dart' as with_params;
|
||||
|
||||
main() {
|
||||
group('parse params', parse_params.main);
|
||||
group('no params', no_params.main);
|
||||
group('with params', with_params.main);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
final foo = new Route.build('/foo', handlers: ['bar']);
|
||||
final bar = foo.child('/bar');
|
||||
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
|
||||
|
||||
test('matching', () {
|
||||
expect(foo.children.length, equals(1));
|
||||
expect(foo.handlers.length, equals(1));
|
||||
expect(foo.handlerSequence.length, equals(1));
|
||||
expect(foo.path, equals('foo'));
|
||||
expect(foo.match('/foo'), isNotNull);
|
||||
expect(foo.match('/bar'), isNull);
|
||||
expect(foo.match('/foolish'), isNull);
|
||||
expect(foo.parent, isNull);
|
||||
expect(foo.absoluteParent, equals(foo));
|
||||
|
||||
expect(bar.path, equals('foo/bar'));
|
||||
expect(bar.children.length, equals(1));
|
||||
expect(bar.handlers, isEmpty);
|
||||
expect(bar.handlerSequence.length, equals(1));
|
||||
expect(bar.match('/foo/bar'), isNotNull);
|
||||
expect(bar.match('/bar'), isNull);
|
||||
expect(bar.match('/foo/bar/2'), isNull);
|
||||
expect(bar.parent, equals(foo));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
|
||||
expect(baz.children, isEmpty);
|
||||
expect(baz.handlers.length, equals(2));
|
||||
expect(baz.handlerSequence.length, equals(3));
|
||||
expect(baz.path, equals('foo/bar/baz'));
|
||||
expect(baz.match('/foo/bar/baz'), isNotNull);
|
||||
expect(baz.match('/foo/bat/baz'), isNull);
|
||||
expect(baz.match('/foo/bar/baz/1'), isNull);
|
||||
expect(baz.parent, equals(bar));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
});
|
||||
|
||||
test('hierarchy', () {
|
||||
expect(foo.resolve('bar'), equals(bar));
|
||||
expect(foo.resolve('bar/baz'), equals(baz));
|
||||
|
||||
expect(bar.resolve('..'), equals(foo));
|
||||
expect(bar.resolve('/bar/baz'), equals(baz));
|
||||
expect(bar.resolve('../bar'), equals(bar));
|
||||
|
||||
expect(baz.resolve('..'), equals(bar));
|
||||
expect(baz.resolve('../..'), equals(foo));
|
||||
expect(baz.resolve('../baz'), equals(baz));
|
||||
expect(baz.resolve('../../bar'), equals(bar));
|
||||
expect(baz.resolve('../../bar/baz'), equals(baz));
|
||||
expect(baz.resolve('/bar'), equals(bar));
|
||||
expect(baz.resolve('/bar/baz'), equals(baz));
|
||||
});
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
p(x) {
|
||||
print(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
main() {
|
||||
final foo = new Route('/foo/:id');
|
||||
final bar = foo.child('/bar/:barId/baz');
|
||||
|
||||
test('make uri', () {
|
||||
expect(p(foo.makeUri({'id': 1337})), equals('foo/1337'));
|
||||
expect(p(bar.makeUri({'id': 1337, 'barId': 12})),
|
||||
equals('foo/1337/bar/12/baz'));
|
||||
});
|
||||
|
||||
test('parse', () {
|
||||
final fooParams = foo.parseParameters('foo/1337/////');
|
||||
expect(p(fooParams), equals({'id': 1337}));
|
||||
|
||||
final barParams = bar.parseParameters('/////foo/1337/bar/12/baz');
|
||||
expect(p(barParams), equals({'id': 1337, 'barId': 12}));
|
||||
});
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
final foo = new Router().root;
|
||||
final fooById = foo.child(':id((?!bar)[0-9]+)', handlers: ['bar']);
|
||||
final bar = fooById.child('bar');
|
||||
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
|
||||
final bazById = baz.child(':bazId([A-Za-z]+)?');
|
||||
new Router(root: foo).dumpTree();
|
||||
|
||||
test('matching', () {
|
||||
expect(fooById.children.length, equals(1));
|
||||
expect(fooById.handlers.length, equals(1));
|
||||
expect(fooById.handlerSequence.length, equals(1));
|
||||
expect(fooById.path, equals(':id'));
|
||||
expect(fooById.match('/2'), isNotNull);
|
||||
expect(fooById.match('/aaa'), isNull);
|
||||
expect(fooById.match('/bar'), isNull);
|
||||
expect(fooById.match('lish'), isNull);
|
||||
expect(fooById.parent, equals(foo));
|
||||
expect(fooById.absoluteParent, equals(foo));
|
||||
|
||||
expect(bar.path, equals(':id/bar'));
|
||||
expect(bar.children.length, equals(1));
|
||||
expect(bar.handlers, isEmpty);
|
||||
expect(bar.handlerSequence.length, equals(1));
|
||||
expect(bar.match('/2/bar'), isNotNull);
|
||||
expect(bar.match('/bar'), isNull);
|
||||
expect(bar.match('/a/bar'), isNull);
|
||||
expect(bar.parent, equals(fooById));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
|
||||
expect(baz.children.length, equals(1));
|
||||
expect(baz.handlers.length, equals(2));
|
||||
expect(baz.handlerSequence.length, equals(3));
|
||||
expect(baz.path, equals(':id/bar/baz'));
|
||||
expect(baz.match('/2A/bar/baz'), isNull);
|
||||
expect(baz.match('/2/bar/baz'), isNotNull);
|
||||
expect(baz.match('/1337/bar/baz'), isNotNull);
|
||||
expect(baz.match('/bat/baz'), isNull);
|
||||
expect(baz.match('/bar/baz/1'), isNull);
|
||||
expect(baz.parent, equals(bar));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
});
|
||||
|
||||
test('hierarchy', () {
|
||||
expect(fooById.resolve('/2'), equals(fooById));
|
||||
|
||||
expect(fooById.resolve('/2/bar'), equals(bar));
|
||||
expect(fooById.resolve('/bar'), isNull);
|
||||
expect(fooById.resolve('/a/bar'), isNull);
|
||||
expect(fooById.resolve('1337/bar/baz'), equals(baz));
|
||||
|
||||
expect(bar.resolve('..'), equals(fooById));
|
||||
|
||||
new Router(root: 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(baz.resolve('..'), equals(bar));
|
||||
expect(baz.resolve('../..'), equals(fooById));
|
||||
expect(baz.resolve('../baz'), equals(baz));
|
||||
expect(baz.resolve('../../bar'), equals(bar));
|
||||
expect(baz.resolve('../../bar/baz'), equals(baz));
|
||||
expect(baz.resolve('/2/bar'), equals(bar));
|
||||
expect(baz.resolve('/1337/bar/baz'), equals(baz));
|
||||
|
||||
expect(bar.resolve('/2/bar/baz/e'), equals(bazById));
|
||||
expect(bar.resolve('baz/e'), equals(bazById));
|
||||
expect(fooById.resolve('/2/bar/baz/e'), equals(bazById));
|
||||
expect(fooById.resolve('/2/bar/baz/2'), isNull);
|
||||
expect(fooById.resolve('/2a/bar/baz/e'), isNull);
|
||||
});
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'fallback.dart' as fallback;
|
||||
|
||||
final ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
main() {
|
||||
final router = new Router();
|
||||
final indexRoute = router.get('/', () => ':)');
|
||||
final fizz = router.post('/user/fizz', null);
|
||||
final deleteUserById =
|
||||
router.delete('/user/:id/detail', (id) => num.parse(id));
|
||||
|
||||
Route lower;
|
||||
|
||||
router.group('/letter///', (router) {
|
||||
lower = router
|
||||
.get('/:id([A-Za-z])', (id) => ABC.indexOf(id[0]))
|
||||
.child('////lower', handlers: [(String id) => id.toLowerCase()[0]]);
|
||||
|
||||
lower.parent
|
||||
.child('/upper', handlers: [(String id) => id.toUpperCase()[0]]);
|
||||
});
|
||||
|
||||
router.dumpTree(header: "ROUTER TESTS");
|
||||
|
||||
test('extensible', () {
|
||||
router['two'] = 2;
|
||||
expect(router.two, equals(2));
|
||||
});
|
||||
|
||||
group('fallback', fallback.main);
|
||||
|
||||
test('hierarchy', () {
|
||||
expect(lower.absoluteParent, equals(router.root));
|
||||
expect(lower.parent.path, equals('letter/:id'));
|
||||
expect(lower.resolve('../upper').path, equals('letter/:id/upper'));
|
||||
expect(lower.resolve('/user/34/detail'), equals(deleteUserById));
|
||||
expect(deleteUserById.resolve('../../fizz'), equals(fizz));
|
||||
}, skip: 'Hierarchy is deprecated.');
|
||||
|
||||
test('resolve', () {
|
||||
expect(router.resolveOnRoot('/'), equals(indexRoute));
|
||||
expect(router.resolveOnRoot('user/1337/detail'), equals(deleteUserById));
|
||||
expect(router.resolveOnRoot('/user/1337/detail'), equals(deleteUserById));
|
||||
expect(router.resolveOnRoot('letter/a/lower'), equals(lower));
|
||||
expect(router.resolveOnRoot('letter/2/lower'), isNull);
|
||||
});
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
bool checkPost(Route route) => route.method == "POST";
|
||||
|
||||
main() {
|
||||
final router = new Router();
|
||||
|
||||
final userById = router.group('/user', (router) {
|
||||
router.get('/:id', (id) => 'User $id');
|
||||
}).resolve(':id');
|
||||
|
||||
final fallback = router.get('*', () => 'fallback');
|
||||
|
||||
test('resolve', () {
|
||||
expect(router.resolveOnRoot('/foo'), equals(fallback));
|
||||
expect(router.resolveOnRoot('/user/:id'), equals(userById));
|
||||
expect(router.resolveOnRoot('/user/:id', filter: checkPost), isNull);
|
||||
});
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:io';
|
||||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
typedef Future<bool> RequestHandler(HttpRequest request);
|
||||
|
||||
final String MIDDLEWARE_GREETING = 'Hi, I am a middleware!';
|
||||
|
||||
main() {
|
||||
http.Client client;
|
||||
Router router;
|
||||
HttpServer server;
|
||||
String url;
|
||||
|
||||
setUp(() async {
|
||||
client = new http.Client();
|
||||
router = new Router(debug: true);
|
||||
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||
url = 'http://${server.address.address}:${server.port}';
|
||||
|
||||
server.listen((request) async {
|
||||
final resolved = router.resolve(request.uri.path, method: request.method);
|
||||
|
||||
if (resolved == null) {
|
||||
request.response.statusCode = 404;
|
||||
request.response.write('404 Not Found');
|
||||
await request.response.close();
|
||||
} else {
|
||||
// Easy middleware pipeline
|
||||
for (final handler in resolved.handlerSequence) {
|
||||
if (handler is String) {
|
||||
if (!await router.requestMiddleware[handler](request)) break;
|
||||
} else if (!await handler(request)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await request.response.close();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('foo', (HttpRequest request) async {
|
||||
request.response.write('bar');
|
||||
return false;
|
||||
});
|
||||
|
||||
Route square;
|
||||
|
||||
square = router.post('square/:num([0-9]+)', (HttpRequest request) async {
|
||||
final params = square.parseParameters(request.uri.toString());
|
||||
final squared = math.pow(params['num'], 2);
|
||||
request.response.statusCode = squared;
|
||||
request.response.write(squared);
|
||||
return false;
|
||||
});
|
||||
|
||||
router.group('todos', (router) {
|
||||
router.get('/', (HttpRequest request) async {
|
||||
print('TODO INDEX???');
|
||||
request.response.write([]);
|
||||
return false;
|
||||
});
|
||||
}, middleware: [
|
||||
(HttpRequest request) async {
|
||||
request.response.write(MIDDLEWARE_GREETING);
|
||||
return true;
|
||||
}
|
||||
]);
|
||||
|
||||
router.dumpTree();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
client.close();
|
||||
client = null;
|
||||
router = null;
|
||||
url = null;
|
||||
await server.close();
|
||||
});
|
||||
|
||||
group('group', () {
|
||||
test('todo index', () async {
|
||||
final response = await client.get('$url/todos');
|
||||
expect(response.statusCode, equals(200));
|
||||
expect(response.body, equals('$MIDDLEWARE_GREETING[]'));
|
||||
});
|
||||
});
|
||||
|
||||
group('top-level route', () {
|
||||
test('no params', () async {
|
||||
final response = await client.get('$url/foo');
|
||||
expect(response.statusCode, equals(200));
|
||||
expect(response.body, equals('bar'));
|
||||
});
|
||||
|
||||
test('with params', () async {
|
||||
final response = await client.post('$url/square/16');
|
||||
expect(response.statusCode, equals(256));
|
||||
expect(response.body, equals(response.statusCode.toString()));
|
||||
});
|
||||
|
||||
test('throw 404', () async {
|
||||
final response = await client.get('$url/abc');
|
||||
expect(response.statusCode, equals(404));
|
||||
});
|
||||
});
|
||||
}
|
126
test/server_test.dart
Normal file
126
test/server_test.dart
Normal file
|
@ -0,0 +1,126 @@
|
|||
import 'dart:convert';
|
||||
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;
|
||||
final people = [
|
||||
{'name': 'John Smith'}
|
||||
];
|
||||
final Router router = new Router(debug: true);
|
||||
HttpServer server;
|
||||
String url;
|
||||
|
||||
router.get('/', (req, res) {
|
||||
res.write('Root');
|
||||
return false;
|
||||
});
|
||||
|
||||
router.get('/hello', (req, res) {
|
||||
res.write('World');
|
||||
return false;
|
||||
});
|
||||
|
||||
router.group('/people', (router) {
|
||||
router.get('/', (req, res) {
|
||||
res.write(JSON.encode(people));
|
||||
return false;
|
||||
});
|
||||
|
||||
router.group('/:id', (router) {
|
||||
router.get('/', (req, res) {
|
||||
// In a real application, we would take the param,
|
||||
// but not here...
|
||||
res.write(JSON.encode(people.first));
|
||||
return false;
|
||||
});
|
||||
|
||||
router.get('/name', (req, res) {
|
||||
// In a real application, we would take the param,
|
||||
// but not here...
|
||||
res.write(JSON.encode(people.first['name']));
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
client = new http.Client();
|
||||
|
||||
router.dumpTree();
|
||||
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||
url = 'http://${server.address.address}:${server.port}';
|
||||
|
||||
server.listen((req) async {
|
||||
final res = req.response;
|
||||
|
||||
// Easy middleware pipeline
|
||||
final results = router.resolveAll(req.uri.toString(), req.uri.toString(),
|
||||
method: req.method);
|
||||
final pipeline = new MiddlewarePipeline(results);
|
||||
|
||||
if (pipeline.handlers.isEmpty) {
|
||||
res
|
||||
..statusCode = HttpStatus.NOT_FOUND
|
||||
..writeln('404 Not Found');
|
||||
} else {
|
||||
for (final handler in pipeline.handlers) {
|
||||
if (!await handler(req, res)) break;
|
||||
}
|
||||
}
|
||||
|
||||
await res.close();
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await server.close(force: true);
|
||||
client.close();
|
||||
client = null;
|
||||
url = null;
|
||||
});
|
||||
|
||||
group('top-level', () {
|
||||
group('get', () {
|
||||
test('root', () async {
|
||||
final res = await client.get(url);
|
||||
print('Response: ${res.body}');
|
||||
expect(res.body, equals('Root'));
|
||||
});
|
||||
|
||||
test('path', () async {
|
||||
final res = await client.get('$url/hello');
|
||||
print('Response: ${res.body}');
|
||||
expect(res.body, equals('World'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('group', () {
|
||||
group('top-level', () {
|
||||
test('root', () async {
|
||||
final res = await client.get('$url/people');
|
||||
print('Response: ${res.body}');
|
||||
expect(JSON.decode(res.body), equals(people));
|
||||
});
|
||||
|
||||
group('param', () {
|
||||
test('root', () async {
|
||||
final res = await client.get('$url/people/0');
|
||||
print('Response: ${res.body}');
|
||||
expect(JSON.decode(res.body), equals(people.first));
|
||||
});
|
||||
|
||||
test('path', () async {
|
||||
final res = await client.get('$url/people/0/name');
|
||||
print('Response: ${res.body}');
|
||||
expect(JSON.decode(res.body), equals(people.first['name']));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('use', () {});
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final String ARTIFICIAL_INDEX = 'artificial index';
|
||||
|
||||
tattle(x) => 'This ${x.runtimeType}.debug = ${x.debug}';
|
||||
tattleAll(x) => x.map(tattle).join('\n');
|
||||
|
||||
main() {
|
||||
final parent = new Router(debug: true);
|
||||
final child = new Router(debug: true);
|
||||
Route a, b, c;
|
||||
|
||||
a = child.get('a', ['c']);
|
||||
child.group('b', (router) {
|
||||
b = router.get('/', ARTIFICIAL_INDEX);
|
||||
c = router.post('c', 'Hello nested');
|
||||
});
|
||||
|
||||
parent.mount('child', child);
|
||||
parent.dumpTree(header: tattleAll([parent, child, a]));
|
||||
|
||||
group('no params', () {
|
||||
test('resolve', () {
|
||||
expect(child.resolveOnRoot('a'), equals(a));
|
||||
expect(child.resolveOnRoot('b'), equals(b));
|
||||
expect(child.resolveOnRoot('b/c'), equals(c));
|
||||
|
||||
expect(parent.resolveOnRoot('child/a'), equals(a));
|
||||
expect(parent.resolveOnRoot('a'), isNull);
|
||||
expect(parent.resolveOnRoot('child/b'), equals(b));
|
||||
expect(parent.resolveOnRoot('child/b/c'), equals(c));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import 'package:angel_route/browser.dart';
|
||||
import '../shared/basic.dart';
|
||||
|
||||
main() => basic(new BrowserRouter(hash: true));
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<title>Hash Router</title>
|
||||
<style>
|
||||
#routes li {
|
||||
display: inline;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul id="routes">
|
||||
<li><a href="/a">Route A</a></li>
|
||||
<li><a href="/b">Route B</a></li>
|
||||
<li><a href="/b/a">Route B/A</a></li>
|
||||
<li><a href="/b/b">Route B/B</a></li>
|
||||
<li><a href="/c">Route C</a></li>
|
||||
</ul>
|
||||
<h1>No Active Route</h1>
|
||||
<i>Handler Sequence:</i>
|
||||
<ul id="handlers">
|
||||
<li>(empty)</li>
|
||||
</ul>
|
||||
<script src="basic.dart" type="application/dart"></script>
|
||||
<script src="packages/browser/dart.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +0,0 @@
|
|||
import 'package:angel_route/browser.dart';
|
||||
import '../shared/basic.dart';
|
||||
|
||||
main() => basic(new BrowserRouter());
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<title>Push State Router</title>
|
||||
<style>
|
||||
#routes li {
|
||||
display: inline;
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul id="routes">
|
||||
<li><a href="/a">Route A</a></li>
|
||||
<li><a href="/b">Route B</a></li>
|
||||
<li><a href="/b/a">Route B/A</a></li>
|
||||
<li><a href="/b/b">Route B/B</a></li>
|
||||
<li><a href="/c">Route C</a></li>
|
||||
</ul>
|
||||
<h1>No Active Route</h1>
|
||||
<i>Handler Sequence:</i>
|
||||
<ul id="handlers">
|
||||
<li>(empty)</li>
|
||||
</ul>
|
||||
<script src="basic.dart" type="application/dart"></script>
|
||||
<script src="packages/browser/dart.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,34 +0,0 @@
|
|||
import 'dart:html';
|
||||
import 'package:angel_route/browser.dart';
|
||||
|
||||
basic(BrowserRouter router) {
|
||||
final $h1 = window.document.querySelector('h1');
|
||||
final $ul = window.document.getElementById('handlers');
|
||||
|
||||
router.onRoute.listen((route) {
|
||||
if (route == null) {
|
||||
$h1.text = 'No Active Route';
|
||||
$ul.children
|
||||
..clear()
|
||||
..add(new LIElement()..text = '(empty)');
|
||||
} else {
|
||||
$h1.text = 'Active Route: ${route.path}';
|
||||
$ul.children
|
||||
..clear()
|
||||
..addAll(route.handlerSequence
|
||||
.map((handler) => new LIElement()..text = handler.toString()));
|
||||
}
|
||||
});
|
||||
|
||||
router.get('a', 'a handler');
|
||||
|
||||
router.group('b', (router) {
|
||||
print(router.root);
|
||||
router.get('a', 'b/a handler');
|
||||
router.get('b', 'b/b handler', middleware: ['b/b middleware']);
|
||||
}, middleware: ['b middleware']);
|
||||
|
||||
router.get('c', 'c handler');
|
||||
|
||||
router.dumpTree();
|
||||
}
|
Loading…
Reference in a new issue