All tests now passing
This commit is contained in:
parent
6dbccd2be6
commit
1b37e0a2a3
13 changed files with 294 additions and 95 deletions
|
@ -9,6 +9,7 @@
|
||||||
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/test/route/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/test/route/packages" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/test/router/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/test/router/packages" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/test/server/packages" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/web/hash/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/web/hash/packages" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/web/packages" />
|
<excludeFolder url="file://$MODULE_DIR$/web/packages" />
|
||||||
|
|
6
.idea/runConfigurations/All_Server_Tests.xml
Normal file
6
.idea/runConfigurations/All_Server_Tests.xml
Normal file
|
@ -0,0 +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" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -17,7 +17,7 @@ abstract class BrowserRouter extends Router {
|
||||||
: new _PushStateRouter(listen: listen, root: root);
|
: new _PushStateRouter(listen: listen, root: root);
|
||||||
}
|
}
|
||||||
|
|
||||||
BrowserRouter._([Route root]) : super(root);
|
BrowserRouter._([Route root]) : super(root: root);
|
||||||
|
|
||||||
/// Calls `goTo` on the [Route] matching `path`.
|
/// Calls `goTo` on the [Route] matching `path`.
|
||||||
void go(String path, [Map params]);
|
void go(String path, [Map params]);
|
||||||
|
@ -37,7 +37,7 @@ class _BrowserRouterImpl extends Router implements BrowserRouter {
|
||||||
@override
|
@override
|
||||||
Stream<Route> get onRoute => _onRoute.stream;
|
Stream<Route> get onRoute => _onRoute.stream;
|
||||||
|
|
||||||
_BrowserRouterImpl({bool listen, Route root}) : super(root) {
|
_BrowserRouterImpl({bool listen, Route root}) : super(root: root) {
|
||||||
if (listen) this.listen();
|
if (listen) this.listen();
|
||||||
prepareAnchors();
|
prepareAnchors();
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,15 @@ class Route {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [Route] instance that will respond to requests
|
||||||
|
/// to the index of this instance's path.
|
||||||
|
///
|
||||||
|
/// May return `this`.
|
||||||
|
Route get indexRoute {
|
||||||
|
return children.firstWhere((r) => r.path.replaceAll(path, '').isEmpty,
|
||||||
|
orElse: () => this);
|
||||||
|
}
|
||||||
|
|
||||||
void _printDebug(msg) {
|
void _printDebug(msg) {
|
||||||
if (debug) print(msg);
|
if (debug) print(msg);
|
||||||
}
|
}
|
||||||
|
@ -157,6 +166,7 @@ class Route {
|
||||||
/// The final child route is returned.
|
/// The final child route is returned.
|
||||||
factory Route.build(Pattern path,
|
factory Route.build(Pattern path,
|
||||||
{Iterable<Route> children: const [],
|
{Iterable<Route> children: const [],
|
||||||
|
bool debug: false,
|
||||||
Iterable handlers: const [],
|
Iterable handlers: const [],
|
||||||
method: "GET",
|
method: "GET",
|
||||||
String name: null}) {
|
String name: null}) {
|
||||||
|
@ -169,7 +179,11 @@ class Route {
|
||||||
|
|
||||||
if (segments.isEmpty) {
|
if (segments.isEmpty) {
|
||||||
return new Route('/',
|
return new Route('/',
|
||||||
children: children, handlers: handlers, method: method, name: name);
|
children: children,
|
||||||
|
debug: debug,
|
||||||
|
handlers: handlers,
|
||||||
|
method: method,
|
||||||
|
name: name);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < segments.length; i++) {
|
for (int i = 0; i < segments.length; i++) {
|
||||||
|
@ -177,15 +191,15 @@ class Route {
|
||||||
|
|
||||||
if (i == segments.length - 1) {
|
if (i == segments.length - 1) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = new Route(segment);
|
result = new Route(segment, debug: debug);
|
||||||
} else {
|
} else {
|
||||||
result = result.child(segment);
|
result = result.child(segment, debug: debug);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = new Route(segment, method: "*");
|
result = new Route(segment, debug: debug, method: "*");
|
||||||
} else {
|
} else {
|
||||||
result = result.child(segment, method: "*");
|
result = result.child(segment, debug: debug, method: "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,11 +209,11 @@ class Route {
|
||||||
result._method = method;
|
result._method = method;
|
||||||
result._name = name;
|
result._name = name;
|
||||||
|
|
||||||
return result;
|
return result..debug = debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combines the paths and matchers of two [Route] instances, and creates a new instance.
|
/// Combines the paths and matchers of two [Route] instances, and creates a new instance.
|
||||||
factory Route.join(Route parent, Route child) {
|
factory Route.join(Route parent, Route child, {bool debug: false}) {
|
||||||
final String path1 = parent.path
|
final String path1 = parent.path
|
||||||
.replaceAll(_rgxStart, '')
|
.replaceAll(_rgxStart, '')
|
||||||
.replaceAll(_rgxEnd, '')
|
.replaceAll(_rgxEnd, '')
|
||||||
|
@ -217,7 +231,6 @@ class Route {
|
||||||
|
|
||||||
final route = new Route('$path1/$path2',
|
final route = new Route('$path1/$path2',
|
||||||
children: child.children,
|
children: child.children,
|
||||||
debug: parent.debug || child.debug,
|
|
||||||
handlers: child.handlers,
|
handlers: child.handlers,
|
||||||
method: child.method,
|
method: child.method,
|
||||||
name: child.name);
|
name: child.name);
|
||||||
|
@ -228,10 +241,29 @@ class Route {
|
||||||
.._matcher = new RegExp('$pattern1$separator$pattern2')
|
.._matcher = new RegExp('$pattern1$separator$pattern2')
|
||||||
.._parent = parent
|
.._parent = parent
|
||||||
.._stub = child.matcher);
|
.._stub = child.matcher);
|
||||||
parent._printDebug(
|
|
||||||
'Joined $path1 and $path2, produced stub ${route._stub.pattern}');
|
|
||||||
|
|
||||||
return route;
|
parent._printDebug(
|
||||||
|
"Joined '/$path1' and '/$path2', created stub: ${route._stub.pattern}");
|
||||||
|
|
||||||
|
return route..debug = parent.debug || child.debug || debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
Route _inherit(Route route) {
|
||||||
|
/*
|
||||||
|
final List<Route> _children = [];
|
||||||
|
final List _handlers = [];
|
||||||
|
RegExp _matcher;
|
||||||
|
String _method;
|
||||||
|
String _name;
|
||||||
|
Route _parent;
|
||||||
|
RegExp _parentResolver;
|
||||||
|
String _path;
|
||||||
|
String _pathified;
|
||||||
|
RegExp _resolver;
|
||||||
|
RegExp _stub;
|
||||||
|
|
||||||
|
*/
|
||||||
|
return route.._parent = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls [addChild] on all given routes.
|
/// Calls [addChild] on all given routes.
|
||||||
|
@ -241,7 +273,14 @@ class Route {
|
||||||
|
|
||||||
/// Adds the given route as a hierarchical child of this one.
|
/// Adds the given route as a hierarchical child of this one.
|
||||||
Route addChild(Route route, {bool join: true}) {
|
Route addChild(Route route, {bool join: true}) {
|
||||||
Route created = join ? new Route.join(this, route) : route.._parent = this;
|
Route created;
|
||||||
|
|
||||||
|
if (join) {
|
||||||
|
created = new Route.join(this, route);
|
||||||
|
} else {
|
||||||
|
_children.add(created = route.._parent = this);
|
||||||
|
}
|
||||||
|
|
||||||
return created..debug = debug;
|
return created..debug = debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,11 +290,16 @@ class Route {
|
||||||
/// Creates a hierarchical child of this route with the given path.
|
/// Creates a hierarchical child of this route with the given path.
|
||||||
Route child(Pattern path,
|
Route child(Pattern path,
|
||||||
{Iterable<Route> children: const [],
|
{Iterable<Route> children: const [],
|
||||||
|
bool debug: false,
|
||||||
Iterable handlers: const [],
|
Iterable handlers: const [],
|
||||||
String method: "GET",
|
String method: "GET",
|
||||||
String name: null}) {
|
String name: null}) {
|
||||||
final route = new Route.build(path,
|
final route = new Route.build(path,
|
||||||
children: children, handlers: handlers, method: method, name: name);
|
children: children,
|
||||||
|
debug: debug,
|
||||||
|
handlers: handlers,
|
||||||
|
method: method,
|
||||||
|
name: name);
|
||||||
return addChild(route);
|
return addChild(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,25 +354,39 @@ class Route {
|
||||||
///
|
///
|
||||||
/// Can be used to navigate a route hierarchy like a file system.
|
/// Can be used to navigate a route hierarchy like a file system.
|
||||||
Route resolve(String path, {bool filter(Route route), String fullPath}) {
|
Route resolve(String path, {bool filter(Route route), String fullPath}) {
|
||||||
final _filter = filter ?? (_) => true;
|
bool _filter(route) {
|
||||||
|
if (filter == null) {
|
||||||
|
_printDebug('No filter provided, returning true for $route');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
_printDebug('Running filter on $route');
|
||||||
|
final result = filter(route);
|
||||||
|
_printDebug('Filter result: $result');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final _fullPath = fullPath ?? path;
|
final _fullPath = fullPath ?? path;
|
||||||
|
|
||||||
if ((path.isEmpty || path == '.') && _filter(this)) {
|
if ((path.isEmpty || path == '.') && _filter(indexRoute)) {
|
||||||
// Try to find index
|
// Try to find index
|
||||||
_printDebug('INDEX???');
|
_printDebug('Empty path, resolving with indexRoute: $indexRoute');
|
||||||
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this);
|
return indexRoute;
|
||||||
} else if (path == '/') {
|
} else if (path == '/') {
|
||||||
return absoluteParent.resolve('');
|
return absoluteParent.resolve('');
|
||||||
} else if (path.replaceAll(_straySlashes, '').isEmpty) {
|
} else if (path.replaceAll(_straySlashes, '').isEmpty) {
|
||||||
for (Route route in children) {
|
for (Route route in children) {
|
||||||
final stub = route.path.replaceAll(this.path, '');
|
final stub = route.path.replaceAll(this.path, '');
|
||||||
|
|
||||||
if ((stub == '/' || stub.isEmpty) && _filter(route)) return route;
|
if ((stub == '/' || stub.isEmpty) && _filter(route))
|
||||||
|
return route.resolve('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_filter(this))
|
if (_filter(indexRoute)) {
|
||||||
return this;
|
_printDebug(
|
||||||
else
|
'Path "/$path" is technically empty, sending to indexRoute: $indexRoute');
|
||||||
|
return indexRoute;
|
||||||
|
} else
|
||||||
return null;
|
return null;
|
||||||
} else if (path == '..') {
|
} else if (path == '..') {
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
|
@ -343,13 +401,16 @@ class Route {
|
||||||
filter: _filter, fullPath: _fullPath);
|
filter: _filter, fullPath: _fullPath);
|
||||||
} else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) ||
|
} else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) ||
|
||||||
_resolver.hasMatch(path.replaceAll(_straySlashes, ''))) {
|
_resolver.hasMatch(path.replaceAll(_straySlashes, ''))) {
|
||||||
return this;
|
_printDebug(
|
||||||
|
'Path "/$path" matched our matcher, sending to indexRoute: $indexRoute');
|
||||||
|
return indexRoute;
|
||||||
} else {
|
} else {
|
||||||
final segments = path.split('/').where((str) => str.isNotEmpty).toList();
|
final segments = path.split('/').where((str) => str.isNotEmpty).toList();
|
||||||
_printDebug('Segments: $segments on "/${this.path}"');
|
_printDebug('Segments: $segments on "/${this.path}"');
|
||||||
|
|
||||||
if (segments.isEmpty) {
|
if (segments.isEmpty) {
|
||||||
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this);
|
_printDebug('Empty segments, sending to indexRoute: $indexRoute');
|
||||||
|
return indexRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segments[0] == '..') {
|
if (segments[0] == '..') {
|
||||||
|
@ -366,12 +427,12 @@ class Route {
|
||||||
for (Route route in children) {
|
for (Route route in children) {
|
||||||
final subPath = '${this.path}/${segments[0]}';
|
final subPath = '${this.path}/${segments[0]}';
|
||||||
_printDebug(
|
_printDebug(
|
||||||
'seg0: ${segments[0]}, stub: ${route._stub.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}');
|
'seg0: ${segments[0]}, stub: ${route._stub?.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}');
|
||||||
|
|
||||||
if (route.match(subPath) != null ||
|
if (route.match(subPath) != null ||
|
||||||
route._resolver.firstMatch(subPath) != null) {
|
route._resolver.firstMatch(subPath) != null) {
|
||||||
if (segments.length == 1 && _filter(route))
|
if (segments.length == 1 && _filter(route))
|
||||||
return route;
|
return route.resolve('');
|
||||||
else {
|
else {
|
||||||
return route.resolve(segments.skip(1).join('/'),
|
return route.resolve(segments.skip(1).join('/'),
|
||||||
filter: _filter,
|
filter: _filter,
|
||||||
|
@ -380,8 +441,14 @@ class Route {
|
||||||
_fullPath.replaceAll(_straySlashes, ''));
|
_fullPath.replaceAll(_straySlashes, ''));
|
||||||
}
|
}
|
||||||
} else if (route._stub != null && route._stub.hasMatch(segments[0])) {
|
} else if (route._stub != null && route._stub.hasMatch(segments[0])) {
|
||||||
_printDebug('MAYBE STUB?');
|
if (segments.length == 1) {
|
||||||
return route;
|
_printDebug('Stub perhaps matches');
|
||||||
|
return route.resolve('');
|
||||||
|
} else {
|
||||||
|
_printDebug(
|
||||||
|
'Maybe stub matches. Sending remaining segments to $route');
|
||||||
|
return route.resolve(segments.skip(1).join('/'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,38 +472,11 @@ class Route {
|
||||||
(child.match(_fullPath) != null ||
|
(child.match(_fullPath) != null ||
|
||||||
child._resolver.firstMatch(_fullPath) != null) &&
|
child._resolver.firstMatch(_fullPath) != null) &&
|
||||||
_filter(child)) {
|
_filter(child)) {
|
||||||
return child;
|
return child.resolve('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_printDebug('No subpath match: $subPath');
|
|
||||||
} else
|
|
||||||
_printDebug('Nope: $_parentResolver');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Try to fill params
|
|
||||||
for (Route route in children) {
|
|
||||||
final params = parseParameters(_fullPath);
|
|
||||||
final _filledPath = makeUri(params);
|
|
||||||
_printDebug(
|
|
||||||
'Trying to match filled $_filledPath for ${route.path} on ${this.path}');
|
|
||||||
if ((route.match(_filledPath) != null ||
|
|
||||||
route._resolver.firstMatch(_filledPath) != null) &&
|
|
||||||
_filter(route))
|
|
||||||
return route;
|
|
||||||
else if ((route.match(_filledPath) != null ||
|
|
||||||
route._resolver.firstMatch(_filledPath) != null) &&
|
|
||||||
_filter(route))
|
|
||||||
return route;
|
|
||||||
else if ((route.match('/$_filledPath') != null ||
|
|
||||||
route._resolver.firstMatch('/$_filledPath') != null) &&
|
|
||||||
_filter(route))
|
|
||||||
return route;
|
|
||||||
else {
|
|
||||||
_printDebug('Failed for ${route.matcher} when given $_filledPath');
|
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
// Try to match the whole route, if nothing else works
|
// Try to match the whole route, if nothing else works
|
||||||
for (Route route in children) {
|
for (Route route in children) {
|
||||||
|
@ -445,18 +485,20 @@ class Route {
|
||||||
if ((route.match(_fullPath) != null ||
|
if ((route.match(_fullPath) != null ||
|
||||||
route._resolver.firstMatch(_fullPath) != null) &&
|
route._resolver.firstMatch(_fullPath) != null) &&
|
||||||
_filter(route))
|
_filter(route))
|
||||||
return route;
|
return route.resolve('');
|
||||||
else if ((route.match(_fullPath) != null ||
|
else if ((route.match(_fullPath) != null ||
|
||||||
route._resolver.firstMatch(_fullPath) != null) &&
|
route._resolver.firstMatch(_fullPath) != null) &&
|
||||||
_filter(route))
|
_filter(route))
|
||||||
return route;
|
return route.resolve('');
|
||||||
else if ((route.match('/$_fullPath') != null ||
|
else if ((route.match('/$_fullPath') != null ||
|
||||||
route._resolver.firstMatch('/$_fullPath') != null) &&
|
route._resolver.firstMatch('/$_fullPath') != null) &&
|
||||||
_filter(route))
|
_filter(route)) return route.resolve('');
|
||||||
return route;
|
}
|
||||||
else {
|
|
||||||
_printDebug('Failed for ${route.matcher} when given $_fullPath');
|
// Lastly, check to see if we have an index route to resolve with
|
||||||
}
|
if (indexRoute != this) {
|
||||||
|
_printDebug('Forwarding "/$path" to indexRoute');
|
||||||
|
return indexRoute.resolve(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -6,6 +6,8 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||||
|
|
||||||
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
|
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
|
||||||
class Router extends Extensible {
|
class Router extends Extensible {
|
||||||
|
Route _root;
|
||||||
|
|
||||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
/// Set to `true` to print verbose debug output when interacting with this route.
|
||||||
bool debug = false;
|
bool debug = false;
|
||||||
|
|
||||||
|
@ -13,15 +15,16 @@ class Router extends Extensible {
|
||||||
Map<String, dynamic> requestMiddleware = {};
|
Map<String, dynamic> requestMiddleware = {};
|
||||||
|
|
||||||
/// The single [Route] that serves as the root of the hierarchy.
|
/// The single [Route] that serves as the root of the hierarchy.
|
||||||
final Route root;
|
Route get root => _root;
|
||||||
|
|
||||||
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
||||||
/// Not recommended.
|
/// Not recommended.
|
||||||
Router([Route root]) : this.root = root ?? new _RootRoute();
|
Router({this.debug: false, Route root}) {
|
||||||
|
_root = (_root = root ?? new _RootRoute())..debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
void _printDebug(msg) {
|
void _printDebug(msg) {
|
||||||
if (debug)
|
if (debug) print(msg);
|
||||||
_printDebug(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to the given path
|
/// Adds a route that responds to the given path
|
||||||
|
@ -36,9 +39,10 @@ class Router extends Extensible {
|
||||||
..add(handler);
|
..add(handler);
|
||||||
|
|
||||||
if (path is RegExp) {
|
if (path is RegExp) {
|
||||||
return root.child(path, handlers: handlers, method: method);
|
return root.child(path, debug: debug, handlers: handlers, method: method);
|
||||||
} else if (path.toString().replaceAll(_straySlashes, '').isEmpty) {
|
} else if (path.toString().replaceAll(_straySlashes, '').isEmpty) {
|
||||||
return root.child(path.toString(), handlers: handlers, method: method);
|
return root.child(path.toString(),
|
||||||
|
debug: debug, handlers: handlers, method: method);
|
||||||
} else {
|
} else {
|
||||||
var segments = path
|
var segments = path
|
||||||
.toString()
|
.toString()
|
||||||
|
@ -48,9 +52,9 @@ class Router extends Extensible {
|
||||||
Route result;
|
Route result;
|
||||||
|
|
||||||
if (segments.isEmpty) {
|
if (segments.isEmpty) {
|
||||||
return new Route('/', handlers: handlers, method: method);
|
return new Route('/', debug: debug, handlers: handlers, method: method)
|
||||||
|
..debug = debug;
|
||||||
} else {
|
} else {
|
||||||
_printDebug('Want ${segments[0]}');
|
|
||||||
result = resolve(segments[0]);
|
result = resolve(segments[0]);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
@ -67,7 +71,9 @@ class Router extends Extensible {
|
||||||
result = existing;
|
result = existing;
|
||||||
}
|
}
|
||||||
} while (existing != null);
|
} while (existing != null);
|
||||||
} else throw new RoutingException("Cannot overwrite existing route '${segments[0]}'.");
|
} else
|
||||||
|
throw new RoutingException(
|
||||||
|
"Cannot overwrite existing route '${segments[0]}'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,15 +82,17 @@ class Router extends Extensible {
|
||||||
|
|
||||||
if (i == segments.length - 1) {
|
if (i == segments.length - 1) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = root.child(segment, handlers: handlers, method: method);
|
result = root.child(segment,
|
||||||
|
debug: debug, handlers: handlers, method: method);
|
||||||
} else {
|
} else {
|
||||||
result = result.child(segment, handlers: handlers, method: method);
|
result = result.child(segment,
|
||||||
|
debug: debug, handlers: handlers, method: method);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = root.child(segment, method: "*");
|
result = root.child(segment, debug: debug, method: "*");
|
||||||
} else {
|
} else {
|
||||||
result = result.child(segment, method: "*");
|
result = result.child(segment, debug: debug, method: "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +113,8 @@ class Router extends Extensible {
|
||||||
|
|
||||||
if (route == root)
|
if (route == root)
|
||||||
buf.write('(root) ${route.method} ');
|
buf.write('(root) ${route.method} ');
|
||||||
else buf.write('- ${route.method} ');
|
else
|
||||||
|
buf.write('- ${route.method} ');
|
||||||
|
|
||||||
final p =
|
final p =
|
||||||
replace != null ? route.path.replaceAll(replace, '') : route.path;
|
replace != null ? route.path.replaceAll(replace, '') : route.path;
|
||||||
|
@ -143,7 +152,7 @@ class Router extends Extensible {
|
||||||
String namespace: null}) {
|
String namespace: null}) {
|
||||||
final route =
|
final route =
|
||||||
root.child(path, handlers: middleware, method: method, name: name);
|
root.child(path, handlers: middleware, method: method, name: name);
|
||||||
final router = new Router(route);
|
final router = new Router(root: route);
|
||||||
callback(router);
|
callback(router);
|
||||||
|
|
||||||
// Let's copy middleware, heeding the optional middleware namespace.
|
// Let's copy middleware, heeding the optional middleware namespace.
|
||||||
|
@ -191,7 +200,7 @@ class Router extends Extensible {
|
||||||
copiedMiddleware[middlewareName];
|
copiedMiddleware[middlewareName];
|
||||||
}
|
}
|
||||||
|
|
||||||
root.child(path).addChild(router.root);
|
root.child(path, debug: debug).addChild(router.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to any request matching the given path.
|
/// Adds a route that responds to any request matching the given path.
|
||||||
|
@ -236,9 +245,8 @@ class Router extends Extensible {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RootRoute extends Route {
|
class _RootRoute extends Route {
|
||||||
_RootRoute():super("/", name: "<root>");
|
_RootRoute() : super("/", name: "<root>");
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => "ROOT";
|
String toString() => "ROOT";
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,5 @@ author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_route
|
homepage: https://github.com/angel-dart/angel_route
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
browser: ">=0.10.0 < 0.11.0"
|
browser: ">=0.10.0 < 0.11.0"
|
||||||
|
http: ">=0.11.3 <0.12.0"
|
||||||
test: ">=0.12.15 <0.13.0"
|
test: ">=0.12.15 <0.13.0"
|
8
test/all_tests.browser.dart
Normal file
8
test/all_tests.browser.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'route/all_tests.dart' as route;
|
||||||
|
import 'router/all_tests.dart' as router;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
group('route', route.main);
|
||||||
|
group('router', router.main);
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'route/all_tests.dart' as route;
|
import 'route/all_tests.dart' as route;
|
||||||
import 'router/all_tests.dart' as router;
|
import 'router/all_tests.dart' as router;
|
||||||
|
import 'server/all_tests.dart' as server;
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
group('route', route.main);
|
group('route', route.main);
|
||||||
group('router', router.main);
|
group('router', router.main);
|
||||||
|
group('server', server.main);
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
<title>Tests</title>
|
<title>Tests</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="all_tests.dart" type="application/dart"></script>
|
<script src="all_tests.browser.dart" type="application/dart"></script>
|
||||||
<script src="packages/browser/dart.js"></script>
|
<script src="packages/browser/dart.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -7,7 +7,7 @@ main() {
|
||||||
final bar = fooById.child('bar');
|
final bar = fooById.child('bar');
|
||||||
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
|
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
|
||||||
final bazById = baz.child(':bazId([A-Za-z]+)');
|
final bazById = baz.child(':bazId([A-Za-z]+)');
|
||||||
new Router(foo).dumpTree();
|
new Router(root: foo).dumpTree();
|
||||||
|
|
||||||
test('matching', () {
|
test('matching', () {
|
||||||
expect(fooById.children.length, equals(1));
|
expect(fooById.children.length, equals(1));
|
||||||
|
@ -54,7 +54,7 @@ main() {
|
||||||
|
|
||||||
expect(bar.resolve('..'), equals(fooById));
|
expect(bar.resolve('..'), equals(fooById));
|
||||||
|
|
||||||
new Router(bar.parent).dumpTree(header: "POOP");
|
new Router(root: bar.parent).dumpTree(header: "POOP");
|
||||||
expect(bar.parent.resolve('bar/baz'), equals(baz));
|
expect(bar.parent.resolve('bar/baz'), equals(baz));
|
||||||
expect(bar.resolve('/2/bar/baz'), equals(baz));
|
expect(bar.resolve('/2/bar/baz'), equals(baz));
|
||||||
expect(bar.resolve('../bar'), equals(bar));
|
expect(bar.resolve('../bar'), equals(bar));
|
||||||
|
|
|
@ -9,7 +9,8 @@ main() {
|
||||||
final router = new Router();
|
final router = new Router();
|
||||||
final indexRoute = router.get('/', () => ':)');
|
final indexRoute = router.get('/', () => ':)');
|
||||||
final fizz = router.post('/user/fizz', null);
|
final fizz = router.post('/user/fizz', null);
|
||||||
final deleteUserById = router.delete('/user/:id/detail', (id) => num.parse(id));
|
final deleteUserById =
|
||||||
|
router.delete('/user/:id/detail', (id) => num.parse(id));
|
||||||
|
|
||||||
Route lower;
|
Route lower;
|
||||||
final letters = router.group('/letter///', (router) {
|
final letters = router.group('/letter///', (router) {
|
||||||
|
@ -29,6 +30,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group('fallback', fallback.main);
|
group('fallback', fallback.main);
|
||||||
|
test('group & use', use.main);
|
||||||
|
|
||||||
test('hierarchy', () {
|
test('hierarchy', () {
|
||||||
expect(lower.absoluteParent, equals(router.root));
|
expect(lower.absoluteParent, equals(router.root));
|
||||||
|
@ -45,7 +47,4 @@ main() {
|
||||||
expect(router.resolve('letter/a/lower'), equals(lower));
|
expect(router.resolve('letter/a/lower'), equals(lower));
|
||||||
expect(router.resolve('letter/2/lower'), isNull);
|
expect(router.resolve('letter/2/lower'), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('use', use.main);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,35 @@
|
||||||
import 'package:angel_route/angel_route.dart';
|
import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:test/test.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() {
|
main() {
|
||||||
final parent = new Router()..debug = true;
|
final parent = new Router(debug: true);
|
||||||
final child = new Router()..debug = true;
|
final child = new Router(debug: true);
|
||||||
final a = child.get('a', ['c']);
|
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.use('child', child);
|
parent.use('child', child);
|
||||||
parent.dumpTree();
|
parent.dumpTree(header: tattleAll([parent, child, a]));
|
||||||
|
|
||||||
group('no params', () {
|
group('no params', () {
|
||||||
test('resolve', () {
|
test('resolve', () {
|
||||||
|
expect(child.resolve('a'), equals(a));
|
||||||
|
expect(child.resolve('b'), equals(b));
|
||||||
|
expect(child.resolve('b/c'), equals(c));
|
||||||
|
|
||||||
expect(parent.resolve('child/a'), equals(a));
|
expect(parent.resolve('child/a'), equals(a));
|
||||||
expect(parent.resolve('a'), isNull);
|
expect(parent.resolve('a'), isNull);
|
||||||
|
expect(parent.resolve('child/b'), equals(b));
|
||||||
|
expect(parent.resolve('child/b/c'), equals(c));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
114
test/server/all_tests.dart
Normal file
114
test/server/all_tests.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
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.toString(), (route) {
|
||||||
|
print(
|
||||||
|
'$route matches ${request.method} ${request.uri}? ${route.method == request.method || route.method == '*'}');
|
||||||
|
return route.method == request.method || route.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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue