Updated Angel Route to Null Safety

This commit is contained in:
thomashii 2021-03-18 08:11:45 +08:00
parent d62a4aa7ac
commit 5d722a6041
16 changed files with 174 additions and 193 deletions

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/route.iml" filepath="$PROJECT_DIR$/route.iml" />
</modules>
</component>
</project>

View file

@ -1,8 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests in route" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$" />
<option name="scope" value="FOLDER" />
<option name="testRunnerOptions" value="-j4" />
<method />
</configuration>
</component>

View file

@ -12,7 +12,7 @@ main() {
router.get('/ordinal/int:n([0-9]+)st', () {});
print(router.resolveAbsolute('/whois/~thosakwe').first.allParams);
print(router.resolveAbsolute('/wild_thornberrys').first.route.path);
print(router.resolveAbsolute('/wild_thornberrys').first.route!.path);
print(router.resolveAbsolute('/ordinal/1st').first.allParams);
router.get('/users', () {});

View file

@ -11,10 +11,10 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
/// A variation of the [Router] support both hash routing and push state.
abstract class BrowserRouter<T> extends Router<T> {
/// Fires whenever the active route changes. Fires `null` if none is selected (404).
Stream<RoutingResult<T>> get onResolve;
Stream<RoutingResult<T>?> get onResolve;
/// Fires whenever the active route changes. Fires `null` if none is selected (404).
Stream<Route<T>> get onRoute;
Stream<Route<T>?> get onRoute;
/// Set `hash` to true to use hash routing instead of push state.
/// `listen` as `true` will call `listen` after initialization.
@ -26,7 +26,7 @@ abstract class BrowserRouter<T> extends Router<T> {
BrowserRouter._() : super();
void _goTo(String path);
void _goTo(String? path);
/// Navigates to the path generated by calling
/// [navigate] with the given [linkParams].
@ -41,26 +41,26 @@ abstract class BrowserRouter<T> extends Router<T> {
void listen();
/// Identical to [all].
Route on(String path, T handler, {Iterable<T> middleware});
Route on(String path, T handler, {Iterable<T>? middleware});
}
abstract class _BrowserRouterImpl<T> extends Router<T>
implements BrowserRouter<T> {
bool _listening = false;
Route _current;
StreamController<RoutingResult<T>> _onResolve =
StreamController<RoutingResult<T>>();
StreamController<Route<T>> _onRoute = StreamController<Route<T>>();
Route? _current;
StreamController<RoutingResult<T>?> _onResolve =
StreamController<RoutingResult<T>?>();
StreamController<Route<T>?> _onRoute = StreamController<Route<T>?>();
Route get currentRoute => _current;
Route? get currentRoute => _current;
@override
Stream<RoutingResult<T>> get onResolve => _onResolve.stream;
Stream<RoutingResult<T>?> get onResolve => _onResolve.stream;
@override
Stream<Route<T>> get onRoute => _onRoute.stream;
Stream<Route<T>?> get onRoute => _onRoute.stream;
_BrowserRouterImpl({bool listen}) : super() {
_BrowserRouterImpl({bool? listen}) : super() {
if (listen != false) this.listen();
prepareAnchors();
}
@ -68,7 +68,7 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
@override
void go(Iterable linkParams) => _goTo(navigate(linkParams));
Route on(String path, T handler, {Iterable<T> middleware}) =>
Route on(String path, T handler, {Iterable<T>? middleware}) =>
all(path, handler, middleware: middleware);
void prepareAnchors() {
@ -105,18 +105,18 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
}
class _HashRouter<T> extends _BrowserRouterImpl<T> {
_HashRouter({bool listen}) : super(listen: listen) {
_HashRouter({required bool listen}) : super(listen: listen) {
if (listen) this.listen();
}
@override
void _goTo(String uri) {
void _goTo(String? uri) {
window.location.hash = '#$uri';
}
void handleHash([_]) {
final path = window.location.hash.replaceAll(_hash, '');
var allResolved = resolveAbsolute(path);
Iterable<RoutingResult<T>> allResolved = resolveAbsolute(path);
final resolved = allResolved.isEmpty ? null : allResolved.first;
@ -130,7 +130,7 @@ class _HashRouter<T> extends _BrowserRouterImpl<T> {
}
void handlePath(String path) {
final resolved = resolveAbsolute(path).first;
final RoutingResult<T> resolved = resolveAbsolute(path).first;
if (resolved == null) {
_onResolve.add(null);
@ -149,9 +149,9 @@ class _HashRouter<T> extends _BrowserRouterImpl<T> {
}
class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
String _basePath;
String? _basePath;
_PushStateRouter({bool listen, Route root}) : super(listen: listen) {
_PushStateRouter({required bool listen, Route? root}) : super(listen: listen) {
var $base = window.document.querySelector('base[href]') as BaseElement;
if ($base?.href?.isNotEmpty != true) {
@ -163,19 +163,19 @@ class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
}
@override
void _goTo(String uri) {
final resolved = resolveAbsolute(uri).first;
void _goTo(String? uri) {
final RoutingResult<T> resolved = resolveAbsolute(uri).first;
var relativeUri = uri;
if (_basePath?.isNotEmpty == true) {
relativeUri = p.join(_basePath, uri.replaceAll(_straySlashes, ''));
relativeUri = p.join(_basePath!, uri!.replaceAll(_straySlashes, ''));
}
if (resolved == null) {
_onResolve.add(null);
_onRoute.add(_current = null);
} else {
final route = resolved.route;
final route = resolved.route!;
window.history.pushState({'path': route.path, 'params': {}},
route.name ?? route.path, relativeUri);
_onResolve.add(resolved);
@ -186,7 +186,7 @@ class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
void handleState(state) {
if (state is Map && state.containsKey('path')) {
var path = state['path'].toString();
final resolved = resolveAbsolute(path).first;
final RoutingResult<T> resolved = resolveAbsolute(path).first;
if (resolved != null && resolved.route != _current) {
//properties.addAll(state['properties'] ?? {});

View file

@ -4,14 +4,14 @@ class RouteGrammar {
static const String notSlashRgx = r'([^/]+)';
//static final RegExp rgx = RegExp(r'\((.+)\)');
static final Parser<String> notSlash =
match<String>(RegExp(notSlashRgx)).value((r) => r.span.text);
match<String>(RegExp(notSlashRgx)).value((r) => r.span!.text);
static final Parser<Match> regExp =
match<Match>(RegExp(r'\(([^\n)]+)\)([^/]+)?'))
static final Parser<Match?> regExp =
match<Match?>(RegExp(r'\(([^\n)]+)\)([^/]+)?'))
.value((r) => r.scanner.lastMatch);
static final Parser<Match> parameterName =
match<Match>(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?'))
static final Parser<Match?> parameterName =
match<Match?>(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?'))
.value((r) => r.scanner.lastMatch);
static final Parser<ParameterSegment> parameterSegment = chain([
@ -19,12 +19,12 @@ class RouteGrammar {
match<bool>('?').value((r) => true).opt(),
regExp.opt(),
]).map((r) {
var match = r.value[0] as Match;
var rgxMatch = r.value[2] as Match;
var match = r.value![0] as Match;
var rgxMatch = r.value![2] as Match?;
var pre = match[1] ?? '';
var post = match[3] ?? '';
RegExp rgx;
RegExp? rgx;
if (rgxMatch != null) {
rgx = RegExp('(${rgxMatch[1]})');
@ -41,23 +41,23 @@ class RouteGrammar {
}
var s = ParameterSegment(match[2], rgx);
return r.value[1] == true ? OptionalSegment(s) : s;
return r.value![1] == true ? OptionalSegment(s) : s;
});
static final Parser<ParsedParameterSegment> parsedParameterSegment = chain([
match(RegExp(r'(int|num|double)'),
errorMessage: 'Expected "int","double", or "num".')
.map((r) => r.span.text),
.map((r) => r.span!.text),
parameterSegment,
]).map((r) {
return ParsedParameterSegment(
r.value[0] as String, r.value[1] as ParameterSegment);
r.value![0] as String, r.value![1] as ParameterSegment);
});
static final Parser<WildcardSegment> wildcardSegment =
match<WildcardSegment>(RegExp('$notSlashRgx?' r'\*' '$notSlashRgx?'))
.value((r) {
var m = r.scanner.lastMatch;
var m = r.scanner.lastMatch!;
var pre = m[1] ?? '';
var post = m[2] ?? '';
return WildcardSegment(pre, post);
@ -95,8 +95,8 @@ class RouteDefinition {
RouteDefinition(this.segments);
Parser<RouteResult> compile() {
Parser<RouteResult> out;
Parser<RouteResult?>? compile() {
Parser<RouteResult?>? out;
for (int i = 0; i < segments.length; i++) {
var s = segments[i];
@ -116,7 +116,7 @@ class RouteDefinition {
abstract class RouteSegment {
Parser<RouteResult> compile(bool isLast);
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast);
Parser<RouteResult?> compileNext(Parser<RouteResult> p, bool isLast);
}
class SlashSegment implements RouteSegment {
@ -139,7 +139,7 @@ class SlashSegment implements RouteSegment {
}
class ConstantSegment extends RouteSegment {
final String text;
final String? text;
ConstantSegment(this.text);
@ -150,7 +150,7 @@ class ConstantSegment extends RouteSegment {
@override
Parser<RouteResult> compile(bool isLast) {
return match(text).map((r) => RouteResult({}));
return match(text!).map((r) => RouteResult({}));
}
@override
@ -183,13 +183,13 @@ class WildcardSegment extends RouteSegment {
@override
Parser<RouteResult> compile(bool isLast) {
return match(_compile(isLast))
.map((r) => RouteResult({}, tail: r.scanner.lastMatch[1]));
.map((r) => RouteResult({}, tail: r.scanner.lastMatch![1]));
}
@override
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
return p.then(compile(isLast)).map((r) {
var items = r.value.cast<RouteResult>();
var items = r.value!.cast<RouteResult>();
var a = items[0], b = items[1];
return a
..addAll(b?.params ?? {})
@ -214,44 +214,44 @@ class OptionalSegment extends ParameterSegment {
}
@override
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
Parser<RouteResult?> compileNext(Parser<RouteResult> p, bool isLast) {
return p.then(_compile().opt()).map((r) {
if (r.value[1] == null) return r.value[0] as RouteResult;
return (r.value[0] as RouteResult)
..addAll({name: Uri.decodeComponent(r.value[1] as String)});
if (r.value![1] == null) return r.value![0] as RouteResult?;
return (r.value![0] as RouteResult)
..addAll({name: Uri.decodeComponent(r.value![1] as String)});
});
}
}
class ParameterSegment extends RouteSegment {
final String name;
final RegExp regExp;
final String? name;
final RegExp? regExp;
ParameterSegment(this.name, this.regExp);
@override
String toString() {
if (regExp != null) return 'Param: $name (${regExp.pattern})';
if (regExp != null) return 'Param: $name (${regExp!.pattern})';
return 'Param: $name';
}
Parser<String> _compile() {
Parser<String?> _compile() {
return regExp != null
? match<String>(regExp).value((r) => r.scanner.lastMatch[1])
? match<String?>(regExp!).value((r) => r.scanner.lastMatch![1])
: RouteGrammar.notSlash;
}
@override
Parser<RouteResult> compile(bool isLast) {
return _compile()
.map((r) => RouteResult({name: Uri.decodeComponent(r.value)}));
.map((r) => RouteResult({name: Uri.decodeComponent(r.value!)}));
}
@override
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
Parser<RouteResult?> compileNext(Parser<RouteResult> p, bool isLast) {
return p.then(_compile()).map((r) {
return (r.value[0] as RouteResult)
..addAll({name: Uri.decodeComponent(r.value[1] as String)});
return (r.value![0] as RouteResult)
..addAll({name: Uri.decodeComponent(r.value![1] as String)});
});
}
}
@ -276,15 +276,15 @@ class ParsedParameterSegment extends RouteSegment {
@override
Parser<RouteResult> compile(bool isLast) {
return parameter._compile().map((r) => RouteResult(
{parameter.name: getValue(Uri.decodeComponent(r.span.text))}));
{parameter.name: getValue(Uri.decodeComponent(r.span!.text))}));
}
@override
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
return p.then(parameter._compile()).map((r) {
return (r.value[0] as RouteResult)
return (r.value![0] as RouteResult)
..addAll({
parameter.name: getValue(Uri.decodeComponent(r.value[1] as String))
parameter.name: getValue(Uri.decodeComponent(r.value![1] as String))
});
});
}

View file

@ -4,10 +4,10 @@ import 'router.dart';
class MiddlewarePipeline<T> {
/// All the possible routes that matched the given path.
final Iterable<RoutingResult<T>> routingResults;
List<T> _handlers;
List<T>? _handlers;
/// An ordered list of every handler delegated to handle this request.
List<T> get handlers {
List<T>? get handlers {
if (_handlers != null) return _handlers;
final handlers = <T>[];

View file

@ -2,17 +2,17 @@ part of angel_route.src.router;
/// Represents a virtual location within an application.
class Route<T> {
final String method;
final String? method;
final String path;
final List<T> handlers;
final List<T>? handlers;
final Map<String, Map<String, dynamic>> _cache = {};
final RouteDefinition _routeDefinition;
String name;
Parser<RouteResult> _parser;
final RouteDefinition? _routeDefinition;
String? name;
Parser<RouteResult?>? _parser;
Route(this.path, {@required this.method, @required this.handlers})
Route(this.path, {required this.method, required this.handlers})
: _routeDefinition = RouteGrammar.routeDefinition
.parse(SpanScanner(path.replaceAll(_straySlashes, '')))
.parse(SpanScanner(path.replaceAll(_straySlashes, '')))!
.value {
if (_routeDefinition?.segments?.isNotEmpty != true) {
_parser = match('').map((r) => RouteResult({}));
@ -26,7 +26,7 @@ class Route<T> {
method: b.method, handlers: b.handlers);
}
Parser<RouteResult> get parser => _parser ??= _routeDefinition.compile();
Parser<RouteResult?>? get parser => _parser ??= _routeDefinition!.compile();
@override
String toString() {
@ -42,7 +42,7 @@ class Route<T> {
var b = StringBuffer();
int i = 0;
for (var seg in _routeDefinition.segments) {
for (var seg in _routeDefinition!.segments) {
if (i++ > 0) b.write('/');
if (seg is ConstantSegment) {
b.write(seg.text);
@ -50,7 +50,7 @@ class Route<T> {
if (!params.containsKey(seg.name)) {
throw ArgumentError('Missing parameter "${seg.name}".');
}
b.write(params[seg.name]);
b.write(params[seg.name!]);
}
}
@ -61,19 +61,19 @@ class Route<T> {
/// The result of matching an individual route.
class RouteResult {
/// The parsed route parameters.
final Map<String, dynamic> params;
final Map<String?, dynamic> params;
/// Optional. An explicit "tail" value to set.
String get tail => _tail;
String? get tail => _tail;
String _tail;
String? _tail;
RouteResult(this.params, {String tail}) : _tail = tail;
RouteResult(this.params, {String? tail}) : _tail = tail;
void _setTail(String v) => _tail ??= v;
void _setTail(String? v) => _tail ??= v;
/// Adds parameters.
void addAll(Map<String, dynamic> map) {
void addAll(Map<String?, dynamic> map) {
params.addAll(map);
}
}

View file

@ -65,8 +65,8 @@ class Router<T> {
/// Adds a route that responds to the given path
/// for requests with the given method (case-insensitive).
/// Provide '*' as the method to respond to all methods.
Route<T> addRoute(String method, String path, T handler,
{Iterable<T> middleware}) {
Route<T> addRoute(String? method, String path, T handler,
{Iterable<T>? middleware}) {
middleware ??= <T>[];
if (_useCache == true) {
throw StateError('Cannot add routes after caching is enabled.');
@ -115,7 +115,7 @@ class Router<T> {
/// 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),
{callback(String tree)?,
String header = 'Dumping route tree:',
String tab = ' '}) {
final buf = StringBuffer();
@ -147,7 +147,7 @@ class Router<T> {
buf.writeln();
dumpRouter(route.router);
} else {
buf.writeln(' => ${route.handlers.length} handler(s)');
buf.writeln(' => ${route.handlers!.length} handler(s)');
}
}
@ -165,7 +165,7 @@ class Router<T> {
/// Returns the created route.
/// You can also register middleware within the router.
SymlinkRoute<T> group(String path, void callback(Router<T> router),
{Iterable<T> middleware, String name}) {
{Iterable<T>? middleware, String? name}) {
middleware ??= <T>[];
final router = Router<T>().._middleware.addAll(middleware);
callback(router);
@ -175,7 +175,7 @@ class Router<T> {
/// Asynchronous equivalent of [group].
Future<SymlinkRoute<T>> groupAsync(
String path, FutureOr<void> callback(Router<T> router),
{Iterable<T> middleware, String name}) async {
{Iterable<T>? middleware, String? name}) async {
middleware ??= <T>[];
final router = Router<T>().._middleware.addAll(middleware);
await callback(router);
@ -212,7 +212,7 @@ class Router<T> {
String navigate(Iterable linkParams, {bool absolute = true}) {
final List<String> segments = [];
Router search = this;
Route lastRoute;
Route? lastRoute;
for (final param in linkParams) {
bool resolved = false;
@ -238,7 +238,7 @@ class Router<T> {
var scanner = SpanScanner(param.replaceAll(_straySlashes, ''));
for (Route route in search.routes) {
int pos = scanner.position;
if (route.parser.parse(scanner).successful && scanner.isDone) {
if (route.parser!.parse(scanner)!.successful && scanner.isDone) {
segments.add(route.path.replaceAll(_straySlashes, ''));
lastRoute = route;
@ -281,10 +281,10 @@ class Router<T> {
/// Finds the first [Route] that matches the given path,
/// with the given method.
bool resolve(String absolute, String relative, List<RoutingResult<T>> out,
bool resolve(String? absolute, String? relative, List<RoutingResult<T>> out,
{String method = 'GET', bool strip = true}) {
final cleanRelative =
strip == false ? relative : stripStraySlashes(relative);
strip == false ? relative! : stripStraySlashes(relative!);
var scanner = SpanScanner(cleanRelative);
bool crawl(Router<T> r) {
@ -294,22 +294,22 @@ class Router<T> {
int pos = scanner.position;
if (route is SymlinkRoute<T>) {
if (route.parser.parse(scanner).successful) {
if (route.parser!.parse(scanner)!.successful) {
var s = crawl(route.router);
if (s) success = true;
}
scanner.position = pos;
} else if (route.method == '*' || route.method == method) {
var parseResult = route.parser.parse(scanner);
var parseResult = route.parser!.parse(scanner)!;
if (parseResult.successful && scanner.isDone) {
var result = RoutingResult<T>(
parseResult: parseResult,
params: parseResult.value.params,
params: parseResult.value!.params,
shallowRoute: route,
shallowRouter: this,
tail: (parseResult.value.tail ?? '') + scanner.rest);
tail: (parseResult.value!.tail ?? '') + scanner.rest);
out.add(result);
success = true;
}
@ -326,13 +326,13 @@ class Router<T> {
/// Returns the result of [resolve] with [path] passed as
/// both `absolute` and `relative`.
Iterable<RoutingResult<T>> resolveAbsolute(String path,
Iterable<RoutingResult<T>> resolveAbsolute(String? path,
{String method = 'GET', bool strip = true}) =>
resolveAll(path, path, method: method, strip: strip);
/// Finds every possible [Route] that matches the given path,
/// with the given method.
Iterable<RoutingResult<T>> resolveAll(String absolute, String relative,
Iterable<RoutingResult<T>> resolveAll(String? absolute, String? relative,
{String method = 'GET', bool strip = true}) {
if (_useCache == true) {
return _cache.putIfAbsent('$method$absolute',
@ -342,7 +342,7 @@ class Router<T> {
return _resolveAll(absolute, relative, method: method, strip: strip);
}
Iterable<RoutingResult<T>> _resolveAll(String absolute, String relative,
Iterable<RoutingResult<T>> _resolveAll(String? absolute, String? relative,
{String method = 'GET', bool strip = true}) {
var results = <RoutingResult<T>>[];
resolve(absolute, relative, results, method: method, strip: strip);
@ -363,61 +363,61 @@ class Router<T> {
}
/// Adds a route that responds to any request matching the given path.
Route<T> all(String path, T handler, {Iterable<T> middleware}) {
Route<T> all(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('*', path, handler, middleware: middleware);
}
/// Adds a route that responds to a DELETE request.
Route<T> delete(String path, T handler, {Iterable<T> middleware}) {
Route<T> delete(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('DELETE', path, handler, middleware: middleware);
}
/// Adds a route that responds to a GET request.
Route<T> get(String path, T handler, {Iterable<T> middleware}) {
Route<T> get(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('GET', path, handler, middleware: middleware);
}
/// Adds a route that responds to a HEAD request.
Route<T> head(String path, T handler, {Iterable<T> middleware}) {
Route<T> head(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('HEAD', path, handler, middleware: middleware);
}
/// Adds a route that responds to a OPTIONS request.
Route<T> options(String path, T handler, {Iterable<T> middleware}) {
Route<T> options(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('OPTIONS', path, handler, middleware: middleware);
}
/// Adds a route that responds to a POST request.
Route<T> post(String path, T handler, {Iterable<T> middleware}) {
Route<T> post(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('POST', path, handler, middleware: middleware);
}
/// Adds a route that responds to a PATCH request.
Route<T> patch(String path, T handler, {Iterable<T> middleware}) {
Route<T> patch(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('PATCH', path, handler, middleware: middleware);
}
/// Adds a route that responds to a PUT request.
Route put(String path, T handler, {Iterable<T> middleware}) {
Route put(String path, T handler, {Iterable<T>? middleware}) {
return addRoute('PUT', path, handler, middleware: middleware);
}
}
class _ChainedRouter<T> extends Router<T> {
final List<T> _handlers = <T>[];
Router _root;
Router? _root;
_ChainedRouter.empty();
_ChainedRouter(Router root, Iterable<T> middleware) {
_ChainedRouter(Router? root, Iterable<T> middleware) {
this._root = root;
_handlers.addAll(middleware);
}
@override
Route<T> addRoute(String method, String path, handler,
{Iterable<T> middleware}) {
var route = super.addRoute(method, path, handler,
Route<T> addRoute(String? method, String path, handler,
{Iterable<T>? middleware}) {
Route<T> route = super.addRoute(method, path, handler,
middleware: []..addAll(_handlers)..addAll(middleware ?? []));
//_root._routes.add(route);
return route;
@ -425,7 +425,7 @@ class _ChainedRouter<T> extends Router<T> {
@override
SymlinkRoute<T> group(String path, void callback(Router<T> router),
{Iterable<T> middleware, String name}) {
{Iterable<T>? middleware, String? name}) {
final router = _ChainedRouter<T>(
_root, []..addAll(_handlers)..addAll(middleware ?? []));
callback(router);
@ -435,7 +435,7 @@ class _ChainedRouter<T> extends Router<T> {
@override
Future<SymlinkRoute<T>> groupAsync(
String path, FutureOr<void> callback(Router<T> router),
{Iterable<T> middleware, String name}) async {
{Iterable<T>? middleware, String? name}) async {
final router = _ChainedRouter<T>(
_root, []..addAll(_handlers)..addAll(middleware ?? []));
await callback(router);
@ -444,7 +444,7 @@ class _ChainedRouter<T> extends Router<T> {
@override
SymlinkRoute<T> mount(String path, Router<T> router) {
final route = super.mount(path, router);
final SymlinkRoute<T> route = super.mount(path, router);
route.router._middleware.insertAll(0, _handlers);
//_root._routes.add(route);
return route;
@ -473,13 +473,13 @@ Router<T> flatten<T>(Router<T> router) {
var path = route.path.replaceAll(_straySlashes, '');
var joined = '$base/$path'.replaceAll(_straySlashes, '');
flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''),
route.handlers.last,
route.handlers!.last,
middleware:
route.handlers.take(route.handlers.length - 1).toList());
route.handlers!.take(route.handlers!.length - 1).toList());
}
} else {
flattened.addRoute(route.method, route.path, route.handlers.last,
middleware: route.handlers.take(route.handlers.length - 1).toList());
flattened.addRoute(route.method, route.path, route.handlers!.last,
middleware: route.handlers!.take(route.handlers!.length - 1).toList());
}
}

View file

@ -3,49 +3,49 @@ part of angel_route.src.router;
/// Represents a complex result of navigating to a path.
class RoutingResult<T> {
/// The parse result that matched the given sub-path.
final ParseResult<RouteResult> parseResult;
final ParseResult<RouteResult?>? parseResult;
/// A nested instance, if a sub-path was matched.
final Iterable<RoutingResult<T>> nested;
final Iterable<RoutingResult<T>>? nested;
/// All route params matching this route on the current sub-path.
final Map<String, dynamic> params = {};
final Map<String?, dynamic> params = {};
/// The [Route] that answered this sub-path.
///
/// This is mostly for internal use, and useless in production.
final Route<T> shallowRoute;
final Route<T>? shallowRoute;
/// The [Router] that answered this sub-path.
///
/// Only really for internal use.
final Router<T> shallowRouter;
final Router<T>? shallowRouter;
/// The remainder of the full path that was not matched, and was passed to [nested] routes.
final String tail;
/// The [RoutingResult] that matched the most specific sub-path.
RoutingResult<T> get deepest {
var search = this;
RoutingResult<T> search = this;
while (search?.nested?.isNotEmpty == true) {
search = search.nested.first;
search = search.nested!.first;
}
return search;
}
/// The most specific route.
Route<T> get route => deepest.shallowRoute;
Route<T>? get route => deepest.shallowRoute;
/// The most specific router.
Router<T> get router => deepest.shallowRouter;
Router<T>? get router => deepest.shallowRouter;
/// The handlers at this sub-path.
List<T> get handlers {
return <T>[]
..addAll(shallowRouter.middleware)
..addAll(shallowRoute.handlers);
..addAll(shallowRouter!.middleware)
..addAll(shallowRoute!.handlers!);
}
/// All handlers on this sub-path and its children.
@ -56,7 +56,7 @@ class RoutingResult<T> {
handlers.addAll(result.handlers);
if (result.nested?.isNotEmpty == true) {
for (var r in result.nested) {
for (var r in result.nested!) {
crawl(r);
}
}
@ -68,14 +68,14 @@ class RoutingResult<T> {
}
/// All parameters on this sub-path and its children.
Map<String, dynamic> get allParams {
final Map<String, dynamic> params = {};
Map<String?, dynamic> get allParams {
final Map<String?, dynamic> params = {};
void crawl(RoutingResult result) {
params.addAll(result.params);
if (result.nested?.isNotEmpty == true) {
for (var r in result.nested) {
for (var r in result.nested!) {
crawl(r);
}
}
@ -87,11 +87,11 @@ class RoutingResult<T> {
RoutingResult(
{this.parseResult,
Map<String, dynamic> params = const {},
Map<String?, dynamic> params = const {},
this.nested,
this.shallowRoute,
this.shallowRouter,
@required this.tail}) {
required this.tail}) {
this.params.addAll(params ?? {});
}
}

View file

@ -1,14 +1,17 @@
name: angel_route
description: A powerful, isomorphic routing library for Dart. It is mainly used in the Angel framework, but can be used in Flutter and on the Web.
version: 4.0.0
version: 5.0.0
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_route
publish_to: none
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'
dependencies:
combinator: ^1.0.0
#meta: ^1.0.0
#path: ^1.0.0
combinator:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x_nnbd
path: packages/combinator
string_scanner: ^1.0.0
dev_dependencies:
build_runner: ^1.11.5

View file

@ -11,7 +11,7 @@ void main() {
..dumpTree();
test('nested route groups with chain', () {
var r = router.resolveAbsolute('/b/e/f')?.first?.route;
var r = router.resolveAbsolute('/b/e/f').first.route!;
expect(r, isNotNull);
expect(r.handlers, hasLength(4));
expect(r.handlers, equals(['a', 'c', 'd', 'g']));

View file

@ -7,9 +7,9 @@ void main() {
..get('/double/double:id', '')
..get('/num/num:id', '');
num getId(String path) {
num? getId(String path) {
var result = router.resolveAbsolute(path).first;
return result.allParams['id'] as num;
return result.allParams['id'] as num?;
}
test('parse', () {

View file

@ -9,11 +9,11 @@ const List<Map<String, String>> people = [
];
void main() {
http.Client client;
http.Client? client;
final router = Router();
HttpServer server;
String url;
late HttpServer server;
String? url;
router.get('/', (req, res) {
res.write('Root');
@ -87,12 +87,12 @@ void main() {
router.resolveAbsolute(req.uri.toString(), method: req.method);
final pipeline = MiddlewarePipeline(results);
if (pipeline.handlers.isEmpty) {
if (pipeline.handlers!.isEmpty) {
res
..statusCode = 404
..writeln('404 Not Found');
} else {
for (final handler in pipeline.handlers) {
for (final handler in pipeline.handlers!) {
if (!((await handler(req, res)) as bool)) break;
}
}
@ -103,7 +103,7 @@ void main() {
tearDown(() async {
await server.close(force: true);
client.close();
client!.close();
client = null;
url = null;
});
@ -111,13 +111,13 @@ void main() {
group('top-level', () {
group('get', () {
test('root', () async {
final res = await client.get(Uri.parse(url));
final res = await client!.get(Uri.parse(url!));
print('Response: ${res.body}');
expect(res.body, equals('Root'));
});
test('path', () async {
final res = await client.get(Uri.parse('$url/hello'));
final res = await client!.get(Uri.parse('$url/hello'));
print('Response: ${res.body}');
expect(res.body, equals('World'));
});
@ -127,20 +127,20 @@ void main() {
group('group', () {
group('top-level', () {
test('root', () async {
final res = await client.get(Uri.parse('$url/people'));
final res = await client!.get(Uri.parse('$url/people'));
print('Response: ${res.body}');
expect(json.decode(res.body), equals(people));
});
group('param', () {
test('root', () async {
final res = await client.get(Uri.parse('$url/people/0'));
final res = await client!.get(Uri.parse('$url/people/0'));
print('Response: ${res.body}');
expect(json.decode(res.body), equals(people.first));
});
test('path', () async {
final res = await client.get(Uri.parse('$url/people/0/name'));
final res = await client!.get(Uri.parse('$url/people/0/name'));
print('Response: ${res.body}');
expect(json.decode(res.body), equals(people.first['name']));
});
@ -151,20 +151,20 @@ void main() {
group('mount', () {
group('path', () {
test('top-level', () async {
final res = await client.post(Uri.parse('$url/beatles/spinal_clacker'));
final res = await client!.post(Uri.parse('$url/beatles/spinal_clacker'));
print('Response: ${res.body}');
expect(res.body, equals('come together'));
});
test('fallback', () async {
final res = await client.patch(Uri.parse('$url/beatles/muddy_water'));
final res = await client!.patch(Uri.parse('$url/beatles/muddy_water'));
print('Response: ${res.body}');
expect(res.body, equals('together'));
});
test('fallback', () async {
final res =
await client.patch(Uri.parse('$url/beatles/spanil_clakcer'));
await client!.patch(Uri.parse('$url/beatles/spanil_clakcer'));
print('Response: ${res.body}');
expect(res.body, equals('together'));
});
@ -172,7 +172,7 @@ void main() {
test('deep nested', () async {
final res =
await client.get(Uri.parse('$url/beatles/big/yellow/submarine'));
await client!.get(Uri.parse('$url/beatles/big/yellow/submarine'));
print('Response: ${res.body}');
expect(res.body, equals('we all live in a'));
});
@ -187,17 +187,17 @@ void main() {
});
test('path', () async {
await expect404(client.get(Uri.parse('$url/foo')));
await expect404(client.get(Uri.parse('$url/bye')));
await expect404(client.get(Uri.parse('$url/people/0/age')));
await expect404(client.get(Uri.parse('$url/beatles2')));
await expect404(client!.get(Uri.parse('$url/foo')));
await expect404(client!.get(Uri.parse('$url/bye')));
await expect404(client!.get(Uri.parse('$url/people/0/age')));
await expect404(client!.get(Uri.parse('$url/beatles2')));
});
test('method', () async {
await expect404(client.head(Uri.parse(url)));
await expect404(client.patch(Uri.parse('$url/people')));
await expect404(client.post(Uri.parse('$url/people/0')));
await expect404(client.delete(Uri.parse('$url/beatles2/spinal_clacker')));
await expect404(client!.head(Uri.parse(url!)));
await expect404(client!.patch(Uri.parse('$url/people')));
await expect404(client!.post(Uri.parse('$url/people/0')));
await expect404(client!.delete(Uri.parse('$url/beatles2/spinal_clacker')));
});
});
}

View file

@ -26,21 +26,21 @@ void main() {
test('tail explicitly set intermediate', () {
var results = router.resolveAbsolute('/songs/in_the/key');
var result = results.first;
print(results.map((r) => {r.route.path: r.tail}));
print(results.map((r) => {r.route!.path: r.tail}));
expect(result.tail, 'in_the');
});
test('tail explicitly set at end', () {
var results = router.resolveAbsolute('/isnt/she/epic');
var result = results.first;
print(results.map((r) => {r.route.path: r.tail}));
print(results.map((r) => {r.route!.path: r.tail}));
expect(result.tail, 'epic');
});
test('tail with trailing', () {
var results = router.resolveAbsolute('/isnt/she/epic/fail');
var result = results.first;
print(results.map((r) => {r.route.path: r.tail}));
print(results.map((r) => {r.route!.path: r.tail}));
expect(result.tail, 'epic/fail');
});
}

View file

@ -9,15 +9,15 @@ basic(BrowserRouter router) {
final route = result?.route;
if (route == null) {
$h1.text = 'No Active Route';
$ul.children
$h1!.text = 'No Active Route';
$ul!.children
..clear()
..add(LIElement()..text = '(empty)');
} else {
$h1.text = 'Active Route: ${route.name ?? route.path}';
$ul.children
$h1!.text = 'Active Route: ${route.name ?? route.path}';
$ul!.children
..clear()
..addAll(result.allHandlers
..addAll(result!.allHandlers
.map((handler) => LIElement()..text = handler.toString()));
}
});