Fixed migration bugs
This commit is contained in:
parent
01171d8954
commit
ddbf7f33b6
22 changed files with 477 additions and 412 deletions
|
@ -4,6 +4,7 @@ import 'dart:io' show stderr, Cookie;
|
||||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||||
import 'package:angel_route/angel_route.dart';
|
import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:combinator/combinator.dart';
|
import 'package:combinator/combinator.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:stack_trace/stack_trace.dart';
|
import 'package:stack_trace/stack_trace.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'core.dart';
|
import 'core.dart';
|
||||||
|
@ -17,11 +18,12 @@ abstract class Driver<
|
||||||
Server extends Stream<Request>,
|
Server extends Stream<Request>,
|
||||||
RequestContextType extends RequestContext,
|
RequestContextType extends RequestContext,
|
||||||
ResponseContextType extends ResponseContext> {
|
ResponseContextType extends ResponseContext> {
|
||||||
final Angel? app;
|
final Angel app;
|
||||||
final bool useZone;
|
final bool useZone;
|
||||||
bool _closed = false;
|
bool _closed = false;
|
||||||
Server? _server;
|
late Server _server;
|
||||||
StreamSubscription<Request>? _sub;
|
StreamSubscription<Request>? _sub;
|
||||||
|
final log = Logger('Driver');
|
||||||
|
|
||||||
/// The function used to bind this instance to a server..
|
/// The function used to bind this instance to a server..
|
||||||
final Future<Server> Function(dynamic, int) serverGenerator;
|
final Future<Server> Function(dynamic, int) serverGenerator;
|
||||||
|
@ -32,39 +34,44 @@ abstract class Driver<
|
||||||
Uri get uri;
|
Uri get uri;
|
||||||
|
|
||||||
/// The native server running this instance.
|
/// The native server running this instance.
|
||||||
Server? get server => _server;
|
Server get server => _server;
|
||||||
|
|
||||||
Future<Server> generateServer(address, int port) =>
|
Future<Server> generateServer(address, int port) =>
|
||||||
serverGenerator(address, port);
|
serverGenerator(address, port);
|
||||||
|
|
||||||
/// Starts, and returns the server.
|
/// Starts, and returns the server.
|
||||||
Future<Server> startServer([address, int? port]) {
|
Future<Server> startServer([address, int port = 5000]) {
|
||||||
var host = address ?? '127.0.0.1';
|
var host = address ?? '127.0.0.1';
|
||||||
return generateServer(host, port ?? 0).then((server) {
|
return generateServer(host, port).then((server) {
|
||||||
_server = server;
|
_server = server;
|
||||||
return Future.wait(app!.startupHooks.map(app!.configure)).then((_) {
|
return Future.wait(app.startupHooks.map(app.configure)).then((_) {
|
||||||
app!.optimizeForProduction();
|
app.optimizeForProduction();
|
||||||
_sub = server.listen((request) {
|
_sub = server.listen((request) {
|
||||||
var stream = createResponseStreamFromRawRequest(request);
|
var stream = createResponseStreamFromRawRequest(request);
|
||||||
stream.listen((response) {
|
stream.listen((response) {
|
||||||
// TODO: To be revisited
|
// TODO: To be revisited
|
||||||
handleRawRequest(request, response);
|
handleRawRequest(request, response);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return _server!;
|
return Future.value(_server);
|
||||||
});
|
});
|
||||||
|
}).catchError((error) {
|
||||||
|
log.severe("Failed to create server", error);
|
||||||
|
throw ArgumentError("[Driver]Failed to create server");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shuts down the underlying server.
|
/// Shuts down the underlying server.
|
||||||
Future<Server> close() {
|
Future<Server> close() {
|
||||||
if (_closed) return Future.value(_server);
|
if (_closed) {
|
||||||
|
return Future.value(_server);
|
||||||
|
}
|
||||||
_closed = true;
|
_closed = true;
|
||||||
|
|
||||||
_sub?.cancel();
|
_sub?.cancel();
|
||||||
return app!.close().then((_) =>
|
return app.close().then((_) =>
|
||||||
Future.wait(app!.shutdownHooks.map(app!.configure))
|
Future.wait(app.shutdownHooks.map(app.configure))
|
||||||
.then((_) => _server!));
|
.then((_) => Future.value(_server)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RequestContextType> createRequestContext(
|
Future<RequestContextType> createRequestContext(
|
||||||
|
@ -102,7 +109,7 @@ abstract class Driver<
|
||||||
|
|
||||||
Tuple4<List?, Map<String?, dynamic>, ParseResult<RouteResult?>?,
|
Tuple4<List?, Map<String?, dynamic>, ParseResult<RouteResult?>?,
|
||||||
MiddlewarePipeline> resolveTuple() {
|
MiddlewarePipeline> resolveTuple() {
|
||||||
var r = app!.optimizedRouter;
|
var r = app.optimizedRouter;
|
||||||
var resolved =
|
var resolved =
|
||||||
r.resolveAbsolute(path, method: req.method!, strip: false);
|
r.resolveAbsolute(path, method: req.method!, strip: false);
|
||||||
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
|
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
|
||||||
|
@ -116,8 +123,8 @@ abstract class Driver<
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = req.method! + path!;
|
var cacheKey = req.method! + path!;
|
||||||
var tuple = app!.environment.isProduction
|
var tuple = app.environment.isProduction
|
||||||
? app!.handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
? app.handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
||||||
: resolveTuple();
|
: resolveTuple();
|
||||||
var line = tuple.item4 as MiddlewarePipeline<RequestHandler?>;
|
var line = tuple.item4 as MiddlewarePipeline<RequestHandler?>;
|
||||||
var it = MiddlewarePipelineIterator<RequestHandler?>(line);
|
var it = MiddlewarePipelineIterator<RequestHandler?>(line);
|
||||||
|
@ -134,7 +141,7 @@ abstract class Driver<
|
||||||
..registerSingleton<ParseResult<RouteResult?>?>(tuple.item3)
|
..registerSingleton<ParseResult<RouteResult?>?>(tuple.item3)
|
||||||
..registerSingleton<ParseResult?>(tuple.item3);
|
..registerSingleton<ParseResult?>(tuple.item3);
|
||||||
|
|
||||||
if (!app!.environment.isProduction && app!.logger != null) {
|
if (app.environment.isProduction && app.logger != null) {
|
||||||
req.container!.registerSingleton<Stopwatch>(Stopwatch()..start());
|
req.container!.registerSingleton<Stopwatch>(Stopwatch()..start());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,14 +167,14 @@ abstract class Driver<
|
||||||
stackTrace: st,
|
stackTrace: st,
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
message: e?.toString() ?? '500 Internal Server Error');
|
message: e?.toString() ?? '500 Internal Server Error');
|
||||||
}, test: (e) => e is! AngelHttpException).catchError(
|
}, test: (e) => e is AngelHttpException).catchError(
|
||||||
(ee, StackTrace st) {
|
(ee, StackTrace st) {
|
||||||
var e = ee as AngelHttpException;
|
var e = ee as AngelHttpException;
|
||||||
|
|
||||||
if (app!.logger != null) {
|
if (app.logger != null) {
|
||||||
var error = e.error ?? e;
|
var error = e.error ?? e;
|
||||||
var trace = Trace.from(e.stackTrace ?? StackTrace.current).terse;
|
var trace = Trace.from(e.stackTrace ?? StackTrace.current).terse;
|
||||||
app!.logger!.severe(e.message ?? e.toString(), error, trace);
|
app.logger?.severe(e.message ?? e.toString(), error, trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAngelHttpException(
|
return handleAngelHttpException(
|
||||||
|
@ -176,8 +183,8 @@ abstract class Driver<
|
||||||
} else {
|
} else {
|
||||||
var zoneSpec = ZoneSpecification(
|
var zoneSpec = ZoneSpecification(
|
||||||
print: (self, parent, zone, line) {
|
print: (self, parent, zone, line) {
|
||||||
if (app!.logger != null) {
|
if (app.logger != null) {
|
||||||
app!.logger!.info(line);
|
app.logger?.info(line);
|
||||||
} else {
|
} else {
|
||||||
parent.print(zone, line);
|
parent.print(zone, line);
|
||||||
}
|
}
|
||||||
|
@ -198,8 +205,8 @@ abstract class Driver<
|
||||||
stackTrace: stackTrace, message: error.toString());
|
stackTrace: stackTrace, message: error.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app!.logger != null) {
|
if (app.logger != null) {
|
||||||
app!.logger!.severe(e.message ?? e.toString(), error, trace);
|
app.logger?.severe(e.message ?? e.toString(), error, trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAngelHttpException(
|
return handleAngelHttpException(
|
||||||
|
@ -209,8 +216,8 @@ abstract class Driver<
|
||||||
closeResponse(response);
|
closeResponse(response);
|
||||||
// Ideally, we won't be in a position where an absolutely fatal error occurs,
|
// Ideally, we won't be in a position where an absolutely fatal error occurs,
|
||||||
// but if so, we'll need to log it.
|
// but if so, we'll need to log it.
|
||||||
if (app!.logger != null) {
|
if (app.logger != null) {
|
||||||
app!.logger!.severe(
|
app.logger?.severe(
|
||||||
'Fatal error occurred when processing $uri.', e, trace);
|
'Fatal error occurred when processing $uri.', e, trace);
|
||||||
} else {
|
} else {
|
||||||
stderr
|
stderr
|
||||||
|
@ -220,7 +227,6 @@ abstract class Driver<
|
||||||
..writeln(trace);
|
..writeln(trace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -243,7 +249,7 @@ abstract class Driver<
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles an [AngelHttpException].
|
/// Handles an [AngelHttpException].
|
||||||
Future? handleAngelHttpException(
|
Future handleAngelHttpException(
|
||||||
AngelHttpException e,
|
AngelHttpException e,
|
||||||
StackTrace st,
|
StackTrace st,
|
||||||
RequestContext? req,
|
RequestContext? req,
|
||||||
|
@ -253,12 +259,12 @@ abstract class Driver<
|
||||||
{bool ignoreFinalizers = false}) {
|
{bool ignoreFinalizers = false}) {
|
||||||
if (req == null || res == null) {
|
if (req == null || res == null) {
|
||||||
try {
|
try {
|
||||||
app!.logger?.severe(null, e, st);
|
app.logger?.severe(null, e, st);
|
||||||
setStatusCode(response, 500);
|
setStatusCode(response, 500);
|
||||||
writeStringToResponse(response, '500 Internal Server Error');
|
writeStringToResponse(response, '500 Internal Server Error');
|
||||||
closeResponse(response);
|
closeResponse(response);
|
||||||
} finally {
|
} finally {
|
||||||
return null;
|
return Future.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,8 +275,8 @@ abstract class Driver<
|
||||||
} else {
|
} else {
|
||||||
res.statusCode = e.statusCode;
|
res.statusCode = e.statusCode;
|
||||||
handleError =
|
handleError =
|
||||||
Future.sync(() => app!.errorHandler(e, req, res)).then((result) {
|
Future.sync(() => app.errorHandler(e, req, res)).then((result) {
|
||||||
return app!.executeHandler(result, req, res).then((_) => res.close());
|
return app.executeHandler(result, req, res).then((_) => res.close());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,11 +289,11 @@ abstract class Driver<
|
||||||
ResponseContext res,
|
ResponseContext res,
|
||||||
{bool ignoreFinalizers = false}) {
|
{bool ignoreFinalizers = false}) {
|
||||||
Future<void> _cleanup(_) {
|
Future<void> _cleanup(_) {
|
||||||
if (!app!.environment.isProduction &&
|
if (app.environment.isProduction &&
|
||||||
app!.logger != null &&
|
app.logger != null &&
|
||||||
req.container!.has<Stopwatch>()) {
|
req.container!.has<Stopwatch>()) {
|
||||||
var sw = req.container!.make<Stopwatch>();
|
var sw = req.container!.make<Stopwatch>();
|
||||||
app!.logger!.info(
|
app.logger?.info(
|
||||||
"${res.statusCode} ${req.method} ${req.uri} (${sw?.elapsedMilliseconds ?? 'unknown'} ms)");
|
"${res.statusCode} ${req.method} ${req.uri} (${sw?.elapsedMilliseconds ?? 'unknown'} ms)");
|
||||||
}
|
}
|
||||||
return req.close();
|
return req.close();
|
||||||
|
@ -297,7 +303,7 @@ abstract class Driver<
|
||||||
|
|
||||||
Future finalizers = ignoreFinalizers == true
|
Future finalizers = ignoreFinalizers == true
|
||||||
? Future.value()
|
? Future.value()
|
||||||
: Future.forEach(app!.responseFinalizers, (dynamic f) => f(req, res));
|
: Future.forEach(app.responseFinalizers, (dynamic f) => f(req, res));
|
||||||
|
|
||||||
return finalizers.then((_) {
|
return finalizers.then((_) {
|
||||||
//if (res.isOpen) res.close();
|
//if (res.isOpen) res.close();
|
||||||
|
@ -358,13 +364,13 @@ abstract class Driver<
|
||||||
MiddlewarePipelineIterator<RequestHandler?> it,
|
MiddlewarePipelineIterator<RequestHandler?> it,
|
||||||
RequestContextType req,
|
RequestContextType req,
|
||||||
ResponseContextType res,
|
ResponseContextType res,
|
||||||
Angel? app) async {
|
Angel app) async {
|
||||||
var broken = false;
|
var broken = false;
|
||||||
while (it.moveNext()) {
|
while (it.moveNext()) {
|
||||||
var current = it.current.handlers.iterator;
|
var current = it.current.handlers.iterator;
|
||||||
|
|
||||||
while (!broken && current.moveNext()) {
|
while (!broken && current.moveNext()) {
|
||||||
var result = await app!.executeHandler(current.current, req, res);
|
var result = await app.executeHandler(current.current, req, res);
|
||||||
if (result != true) {
|
if (result != true) {
|
||||||
broken = true;
|
broken = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -30,7 +30,7 @@ RequestHandler chain(Iterable<RequestHandler> handlers) {
|
||||||
runPipeline = () => Future.sync(() => handler(req, res));
|
runPipeline = () => Future.sync(() => handler(req, res));
|
||||||
} else {
|
} else {
|
||||||
var current = runPipeline;
|
var current = runPipeline;
|
||||||
runPipeline = () => current().then((result) => !res.isOpen
|
runPipeline = () => current().then((result) => res.isOpen
|
||||||
? Future.value(result)
|
? Future.value(result)
|
||||||
: req.app.executeHandler(handler, req, res));
|
: req.app.executeHandler(handler, req, res));
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ RequestHandler chain(Iterable<RequestHandler> handlers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A routable server that can handle dynamic requests.
|
/// A routable server that can handle dynamic requests.
|
||||||
class Routable extends Router<RequestHandler?> {
|
class Routable extends Router<RequestHandler> {
|
||||||
final Map<Pattern, Service> _services = {};
|
final Map<Pattern, Service> _services = {};
|
||||||
final Map<Pattern, Service?> _serviceLookups = {};
|
final Map<Pattern, Service?> _serviceLookups = {};
|
||||||
final Map configuration = {};
|
final Map configuration = {};
|
||||||
|
@ -92,10 +92,9 @@ class Routable extends Router<RequestHandler?> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Route<RequestHandler?> addRoute(
|
Route<RequestHandler> addRoute(
|
||||||
String? method, String? path, RequestHandler? handler,
|
String method, String path, RequestHandler handler,
|
||||||
{Iterable<RequestHandler?>? middleware}) {
|
{Iterable<RequestHandler> middleware = const Iterable.empty()}) {
|
||||||
middleware ??= [];
|
|
||||||
final handlers = <RequestHandler>[];
|
final handlers = <RequestHandler>[];
|
||||||
// Merge @Middleware declaration, if any
|
// Merge @Middleware declaration, if any
|
||||||
var reflector = _container?.reflector;
|
var reflector = _container?.reflector;
|
||||||
|
@ -108,10 +107,7 @@ class Routable extends Router<RequestHandler?> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final handlerSequence = <RequestHandler>[];
|
final handlerSequence = <RequestHandler>[];
|
||||||
handlerSequence.addAll(middleware as Iterable<
|
handlerSequence.addAll(middleware);
|
||||||
FutureOr<dynamic>? Function(
|
|
||||||
RequestContext<dynamic>, ResponseContext<dynamic>)>? ??
|
|
||||||
[]);
|
|
||||||
handlerSequence.addAll(handlers);
|
handlerSequence.addAll(handlers);
|
||||||
|
|
||||||
return super.addRoute(method, path.toString(), handler,
|
return super.addRoute(method, path.toString(), handler,
|
||||||
|
|
|
@ -19,15 +19,19 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
|
||||||
class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
HttpRequestContext, HttpResponseContext> {
|
HttpRequestContext, HttpResponseContext> {
|
||||||
@override
|
@override
|
||||||
Uri get uri => server == null
|
Uri get uri {
|
||||||
? Uri()
|
if (server == null) {
|
||||||
: Uri(scheme: 'http', host: server!.address.address, port: server!.port);
|
throw ArgumentError("[AngelHttp] Server instance not intialised");
|
||||||
|
}
|
||||||
|
return Uri(
|
||||||
|
scheme: 'http', host: server!.address.address, port: server!.port);
|
||||||
|
}
|
||||||
|
|
||||||
AngelHttp._(Angel? app,
|
AngelHttp._(Angel app,
|
||||||
Future<HttpServer> Function(dynamic, int) serverGenerator, bool useZone)
|
Future<HttpServer> Function(dynamic, int) serverGenerator, bool useZone)
|
||||||
: super(app, serverGenerator, useZone: useZone);
|
: super(app, serverGenerator, useZone: useZone);
|
||||||
|
|
||||||
factory AngelHttp(Angel? app, {bool useZone = true}) {
|
factory AngelHttp(Angel app, {bool useZone = true}) {
|
||||||
return AngelHttp._(app, HttpServer.bind, useZone);
|
return AngelHttp._(app, HttpServer.bind, useZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +63,18 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
var serverContext = SecurityContext();
|
var serverContext = SecurityContext();
|
||||||
serverContext.useCertificateChain(certificateChain, password: password);
|
serverContext.useCertificateChain(certificateChain, password: password);
|
||||||
serverContext.usePrivateKey(serverKey, password: password);
|
serverContext.usePrivateKey(serverKey, password: password);
|
||||||
|
|
||||||
return AngelHttp.fromSecurityContext(app, serverContext, useZone: useZone);
|
return AngelHttp.fromSecurityContext(app, serverContext, useZone: useZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use [server] instead.
|
/// Use [server] instead.
|
||||||
@deprecated
|
@deprecated
|
||||||
HttpServer? get httpServer => server;
|
HttpServer get httpServer {
|
||||||
|
if (server == null) {
|
||||||
|
throw ArgumentError("[AngelHttp] Server instance not initialised");
|
||||||
|
}
|
||||||
|
return server!;
|
||||||
|
}
|
||||||
|
|
||||||
Future handleRequest(HttpRequest request) =>
|
Future handleRequest(HttpRequest request) =>
|
||||||
handleRawRequest(request, request.response);
|
handleRawRequest(request, request.response);
|
||||||
|
@ -75,7 +85,6 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HttpServer> close() async {
|
Future<HttpServer> close() async {
|
||||||
await server?.close();
|
|
||||||
return await super.close();
|
return await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +96,7 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
HttpRequest request, HttpResponse response) {
|
HttpRequest request, HttpResponse response) {
|
||||||
var path = request.uri.path.replaceAll(_straySlashes, '');
|
var path = request.uri.path.replaceAll(_straySlashes, '');
|
||||||
if (path.isEmpty) path = '/';
|
if (path.isEmpty) path = '/';
|
||||||
return HttpRequestContext.from(request, app!, path);
|
return HttpRequestContext.from(request, app, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -98,10 +107,10 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
||||||
HttpResponseContext context =
|
HttpResponseContext context =
|
||||||
HttpResponseContext(response, app, correspondingRequest);
|
HttpResponseContext(response, app, correspondingRequest);
|
||||||
|
|
||||||
if (app!.serializer == null) {
|
if (app.serializer == null) {
|
||||||
context.serializer = json.encode;
|
context.serializer = json.encode;
|
||||||
} else {
|
} else {
|
||||||
context.serializer = app!.serializer;
|
context.serializer = app.serializer;
|
||||||
}
|
}
|
||||||
return Future<HttpResponseContext>.value(context);
|
return Future<HttpResponseContext>.value(context);
|
||||||
// return Future<HttpResponseContext>.value(
|
// return Future<HttpResponseContext>.value(
|
||||||
|
|
|
@ -70,7 +70,7 @@ bool bar(RequestContext req, ResponseContext res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel? app;
|
late Angel app;
|
||||||
late TodoController todoController;
|
late TodoController todoController;
|
||||||
late NoExposeController noExposeCtrl;
|
late NoExposeController noExposeCtrl;
|
||||||
late HttpServer server;
|
late HttpServer server;
|
||||||
|
@ -79,30 +79,30 @@ main() {
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector());
|
app = Angel(reflector: MirrorsReflector());
|
||||||
app!.get(
|
app.get(
|
||||||
"/redirect",
|
"/redirect",
|
||||||
(req, res) async =>
|
(req, res) async =>
|
||||||
res.redirectToAction("TodoController@foo", {"foo": "world"}));
|
res.redirectToAction("TodoController@foo", {"foo": "world"}));
|
||||||
|
|
||||||
// Register as a singleton, just for the purpose of this test
|
// Register as a singleton, just for the purpose of this test
|
||||||
if (!app!.container!.has<TodoController>()) {
|
if (!app.container!.has<TodoController>()) {
|
||||||
app!.container!.registerSingleton(todoController = TodoController());
|
app.container!.registerSingleton(todoController = TodoController());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using mountController<T>();
|
// Using mountController<T>();
|
||||||
await app!.mountController<TodoController>();
|
await app.mountController<TodoController>();
|
||||||
|
|
||||||
noExposeCtrl = await app!.mountController<NoExposeController>();
|
noExposeCtrl = await app.mountController<NoExposeController>();
|
||||||
|
|
||||||
// Place controller in group. The applyRoutes() call, however, is async.
|
// Place controller in group. The applyRoutes() call, however, is async.
|
||||||
// Until https://github.com/angel-dart/route/issues/28 is closed,
|
// Until https://github.com/angel-dart/route/issues/28 is closed,
|
||||||
// this will need to be done by manually mounting the router.
|
// this will need to be done by manually mounting the router.
|
||||||
var subRouter = Router<RequestHandler>();
|
var subRouter = Router<RequestHandler>();
|
||||||
await todoController.applyRoutes(subRouter, app!.container!.reflector);
|
await todoController.applyRoutes(subRouter, app.container!.reflector);
|
||||||
app!.mount('/ctrl_group', subRouter);
|
app.mount('/ctrl_group', subRouter);
|
||||||
|
|
||||||
print(app!.controllers);
|
print(app.controllers);
|
||||||
app!.dumpTree();
|
app.dumpTree();
|
||||||
|
|
||||||
server = await AngelHttp(app).startServer();
|
server = await AngelHttp(app).startServer();
|
||||||
url = 'http://${server.address.address}:${server.port}';
|
url = 'http://${server.address.address}:${server.port}';
|
||||||
|
@ -110,7 +110,6 @@ main() {
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await server.close(force: true);
|
await server.close(force: true);
|
||||||
app = null;
|
|
||||||
url = null;
|
url = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ final String TEXT = "make your bed";
|
||||||
final String OVER = "never";
|
final String OVER = "never";
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel? app;
|
late Angel app;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
late HttpServer server;
|
late HttpServer server;
|
||||||
String? url;
|
String? url;
|
||||||
|
|
||||||
|
@ -25,31 +25,29 @@ main() {
|
||||||
client = http.Client();
|
client = http.Client();
|
||||||
|
|
||||||
// Inject some todos
|
// Inject some todos
|
||||||
app!.container!.registerSingleton(Todo(text: TEXT, over: OVER));
|
app.container!.registerSingleton(Todo(text: TEXT, over: OVER));
|
||||||
app!.container!.registerFactory<Future<Foo>>((container) async {
|
app.container!.registerFactory<Future<Foo>>((container) async {
|
||||||
var req = container.make<RequestContext>()!;
|
var req = container.make<RequestContext>()!;
|
||||||
var text = await utf8.decoder.bind(req.body!).join();
|
var text = await utf8.decoder.bind(req.body!).join();
|
||||||
return Foo(text);
|
return Foo(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
app!.get("/errands", ioc((Todo singleton) => singleton));
|
app.get("/errands", ioc((Todo singleton) => singleton));
|
||||||
app!.get(
|
app.get(
|
||||||
"/errands3",
|
"/errands3",
|
||||||
ioc(({required Errand singleton, Todo? foo, RequestContext? req}) =>
|
ioc(({required Errand singleton, Todo? foo, RequestContext? req}) =>
|
||||||
singleton.text));
|
singleton.text));
|
||||||
app!.post('/async', ioc((Foo foo) => {'baz': foo.bar}));
|
app.post('/async', ioc((Foo foo) => {'baz': foo.bar}));
|
||||||
await app!.configure(SingletonController().configureServer);
|
await app.configure(SingletonController().configureServer);
|
||||||
await app!.configure(ErrandController().configureServer);
|
await app.configure(ErrandController().configureServer);
|
||||||
|
|
||||||
server = await AngelHttp(app).startServer();
|
server = await AngelHttp(app).startServer();
|
||||||
url = "http://${server.address.host}:${server.port}";
|
url = "http://${server.address.host}:${server.port}";
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
app = null;
|
|
||||||
url = null;
|
url = null;
|
||||||
client!.close();
|
client.close();
|
||||||
client = null;
|
|
||||||
await server.close(force: true);
|
await server.close(force: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,33 +69,33 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("singleton in route", () async {
|
test("singleton in route", () async {
|
||||||
validateTodoSingleton(await client!.get(Uri.parse("$url/errands")));
|
validateTodoSingleton(await client.get(Uri.parse("$url/errands")));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("singleton in controller", () async {
|
test("singleton in controller", () async {
|
||||||
validateTodoSingleton(await client!.get(Uri.parse("$url/errands2")));
|
validateTodoSingleton(await client.get(Uri.parse("$url/errands2")));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("make in route", () async {
|
test("make in route", () async {
|
||||||
var response = await client!.get(Uri.parse("$url/errands3"));
|
var response = await client.get(Uri.parse("$url/errands3"));
|
||||||
var text = await json.decode(response.body) as String?;
|
var text = await json.decode(response.body) as String?;
|
||||||
expect(text, equals(TEXT));
|
expect(text, equals(TEXT));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("make in controller", () async {
|
test("make in controller", () async {
|
||||||
var response = await client!.get(Uri.parse("$url/errands4"));
|
var response = await client.get(Uri.parse("$url/errands4"));
|
||||||
var text = await json.decode(response.body) as String?;
|
var text = await json.decode(response.body) as String?;
|
||||||
expect(text, equals(TEXT));
|
expect(text, equals(TEXT));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolve from future in controller', () async {
|
test('resolve from future in controller', () async {
|
||||||
var response =
|
var response =
|
||||||
await client!.post(Uri.parse('$url/errands4/async'), body: 'hey');
|
await client.post(Uri.parse('$url/errands4/async'), body: 'hey');
|
||||||
expect(response.body, json.encode({'bar': 'hey'}));
|
expect(response.body, json.encode({'bar': 'hey'}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolve from future in route', () async {
|
test('resolve from future in route', () async {
|
||||||
var response = await client!.post(Uri.parse('$url/async'), body: 'yes');
|
var response = await client.post(Uri.parse('$url/async'), body: 'yes');
|
||||||
expect(response.body, json.encode({'baz': 'yes'}));
|
expect(response.body, json.encode({'baz': 'yes'}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel? app;
|
late Angel app;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
late HttpServer server;
|
late HttpServer server;
|
||||||
String? url;
|
late String url;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector())
|
app = Angel(reflector: MirrorsReflector())
|
||||||
|
@ -23,15 +23,12 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
app = null;
|
client.close();
|
||||||
url = null;
|
|
||||||
client!.close();
|
|
||||||
client = null;
|
|
||||||
await server.close(force: true);
|
await server.close(force: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("allow override of method", () async {
|
test("allow override of method", () async {
|
||||||
var response = await client!.get(Uri.parse('$url/foo'),
|
var response = await client.get(Uri.parse('$url/foo'),
|
||||||
headers: {'X-HTTP-Method-Override': 'POST'});
|
headers: {'X-HTTP-Method-Override': 'POST'});
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
expect(json.decode(response.body), equals({'hello': 'world'}));
|
expect(json.decode(response.body), equals({'hello': 'world'}));
|
||||||
|
|
|
@ -13,25 +13,26 @@ main() {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
};
|
};
|
||||||
|
|
||||||
Angel? app;
|
late Angel app;
|
||||||
late HttpServer server;
|
late HttpServer server;
|
||||||
String? url;
|
late String url;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
HookedService? todoService;
|
late HookedService todoService;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector());
|
app = Angel(reflector: MirrorsReflector());
|
||||||
client = http.Client();
|
client = http.Client();
|
||||||
app!.use('/todos', MapService());
|
app.use('/todos', MapService());
|
||||||
app!.use('/books', BookService());
|
app.use('/books', BookService());
|
||||||
|
|
||||||
todoService = app!.findHookedService<MapService>('todos');
|
todoService = app.findHookedService<MapService>('todos')
|
||||||
|
as HookedService<dynamic, dynamic, Service>;
|
||||||
|
|
||||||
todoService!.beforeAllStream().listen((e) {
|
todoService.beforeAllStream().listen((e) {
|
||||||
print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}');
|
print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}');
|
||||||
});
|
});
|
||||||
|
|
||||||
app!.errorHandler = (e, req, res) {
|
app.errorHandler = (e, req, res) {
|
||||||
throw e.error as Object;
|
throw e.error as Object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,31 +42,27 @@ main() {
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await server.close(force: true);
|
await server.close(force: true);
|
||||||
app = null;
|
client.close();
|
||||||
url = null;
|
|
||||||
client!.close();
|
|
||||||
client = null;
|
|
||||||
todoService = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listen before and after", () async {
|
test("listen before and after", () async {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
todoService
|
todoService
|
||||||
?..beforeIndexed.listen((_) {
|
..beforeIndexed.listen((_) {
|
||||||
count++;
|
count++;
|
||||||
})
|
})
|
||||||
..afterIndexed.listen((_) {
|
..afterIndexed.listen((_) {
|
||||||
count++;
|
count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await client!.get(Uri.parse("$url/todos"));
|
var response = await client.get(Uri.parse("$url/todos"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(count, equals(2));
|
expect(count, equals(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("cancel before", () async {
|
test("cancel before", () async {
|
||||||
todoService!.beforeCreated
|
todoService.beforeCreated
|
||||||
..listen((HookedServiceEvent event) {
|
..listen((HookedServiceEvent event) {
|
||||||
event.cancel({"hello": "hooked world"});
|
event.cancel({"hello": "hooked world"});
|
||||||
})
|
})
|
||||||
|
@ -73,7 +70,7 @@ main() {
|
||||||
event.cancel({"this_hook": "should never run"});
|
event.cancel({"this_hook": "should never run"});
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await client!.post(Uri.parse("$url/todos"),
|
var response = await client.post(Uri.parse("$url/todos"),
|
||||||
body: json.encode({"arbitrary": "data"}),
|
body: json.encode({"arbitrary": "data"}),
|
||||||
headers: headers as Map<String, String>);
|
headers: headers as Map<String, String>);
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
@ -82,7 +79,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("cancel after", () async {
|
test("cancel after", () async {
|
||||||
todoService!.afterIndexed
|
todoService.afterIndexed
|
||||||
..listen((HookedServiceEvent event) async {
|
..listen((HookedServiceEvent event) async {
|
||||||
// Hooks can be Futures ;)
|
// Hooks can be Futures ;)
|
||||||
event.cancel([
|
event.cancel([
|
||||||
|
@ -93,20 +90,20 @@ main() {
|
||||||
event.cancel({"this_hook": "should never run either"});
|
event.cancel({"this_hook": "should never run either"});
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await client!.get(Uri.parse("$url/todos"));
|
var response = await client.get(Uri.parse("$url/todos"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
var result = json.decode(response.body) as List;
|
var result = json.decode(response.body) as List;
|
||||||
expect(result[0]["angel"], equals("framework"));
|
expect(result[0]["angel"], equals("framework"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('asStream() fires', () async {
|
test('asStream() fires', () async {
|
||||||
var stream = todoService!.afterCreated.asStream();
|
var stream = todoService.afterCreated.asStream();
|
||||||
await todoService!.create({'angel': 'framework'});
|
await todoService.create({'angel': 'framework'});
|
||||||
expect(await stream.first.then((e) => e.result['angel']), 'framework');
|
expect(await stream.first.then((e) => e.result['angel']), 'framework');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('metadata', () async {
|
test('metadata', () async {
|
||||||
final service = HookedService(IncrementService())..addHooks(app!);
|
final service = HookedService(IncrementService())..addHooks(app);
|
||||||
expect(service.inner, isNot(const IsInstanceOf<MapService>()));
|
expect(service.inner, isNot(const IsInstanceOf<MapService>()));
|
||||||
IncrementService.TIMES = 0;
|
IncrementService.TIMES = 0;
|
||||||
await service.index();
|
await service.index();
|
||||||
|
@ -114,15 +111,15 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('inject request + response', () async {
|
test('inject request + response', () async {
|
||||||
HookedService books = app!.findService('books')
|
HookedService books = app.findService('books')
|
||||||
as HookedService<dynamic, dynamic, Service<dynamic, dynamic>>;
|
as HookedService<dynamic, dynamic, Service<dynamic, dynamic>>;
|
||||||
|
|
||||||
books.beforeIndexed.listen((e) {
|
books.beforeIndexed.listen((e) {
|
||||||
expect([e.request, e.response], everyElement(isNotNull));
|
expect([e.request, e.response], everyElement(isNotNull));
|
||||||
print('Indexing books at path: ${e.request!.path}');
|
print('Indexing books at path: ${e.request?.path}');
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await client!.get(Uri.parse('$url/books'));
|
var response = await client.get(Uri.parse('$url/books'));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
|
||||||
var result = json.decode(response.body);
|
var result = json.decode(response.body);
|
||||||
|
@ -138,8 +135,8 @@ main() {
|
||||||
var type = e.isBefore ? 'before' : 'after';
|
var type = e.isBefore ? 'before' : 'after';
|
||||||
print('Params to $type ${e.eventName}: ${e.params}');
|
print('Params to $type ${e.eventName}: ${e.params}');
|
||||||
expect(e.params, isMap);
|
expect(e.params, isMap);
|
||||||
expect(e.params!.keys, contains('provider'));
|
expect(e.params?.keys, contains('provider'));
|
||||||
expect(e.params!['provider'], const IsInstanceOf<Providers>());
|
expect(e.params?['provider'], const IsInstanceOf<Providers>());
|
||||||
}
|
}
|
||||||
|
|
||||||
svc
|
svc
|
||||||
|
|
|
@ -35,11 +35,11 @@ bool interceptService(RequestContext req, ResponseContext res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel? app;
|
late Angel app;
|
||||||
Angel? nested;
|
late Angel nested;
|
||||||
Angel? todos;
|
late Angel todos;
|
||||||
String? url;
|
late String url;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector());
|
app = Angel(reflector: MirrorsReflector());
|
||||||
|
@ -47,7 +47,7 @@ main() {
|
||||||
todos = Angel(reflector: MirrorsReflector());
|
todos = Angel(reflector: MirrorsReflector());
|
||||||
|
|
||||||
[app, nested, todos].forEach((Angel? app) {
|
[app, nested, todos].forEach((Angel? app) {
|
||||||
app!.logger = Logger('routing_test')
|
app?.logger = Logger('routing_test')
|
||||||
..onRecord.listen((rec) {
|
..onRecord.listen((rec) {
|
||||||
if (rec.error != null) {
|
if (rec.error != null) {
|
||||||
stdout
|
stdout
|
||||||
|
@ -58,44 +58,44 @@ main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
todos!.get('/action/:action', (req, res) => res.json(req.params));
|
todos.get('/action/:action', (req, res) => res.json(req.params));
|
||||||
|
|
||||||
late Route ted;
|
late Route ted;
|
||||||
|
|
||||||
ted = nested!.post('/ted/:route', (RequestContext req, res) {
|
ted = nested.post('/ted/:route', (RequestContext req, res) {
|
||||||
print('Params: ${req.params}');
|
print('Params: ${req.params}');
|
||||||
print('Path: ${ted.path}, uri: ${req.path}');
|
print('Path: ${ted.path}, uri: ${req.path}');
|
||||||
print('matcher: ${ted.parser}');
|
print('matcher: ${ted.parser}');
|
||||||
return req.params;
|
return req.params;
|
||||||
});
|
});
|
||||||
|
|
||||||
app!.mount('/nes', nested!);
|
app.mount('/nes', nested);
|
||||||
app!.get('/meta', testMiddlewareMetadata);
|
app.get('/meta', testMiddlewareMetadata);
|
||||||
app!.get('/intercepted', (req, res) => 'This should not be shown',
|
app.get('/intercepted', (req, res) => 'This should not be shown',
|
||||||
middleware: [interceptor]);
|
middleware: [interceptor]);
|
||||||
app!.get('/hello', (req, res) => 'world');
|
app.get('/hello', (req, res) => 'world');
|
||||||
app!.get('/name/:first/last/:last', (req, res) => req.params);
|
app.get('/name/:first/last/:last', (req, res) => req.params);
|
||||||
app!.post(
|
app.post(
|
||||||
'/lambda',
|
'/lambda',
|
||||||
(RequestContext req, res) =>
|
(RequestContext req, res) =>
|
||||||
req.parseBody().then((_) => req.bodyAsMap));
|
req.parseBody().then((_) => req.bodyAsMap));
|
||||||
app!.mount('/todos/:id', todos!);
|
app.mount('/todos/:id', todos);
|
||||||
app!
|
app
|
||||||
.get('/greet/:name',
|
.get('/greet/:name',
|
||||||
(RequestContext req, res) async => "Hello ${req.params['name']}")
|
(RequestContext req, res) async => "Hello ${req.params['name']}")
|
||||||
.name = 'Named routes';
|
.name = 'Named routes';
|
||||||
app!.get('/named', (req, ResponseContext res) async {
|
app.get('/named', (req, ResponseContext res) async {
|
||||||
await res.redirectTo('Named routes', {'name': 'tests'});
|
await res.redirectTo('Named routes', {'name': 'tests'});
|
||||||
});
|
});
|
||||||
app!.get('/log', (RequestContext req, res) async {
|
app.get('/log', (RequestContext req, res) async {
|
||||||
print("Query: ${req.queryParameters}");
|
print("Query: ${req.queryParameters}");
|
||||||
return "Logged";
|
return "Logged";
|
||||||
});
|
});
|
||||||
|
|
||||||
app!.get('/method', (req, res) => 'Only GET');
|
app.get('/method', (req, res) => 'Only GET');
|
||||||
app!.post('/method', (req, res) => 'Only POST');
|
app.post('/method', (req, res) => 'Only POST');
|
||||||
|
|
||||||
app!.use('/query', QueryService());
|
app.use('/query', QueryService());
|
||||||
|
|
||||||
RequestHandler write(String message) {
|
RequestHandler write(String message) {
|
||||||
return (req, res) {
|
return (req, res) {
|
||||||
|
@ -104,10 +104,10 @@ main() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
app!.chain([write('a')]).chain([write('b'), write('c')]).get(
|
app.chain([write('a')]).chain([write('b'), write('c')]).get(
|
||||||
'/chained', (req, res) => res.close());
|
'/chained', (req, res) => res.close());
|
||||||
|
|
||||||
app!.fallback((req, res) => 'MJ');
|
app.fallback((req, res) => 'MJ');
|
||||||
|
|
||||||
//app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
|
//app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
|
||||||
|
|
||||||
|
@ -117,22 +117,17 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await app!.close();
|
await app.close();
|
||||||
app = null;
|
client.close();
|
||||||
nested = null;
|
|
||||||
todos = null;
|
|
||||||
client!.close();
|
|
||||||
client = null;
|
|
||||||
url = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can match basic url', () async {
|
test('Can match basic url', () async {
|
||||||
var response = await client!.get(Uri.parse("$url/hello"));
|
var response = await client.get(Uri.parse("$url/hello"));
|
||||||
expect(response.body, equals('"world"'));
|
expect(response.body, equals('"world"'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can match url with multiple parameters', () async {
|
test('Can match url with multiple parameters', () async {
|
||||||
var response = await client!.get(Uri.parse('$url/name/HELLO/last/WORLD'));
|
var response = await client.get(Uri.parse('$url/name/HELLO/last/WORLD'));
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
var json_ = json.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
expect(json_, const IsInstanceOf<Map>());
|
expect(json_, const IsInstanceOf<Map>());
|
||||||
|
@ -141,18 +136,18 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Chained routes', () async {
|
test('Chained routes', () async {
|
||||||
var response = await client!.get(Uri.parse("$url/chained"));
|
var response = await client.get(Uri.parse("$url/chained"));
|
||||||
expect(response.body, equals('abc'));
|
expect(response.body, equals('abc'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can nest another Angel instance', () async {
|
test('Can nest another Angel instance', () async {
|
||||||
var response = await client!.post(Uri.parse('$url/nes/ted/foo'));
|
var response = await client.post(Uri.parse('$url/nes/ted/foo'));
|
||||||
var json_ = json.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
expect(json_['route'], equals('foo'));
|
expect(json_['route'], equals('foo'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can parse parameters from a nested Angel instance', () async {
|
test('Can parse parameters from a nested Angel instance', () async {
|
||||||
var response = await client!.get(Uri.parse('$url/todos/1337/action/test'));
|
var response = await client.get(Uri.parse('$url/todos/1337/action/test'));
|
||||||
var json_ = json.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
print('JSON: $json_');
|
print('JSON: $json_');
|
||||||
expect(json_['id'], equals('1337'));
|
expect(json_['id'], equals('1337'));
|
||||||
|
@ -160,32 +155,32 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can add and use named middleware', () async {
|
test('Can add and use named middleware', () async {
|
||||||
var response = await client!.get(Uri.parse('$url/intercepted'));
|
var response = await client.get(Uri.parse('$url/intercepted'));
|
||||||
expect(response.body, equals('Middleware'));
|
expect(response.body, equals('Middleware'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Middleware via metadata', () async {
|
test('Middleware via metadata', () async {
|
||||||
// Metadata
|
// Metadata
|
||||||
var response = await client!.get(Uri.parse('$url/meta'));
|
var response = await client.get(Uri.parse('$url/meta'));
|
||||||
expect(response.body, equals('Middleware'));
|
expect(response.body, equals('Middleware'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can serialize function result as JSON', () async {
|
test('Can serialize function result as JSON', () async {
|
||||||
Map headers = <String, String>{'Content-Type': 'application/json'};
|
Map headers = <String, String>{'Content-Type': 'application/json'};
|
||||||
String postData = json.encode({'it': 'works'});
|
String postData = json.encode({'it': 'works'});
|
||||||
var response = await client!.post(Uri.parse("$url/lambda"),
|
var response = await client.post(Uri.parse("$url/lambda"),
|
||||||
headers: headers as Map<String, String>, body: postData);
|
headers: headers as Map<String, String>, body: postData);
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
expect(json.decode(response.body)['it'], equals('works'));
|
expect(json.decode(response.body)['it'], equals('works'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Fallback routes', () async {
|
test('Fallback routes', () async {
|
||||||
var response = await client!.get(Uri.parse('$url/my_favorite_artist'));
|
var response = await client.get(Uri.parse('$url/my_favorite_artist'));
|
||||||
expect(response.body, equals('"MJ"'));
|
expect(response.body, equals('"MJ"'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can name routes', () {
|
test('Can name routes', () {
|
||||||
Route foo = app!.get('/framework/:id', null)..name = 'frm';
|
Route foo = app.get('/framework/:id', null)..name = 'frm';
|
||||||
print('Foo: $foo');
|
print('Foo: $foo');
|
||||||
String uri = foo.makeUri({'id': 'angel'});
|
String uri = foo.makeUri({'id': 'angel'});
|
||||||
print(uri);
|
print(uri);
|
||||||
|
@ -193,32 +188,32 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Redirect to named routes', () async {
|
test('Redirect to named routes', () async {
|
||||||
var response = await client!.get(Uri.parse('$url/named'));
|
var response = await client.get(Uri.parse('$url/named'));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(json.decode(response.body), equals('Hello tests'));
|
expect(json.decode(response.body), equals('Hello tests'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Match routes, even with query params', () async {
|
test('Match routes, even with query params', () async {
|
||||||
var response = await client!
|
var response = await client
|
||||||
.get(Uri.parse("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo"));
|
.get(Uri.parse("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(json.decode(response.body), equals('Logged'));
|
expect(json.decode(response.body), equals('Logged'));
|
||||||
|
|
||||||
response = await client!.get(Uri.parse("$url/query/foo?bar=baz"));
|
response = await client.get(Uri.parse("$url/query/foo?bar=baz"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, equals("Service with Middleware"));
|
expect(response.body, equals("Service with Middleware"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('only match route with matching method', () async {
|
test('only match route with matching method', () async {
|
||||||
var response = await client!.get(Uri.parse("$url/method"));
|
var response = await client.get(Uri.parse("$url/method"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, '"Only GET"');
|
expect(response.body, '"Only GET"');
|
||||||
|
|
||||||
response = await client!.post(Uri.parse("$url/method"));
|
response = await client.post(Uri.parse("$url/method"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, '"Only POST"');
|
expect(response.body, '"Only POST"');
|
||||||
|
|
||||||
response = await client!.patch(Uri.parse("$url/method"));
|
response = await client.patch(Uri.parse("$url/method"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, '"MJ"');
|
expect(response.body, '"MJ"');
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,10 +8,10 @@ import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
Angel? app;
|
late Angel app;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
late HttpServer server;
|
late HttpServer server;
|
||||||
String? url;
|
late String url;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector())
|
app = Angel(reflector: MirrorsReflector())
|
||||||
|
@ -27,19 +27,16 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
app = null;
|
client.close();
|
||||||
url = null;
|
|
||||||
client!.close();
|
|
||||||
client = null;
|
|
||||||
await server.close(force: true);
|
await server.close(force: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("correct content-type", () async {
|
test("correct content-type", () async {
|
||||||
var response = await client!.get(Uri.parse('$url/foo'));
|
var response = await client.get(Uri.parse('$url/foo'));
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
expect(response.headers['content-type'], contains('application/json'));
|
expect(response.headers['content-type'], contains('application/json'));
|
||||||
|
|
||||||
response = await client!.get(Uri.parse('$url/bar'));
|
response = await client.get(Uri.parse('$url/bar'));
|
||||||
print('Response: ${response.body}');
|
print('Response: ${response.body}');
|
||||||
expect(response.headers['content-type'], contains('text/html'));
|
expect(response.headers['content-type'], contains('text/html'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,10 +18,10 @@ main() {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
};
|
};
|
||||||
Angel? app;
|
late Angel app;
|
||||||
late MapService service;
|
late MapService service;
|
||||||
String? url;
|
late String url;
|
||||||
http.Client? client;
|
late http.Client client;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
app = Angel(reflector: MirrorsReflector())
|
app = Angel(reflector: MirrorsReflector())
|
||||||
|
@ -37,16 +37,13 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await app!.close();
|
await app.close();
|
||||||
app = null;
|
client.close();
|
||||||
url = null;
|
|
||||||
client!.close();
|
|
||||||
client = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('memory', () {
|
group('memory', () {
|
||||||
test('can index an empty service', () async {
|
test('can index an empty service', () async {
|
||||||
var response = await client!.get(Uri.parse("$url/todos/"));
|
var response = await client.get(Uri.parse("$url/todos/"));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
expect(response.body, equals('[]'));
|
expect(response.body, equals('[]'));
|
||||||
print(response.body);
|
print(response.body);
|
||||||
|
@ -55,7 +52,7 @@ main() {
|
||||||
|
|
||||||
test('can create data', () async {
|
test('can create data', () async {
|
||||||
String postData = json.encode({'text': 'Hello, world!'});
|
String postData = json.encode({'text': 'Hello, world!'});
|
||||||
var response = await client!.post(Uri.parse("$url/todos"),
|
var response = await client.post(Uri.parse("$url/todos"),
|
||||||
headers: headers as Map<String, String>, body: postData);
|
headers: headers as Map<String, String>, body: postData);
|
||||||
expect(response.statusCode, 201);
|
expect(response.statusCode, 201);
|
||||||
var jsons = json.decode(response.body);
|
var jsons = json.decode(response.body);
|
||||||
|
@ -65,9 +62,9 @@ main() {
|
||||||
|
|
||||||
test('can fetch data', () async {
|
test('can fetch data', () async {
|
||||||
String postData = json.encode({'text': 'Hello, world!'});
|
String postData = json.encode({'text': 'Hello, world!'});
|
||||||
await client!.post(Uri.parse("$url/todos"),
|
await client.post(Uri.parse("$url/todos"),
|
||||||
headers: headers as Map<String, String>, body: postData);
|
headers: headers as Map<String, String>, body: postData);
|
||||||
var response = await client!.get(Uri.parse("$url/todos/0"));
|
var response = await client.get(Uri.parse("$url/todos/0"));
|
||||||
expect(response.statusCode, 200);
|
expect(response.statusCode, 200);
|
||||||
var jsons = json.decode(response.body);
|
var jsons = json.decode(response.body);
|
||||||
print(jsons);
|
print(jsons);
|
||||||
|
@ -76,12 +73,12 @@ main() {
|
||||||
|
|
||||||
test('can modify data', () async {
|
test('can modify data', () async {
|
||||||
String postData = json.encode({'text': 'Hello, world!'});
|
String postData = json.encode({'text': 'Hello, world!'});
|
||||||
await client!.post(Uri.parse("$url/todos"),
|
await client.post(Uri.parse("$url/todos"),
|
||||||
headers: headers as Map<String, String>, body: postData);
|
headers: headers as Map<String, String>, body: postData);
|
||||||
postData = json.encode({'text': 'modified'});
|
postData = json.encode({'text': 'modified'});
|
||||||
|
|
||||||
var response = await client!
|
var response = await client.patch(Uri.parse("$url/todos/0"),
|
||||||
.patch(Uri.parse("$url/todos/0"), headers: headers, body: postData);
|
headers: headers, body: postData);
|
||||||
expect(response.statusCode, 200);
|
expect(response.statusCode, 200);
|
||||||
var jsons = json.decode(response.body);
|
var jsons = json.decode(response.body);
|
||||||
print(jsons);
|
print(jsons);
|
||||||
|
@ -90,12 +87,12 @@ main() {
|
||||||
|
|
||||||
test('can overwrite data', () async {
|
test('can overwrite data', () async {
|
||||||
String postData = json.encode({'text': 'Hello, world!'});
|
String postData = json.encode({'text': 'Hello, world!'});
|
||||||
await client!.post(Uri.parse("$url/todos"),
|
await client.post(Uri.parse("$url/todos"),
|
||||||
headers: headers as Map<String, String>, body: postData);
|
headers: headers as Map<String, String>, body: postData);
|
||||||
postData = json.encode({'over': 'write'});
|
postData = json.encode({'over': 'write'});
|
||||||
|
|
||||||
var response = await client!
|
var response = await client.post(Uri.parse("$url/todos/0"),
|
||||||
.post(Uri.parse("$url/todos/0"), headers: headers, body: postData);
|
headers: headers, body: postData);
|
||||||
expect(response.statusCode, 200);
|
expect(response.statusCode, 200);
|
||||||
var jsons = json.decode(response.body);
|
var jsons = json.decode(response.body);
|
||||||
print(jsons);
|
print(jsons);
|
||||||
|
@ -116,12 +113,12 @@ main() {
|
||||||
|
|
||||||
test('can delete data', () async {
|
test('can delete data', () async {
|
||||||
String postData = json.encode({'text': 'Hello, world!'});
|
String postData = json.encode({'text': 'Hello, world!'});
|
||||||
var created = await client!
|
var created = await client
|
||||||
.post(Uri.parse("$url/todos"),
|
.post(Uri.parse("$url/todos"),
|
||||||
headers: headers as Map<String, String>, body: postData)
|
headers: headers as Map<String, String>, body: postData)
|
||||||
.then((r) => json.decode(r.body));
|
.then((r) => json.decode(r.body));
|
||||||
var response =
|
var response =
|
||||||
await client!.delete(Uri.parse("$url/todos/${created['id']}"));
|
await client.delete(Uri.parse("$url/todos/${created['id']}"));
|
||||||
expect(response.statusCode, 200);
|
expect(response.statusCode, 200);
|
||||||
var json_ = json.decode(response.body);
|
var json_ = json.decode(response.body);
|
||||||
print(json_);
|
print(json_);
|
||||||
|
@ -129,7 +126,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('cannot remove all unless explicitly set', () async {
|
test('cannot remove all unless explicitly set', () async {
|
||||||
var response = await client!.delete(Uri.parse('$url/todos/null'));
|
var response = await client.delete(Uri.parse('$url/todos/null'));
|
||||||
expect(response.statusCode, 403);
|
expect(response.statusCode, 403);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:angel_route/angel_route.dart';
|
import 'package:angel_route/angel_route.dart';
|
||||||
|
|
||||||
main() {
|
void main() {
|
||||||
final router = Router();
|
final router = Router();
|
||||||
|
|
||||||
router.get('/whois/~:user', () {});
|
router.get('/whois/~:user', () {});
|
||||||
|
@ -12,7 +12,7 @@ main() {
|
||||||
router.get('/ordinal/int:n([0-9]+)st', () {});
|
router.get('/ordinal/int:n([0-9]+)st', () {});
|
||||||
|
|
||||||
print(router.resolveAbsolute('/whois/~thosakwe').first.allParams);
|
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);
|
print(router.resolveAbsolute('/ordinal/1st').first.allParams);
|
||||||
|
|
||||||
router.get('/users', () {});
|
router.get('/users', () {});
|
||||||
|
|
|
@ -11,10 +11,10 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
|
||||||
/// A variation of the [Router] support both hash routing and push state.
|
/// A variation of the [Router] support both hash routing and push state.
|
||||||
abstract class BrowserRouter<T> extends Router<T> {
|
abstract class BrowserRouter<T> extends Router<T> {
|
||||||
/// Fires whenever the active route changes. Fires `null` if none is selected (404).
|
/// 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).
|
/// 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.
|
/// Set `hash` to true to use hash routing instead of push state.
|
||||||
/// `listen` as `true` will call `listen` after initialization.
|
/// `listen` as `true` will call `listen` after initialization.
|
||||||
|
@ -26,7 +26,7 @@ abstract class BrowserRouter<T> extends Router<T> {
|
||||||
|
|
||||||
BrowserRouter._() : super();
|
BrowserRouter._() : super();
|
||||||
|
|
||||||
void _goTo(String? path);
|
void _goTo(String path);
|
||||||
|
|
||||||
/// Navigates to the path generated by calling
|
/// Navigates to the path generated by calling
|
||||||
/// [navigate] with the given [linkParams].
|
/// [navigate] with the given [linkParams].
|
||||||
|
@ -41,26 +41,26 @@ abstract class BrowserRouter<T> extends Router<T> {
|
||||||
void listen();
|
void listen();
|
||||||
|
|
||||||
/// Identical to [all].
|
/// 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>
|
abstract class _BrowserRouterImpl<T> extends Router<T>
|
||||||
implements BrowserRouter<T> {
|
implements BrowserRouter<T> {
|
||||||
bool _listening = false;
|
bool _listening = false;
|
||||||
Route? _current;
|
Route? _current;
|
||||||
StreamController<RoutingResult<T>?> _onResolve =
|
final StreamController<RoutingResult<T>> _onResolve =
|
||||||
StreamController<RoutingResult<T>?>();
|
StreamController<RoutingResult<T>>();
|
||||||
StreamController<Route<T>?> _onRoute = StreamController<Route<T>?>();
|
final StreamController<Route<T>> _onRoute = StreamController<Route<T>>();
|
||||||
|
|
||||||
Route? get currentRoute => _current;
|
Route? get currentRoute => _current;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<RoutingResult<T>?> get onResolve => _onResolve.stream;
|
Stream<RoutingResult<T>> get onResolve => _onResolve.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<Route<T>?> get onRoute => _onRoute.stream;
|
Stream<Route<T>> get onRoute => _onRoute.stream;
|
||||||
|
|
||||||
_BrowserRouterImpl({bool? listen}) : super() {
|
_BrowserRouterImpl({bool listen = false}) : super() {
|
||||||
if (listen != false) this.listen();
|
if (listen != false) this.listen();
|
||||||
prepareAnchors();
|
prepareAnchors();
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,9 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
|
||||||
@override
|
@override
|
||||||
void go(Iterable linkParams) => _goTo(navigate(linkParams));
|
void go(Iterable linkParams) => _goTo(navigate(linkParams));
|
||||||
|
|
||||||
Route on(String path, T handler, {Iterable<T>? middleware}) =>
|
@override
|
||||||
|
Route on(String path, T handler,
|
||||||
|
{Iterable<T> middleware = const Iterable.empty()}) =>
|
||||||
all(path, handler, middleware: middleware);
|
all(path, handler, middleware: middleware);
|
||||||
|
|
||||||
void prepareAnchors() {
|
void prepareAnchors() {
|
||||||
|
@ -76,14 +78,14 @@ abstract class _BrowserRouterImpl<T> extends Router<T>
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
.cast<AnchorElement>(); //:not([dynamic])');
|
.cast<AnchorElement>(); //:not([dynamic])');
|
||||||
|
|
||||||
for (final AnchorElement $a in anchors) {
|
for (final $a in anchors) {
|
||||||
if ($a.attributes.containsKey('href') &&
|
if ($a.attributes.containsKey('href') &&
|
||||||
!$a.attributes.containsKey('download') &&
|
$a.attributes.containsKey('download') &&
|
||||||
!$a.attributes.containsKey('target') &&
|
$a.attributes.containsKey('target') &&
|
||||||
$a.attributes['rel'] != 'external') {
|
$a.attributes['rel'] != 'external') {
|
||||||
$a.onClick.listen((e) {
|
$a.onClick.listen((e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
_goTo($a.attributes['href']);
|
_goTo($a.attributes['href']!);
|
||||||
//go($a.attributes['href'].split('/').where((str) => str.isNotEmpty));
|
//go($a.attributes['href'].split('/').where((str) => str.isNotEmpty));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -110,32 +112,36 @@ class _HashRouter<T> extends _BrowserRouterImpl<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void _goTo(String? uri) {
|
void _goTo(String uri) {
|
||||||
window.location.hash = '#$uri';
|
window.location.hash = '#$uri';
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleHash([_]) {
|
void handleHash([_]) {
|
||||||
final path = window.location.hash.replaceAll(_hash, '');
|
final path = window.location.hash.replaceAll(_hash, '');
|
||||||
Iterable<RoutingResult<T>> allResolved = resolveAbsolute(path);
|
var allResolved = resolveAbsolute(path);
|
||||||
|
|
||||||
final resolved = allResolved.isEmpty ? null : allResolved.first;
|
if (allResolved.isEmpty) {
|
||||||
|
// TODO: Need fixing
|
||||||
if (resolved == null) {
|
//_onResolve.add(null);
|
||||||
_onResolve.add(null);
|
//_onRoute.add(_current = null);
|
||||||
_onRoute.add(_current = null);
|
_current = null;
|
||||||
} else if (resolved != null && resolved.route != _current) {
|
} else {
|
||||||
|
var resolved = allResolved.first;
|
||||||
|
if (resolved.route != _current) {
|
||||||
_onResolve.add(resolved);
|
_onResolve.add(resolved);
|
||||||
_onRoute.add(_current = resolved.route);
|
_onRoute.add(_current = resolved.route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handlePath(String path) {
|
void handlePath(String path) {
|
||||||
final RoutingResult<T> resolved = resolveAbsolute(path).first;
|
final resolved = resolveAbsolute(path).first;
|
||||||
|
|
||||||
if (resolved == null) {
|
//if (resolved == null) {
|
||||||
_onResolve.add(null);
|
// _onResolve.add(null);
|
||||||
_onRoute.add(_current = null);
|
// _onRoute.add(_current = null);
|
||||||
} else if (resolved != null && resolved.route != _current) {
|
//} else
|
||||||
|
if (resolved.route != _current) {
|
||||||
_onResolve.add(resolved);
|
_onResolve.add(resolved);
|
||||||
_onRoute.add(_current = resolved.route);
|
_onRoute.add(_current = resolved.route);
|
||||||
}
|
}
|
||||||
|
@ -149,12 +155,12 @@ class _HashRouter<T> extends _BrowserRouterImpl<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
|
class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
|
||||||
String? _basePath;
|
late String _basePath;
|
||||||
|
|
||||||
_PushStateRouter({required bool listen, Route? root}) : super(listen: listen) {
|
_PushStateRouter({required bool listen}) : super(listen: listen) {
|
||||||
var $base = window.document.querySelector('base[href]') as BaseElement;
|
var $base = window.document.querySelector('base[href]') as BaseElement;
|
||||||
|
|
||||||
if ($base?.href?.isNotEmpty != true) {
|
if ($base.href.isNotEmpty != true) {
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'You must have a <base href="<base-url-here>"> element present in your document to run the push state router.');
|
'You must have a <base href="<base-url-here>"> element present in your document to run the push state router.');
|
||||||
}
|
}
|
||||||
|
@ -163,42 +169,48 @@ class _PushStateRouter<T> extends _BrowserRouterImpl<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void _goTo(String? uri) {
|
void _goTo(String uri) {
|
||||||
final RoutingResult<T> resolved = resolveAbsolute(uri).first;
|
final resolved = resolveAbsolute(uri).first;
|
||||||
var relativeUri = uri;
|
var relativeUri = uri;
|
||||||
|
|
||||||
if (_basePath?.isNotEmpty == true) {
|
if (_basePath.isNotEmpty) {
|
||||||
relativeUri = p.join(_basePath!, uri!.replaceAll(_straySlashes, ''));
|
relativeUri = p.join(_basePath, uri.replaceAll(_straySlashes, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolved == null) {
|
//if (resolved == null) {
|
||||||
_onResolve.add(null);
|
// _onResolve.add(null);
|
||||||
_onRoute.add(_current = null);
|
// _onRoute.add(_current = null);
|
||||||
} else {
|
//} else {
|
||||||
final route = resolved.route!;
|
final route = resolved.route;
|
||||||
window.history.pushState({'path': route.path, 'params': {}},
|
var thisPath = route.name;
|
||||||
route.name ?? route.path, relativeUri);
|
if (thisPath.isEmpty) {
|
||||||
|
thisPath = route.path;
|
||||||
|
}
|
||||||
|
window.history
|
||||||
|
.pushState({'path': route.path, 'params': {}}, thisPath, relativeUri);
|
||||||
_onResolve.add(resolved);
|
_onResolve.add(resolved);
|
||||||
_onRoute.add(_current = route);
|
_onRoute.add(_current = route);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleState(state) {
|
void handleState(state) {
|
||||||
if (state is Map && state.containsKey('path')) {
|
if (state is Map && state.containsKey('path')) {
|
||||||
var path = state['path'].toString();
|
var path = state['path'].toString();
|
||||||
final RoutingResult<T> resolved = resolveAbsolute(path).first;
|
final resolved = resolveAbsolute(path).first;
|
||||||
|
|
||||||
if (resolved != null && resolved.route != _current) {
|
if (resolved.route != _current) {
|
||||||
//properties.addAll(state['properties'] ?? {});
|
//properties.addAll(state['properties'] ?? {});
|
||||||
_onResolve.add(resolved);
|
_onResolve.add(resolved);
|
||||||
_onRoute.add(_current = resolved.route);
|
_onRoute.add(_current = resolved.route);
|
||||||
} else {
|
} else {
|
||||||
_onResolve.add(null);
|
//_onResolve.add(null);
|
||||||
_onRoute.add(_current = null);
|
//_onRoute.add(_current = null);
|
||||||
|
_current = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_onResolve.add(null);
|
//_onResolve.add(null);
|
||||||
_onRoute.add(_current = null);
|
//_onRoute.add(_current = null);
|
||||||
|
_current = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,9 @@ class RouteGrammar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = ParameterSegment(match[2], rgx);
|
// TODO: relook at this later
|
||||||
|
var m2 = match[2] ?? '';
|
||||||
|
var s = ParameterSegment(m2, rgx);
|
||||||
return r.value![1] == true ? OptionalSegment(s) : s;
|
return r.value![1] == true ? OptionalSegment(s) : s;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,12 +97,12 @@ class RouteDefinition {
|
||||||
|
|
||||||
RouteDefinition(this.segments);
|
RouteDefinition(this.segments);
|
||||||
|
|
||||||
Parser<RouteResult?>? compile() {
|
Parser<RouteResult>? compile() {
|
||||||
Parser<RouteResult?>? out;
|
Parser<RouteResult>? out;
|
||||||
|
|
||||||
for (int i = 0; i < segments.length; i++) {
|
for (var i = 0; i < segments.length; i++) {
|
||||||
var s = segments[i];
|
var s = segments[i];
|
||||||
bool isLast = i == segments.length - 1;
|
var isLast = i == segments.length - 1;
|
||||||
if (out == null) {
|
if (out == null) {
|
||||||
out = s.compile(isLast);
|
out = s.compile(isLast);
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,7 +118,7 @@ class RouteDefinition {
|
||||||
abstract class RouteSegment {
|
abstract class RouteSegment {
|
||||||
Parser<RouteResult> compile(bool isLast);
|
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 {
|
class SlashSegment implements RouteSegment {
|
||||||
|
@ -182,8 +184,15 @@ class WildcardSegment extends RouteSegment {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<RouteResult> compile(bool isLast) {
|
Parser<RouteResult> compile(bool isLast) {
|
||||||
return match(_compile(isLast))
|
return match(_compile(isLast)).map((r) {
|
||||||
.map((r) => RouteResult({}, tail: r.scanner.lastMatch![1]));
|
var result = r.scanner.lastMatch;
|
||||||
|
if (result != null) {
|
||||||
|
//return RouteResult({}, tail: r.scanner.lastMatch![1])
|
||||||
|
return RouteResult({}, tail: result[1]);
|
||||||
|
} else {
|
||||||
|
return RouteResult({});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -214,31 +223,53 @@ class OptionalSegment extends ParameterSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<RouteResult?> compileNext(Parser<RouteResult> p, bool isLast) {
|
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
|
||||||
return p.then(_compile().opt()).map((r) {
|
return p.then(_compile().opt()).map((r) {
|
||||||
if (r.value![1] == null) return r.value![0] as RouteResult?;
|
// Return an empty RouteResult if null
|
||||||
return (r.value![0] as RouteResult)
|
if (r.value == null) {
|
||||||
..addAll({name: Uri.decodeComponent(r.value![1] as String)});
|
return RouteResult({});
|
||||||
|
}
|
||||||
|
|
||||||
|
var v = r.value!;
|
||||||
|
|
||||||
|
if (v[1] == null) {
|
||||||
|
return v[0] as RouteResult;
|
||||||
|
}
|
||||||
|
return (v[0] as RouteResult)
|
||||||
|
..addAll({name: Uri.decodeComponent(v as String)});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParameterSegment extends RouteSegment {
|
class ParameterSegment extends RouteSegment {
|
||||||
final String? name;
|
final String name;
|
||||||
final RegExp? regExp;
|
final RegExp? regExp;
|
||||||
|
|
||||||
ParameterSegment(this.name, this.regExp);
|
ParameterSegment(this.name, this.regExp);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (regExp != null) return 'Param: $name (${regExp!.pattern})';
|
if (regExp != null) {
|
||||||
|
return 'Param: $name (${regExp?.pattern})';
|
||||||
|
}
|
||||||
return 'Param: $name';
|
return 'Param: $name';
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser<String?> _compile() {
|
Parser<String> _compile() {
|
||||||
return regExp != null
|
if (regExp != null) {
|
||||||
? match<String?>(regExp!).value((r) => r.scanner.lastMatch![1])
|
return match<String>(regExp!).value((r) {
|
||||||
: RouteGrammar.notSlash;
|
var result = r.scanner.lastMatch;
|
||||||
|
if (result != null) {
|
||||||
|
// TODO: Invalid method
|
||||||
|
//return r.scanner.lastMatch![1];
|
||||||
|
return result.toString();
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return RouteGrammar.notSlash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -248,7 +279,7 @@ class ParameterSegment extends RouteSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<RouteResult?> compileNext(Parser<RouteResult> p, bool isLast) {
|
Parser<RouteResult> compileNext(Parser<RouteResult> p, bool isLast) {
|
||||||
return p.then(_compile()).map((r) {
|
return p.then(_compile()).map((r) {
|
||||||
return (r.value![0] as RouteResult)
|
return (r.value![0] as RouteResult)
|
||||||
..addAll({name: Uri.decodeComponent(r.value![1] as String)});
|
..addAll({name: Uri.decodeComponent(r.value![1] as String)});
|
||||||
|
|
|
@ -2,21 +2,28 @@ part of angel_route.src.router;
|
||||||
|
|
||||||
/// Represents a virtual location within an application.
|
/// Represents a virtual location within an application.
|
||||||
class Route<T> {
|
class Route<T> {
|
||||||
final String? method;
|
String method;
|
||||||
final String path;
|
String path;
|
||||||
final List<T>? handlers;
|
|
||||||
final Map<String, Map<String, dynamic>> _cache = {};
|
final Map<String, Map<String, dynamic>> _cache = {};
|
||||||
final RouteDefinition? _routeDefinition;
|
String name = '';
|
||||||
String? name;
|
Parser<RouteResult>? _parser;
|
||||||
Parser<RouteResult?>? _parser;
|
late RouteDefinition _routeDefinition;
|
||||||
|
late List<T> handlers;
|
||||||
|
|
||||||
Route(this.path, {required this.method, required this.handlers})
|
Route(this.path, {required this.method, required this.handlers}) {
|
||||||
: _routeDefinition = RouteGrammar.routeDefinition
|
var result = RouteGrammar.routeDefinition
|
||||||
.parse(SpanScanner(path.replaceAll(_straySlashes, '')))!
|
.parse(SpanScanner(path.replaceAll(_straySlashes, '')));
|
||||||
.value {
|
|
||||||
if (_routeDefinition?.segments.isNotEmpty != true) {
|
if (result.value != null) {
|
||||||
|
//throw ArgumentError('[Route] Failed to create route for $path');
|
||||||
|
_routeDefinition = result.value!;
|
||||||
|
|
||||||
|
if (_routeDefinition.segments.isEmpty) {
|
||||||
_parser = match('').map((r) => RouteResult({}));
|
_parser = match('').map((r) => RouteResult({}));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
//print('[Route] Failed to create route for $path');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
factory Route.join(Route<T> a, Route<T> b) {
|
factory Route.join(Route<T> a, Route<T> b) {
|
||||||
|
@ -26,7 +33,9 @@ class Route<T> {
|
||||||
method: b.method, handlers: b.handlers);
|
method: b.method, handlers: b.handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser<RouteResult?>? get parser => _parser ??= _routeDefinition!.compile();
|
//List<T> get handlers => _handlers;
|
||||||
|
|
||||||
|
Parser<RouteResult>? get parser => _parser ??= _routeDefinition.compile();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -40,9 +49,9 @@ class Route<T> {
|
||||||
|
|
||||||
String makeUri(Map<String, dynamic> params) {
|
String makeUri(Map<String, dynamic> params) {
|
||||||
var b = StringBuffer();
|
var b = StringBuffer();
|
||||||
int i = 0;
|
var i = 0;
|
||||||
|
|
||||||
for (var seg in _routeDefinition!.segments) {
|
for (var seg in _routeDefinition.segments) {
|
||||||
if (i++ > 0) b.write('/');
|
if (i++ > 0) b.write('/');
|
||||||
if (seg is ConstantSegment) {
|
if (seg is ConstantSegment) {
|
||||||
b.write(seg.text);
|
b.write(seg.text);
|
||||||
|
@ -50,7 +59,7 @@ class Route<T> {
|
||||||
if (!params.containsKey(seg.name)) {
|
if (!params.containsKey(seg.name)) {
|
||||||
throw ArgumentError('Missing parameter "${seg.name}".');
|
throw ArgumentError('Missing parameter "${seg.name}".');
|
||||||
}
|
}
|
||||||
b.write(params[seg.name!]);
|
b.write(params[seg.name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +70,7 @@ class Route<T> {
|
||||||
/// The result of matching an individual route.
|
/// The result of matching an individual route.
|
||||||
class RouteResult {
|
class RouteResult {
|
||||||
/// The parsed route parameters.
|
/// The parsed route parameters.
|
||||||
final Map<String?, dynamic> params;
|
final Map<String, dynamic> params;
|
||||||
|
|
||||||
/// Optional. An explicit "tail" value to set.
|
/// Optional. An explicit "tail" value to set.
|
||||||
String? get tail => _tail;
|
String? get tail => _tail;
|
||||||
|
@ -73,7 +82,7 @@ class RouteResult {
|
||||||
void _setTail(String? v) => _tail ??= v;
|
void _setTail(String? v) => _tail ??= v;
|
||||||
|
|
||||||
/// Adds parameters.
|
/// Adds parameters.
|
||||||
void addAll(Map<String?, dynamic> map) {
|
void addAll(Map<String, dynamic> map) {
|
||||||
params.addAll(map);
|
params.addAll(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ library angel_route.src.router;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:combinator/combinator.dart';
|
import 'package:combinator/combinator.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:string_scanner/string_scanner.dart';
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
import '../string_util.dart';
|
import '../string_util.dart';
|
||||||
|
@ -65,9 +64,8 @@ class Router<T> {
|
||||||
/// Adds a route that responds to the given path
|
/// Adds a route that responds to the given path
|
||||||
/// for requests with the given method (case-insensitive).
|
/// for requests with the given method (case-insensitive).
|
||||||
/// Provide '*' as the method to respond to all methods.
|
/// Provide '*' as the method to respond to all methods.
|
||||||
Route<T> addRoute(String? method, String path, T handler,
|
Route<T> addRoute(String method, String path, T handler,
|
||||||
{Iterable<T>? middleware}) {
|
{Iterable<T>? middleware}) {
|
||||||
middleware ??= <T>[];
|
|
||||||
if (_useCache == true) {
|
if (_useCache == true) {
|
||||||
throw StateError('Cannot add routes after caching is enabled.');
|
throw StateError('Cannot add routes after caching is enabled.');
|
||||||
}
|
}
|
||||||
|
@ -75,7 +73,10 @@ class Router<T> {
|
||||||
// Check if any mounted routers can match this
|
// Check if any mounted routers can match this
|
||||||
final handlers = <T>[handler];
|
final handlers = <T>[handler];
|
||||||
|
|
||||||
if (middleware != null) handlers.insertAll(0, middleware);
|
//middleware = <T>[];
|
||||||
|
middleware ??= <T>[];
|
||||||
|
|
||||||
|
handlers.insertAll(0, middleware);
|
||||||
|
|
||||||
final route = Route<T>(path, method: method, handlers: handlers);
|
final route = Route<T>(path, method: method, handlers: handlers);
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
|
@ -115,29 +116,29 @@ class Router<T> {
|
||||||
/// Creates a visual representation of the route hierarchy and
|
/// Creates a visual representation of the route hierarchy and
|
||||||
/// passes it to a callback. If none is provided, `print` is called.
|
/// passes it to a callback. If none is provided, `print` is called.
|
||||||
void dumpTree(
|
void dumpTree(
|
||||||
{callback(String tree)?,
|
{Function(String tree)? callback,
|
||||||
String header = 'Dumping route tree:',
|
String header = 'Dumping route tree:',
|
||||||
String tab = ' '}) {
|
String tab = ' '}) {
|
||||||
final buf = StringBuffer();
|
final buf = StringBuffer();
|
||||||
int tabs = 0;
|
var tabs = 0;
|
||||||
|
|
||||||
if (header != null && header.isNotEmpty) {
|
if (header.isNotEmpty) {
|
||||||
buf.writeln(header);
|
buf.writeln(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.writeln('<root>');
|
buf.writeln('<root>');
|
||||||
|
|
||||||
indent() {
|
void indent() {
|
||||||
for (int i = 0; i < tabs; i++) {
|
for (var i = 0; i < tabs; i++) {
|
||||||
buf.write(tab);
|
buf.write(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dumpRouter(Router router) {
|
void dumpRouter(Router router) {
|
||||||
indent();
|
indent();
|
||||||
tabs++;
|
tabs++;
|
||||||
|
|
||||||
for (Route route in router.routes) {
|
for (var route in router.routes) {
|
||||||
indent();
|
indent();
|
||||||
buf.write('- ');
|
buf.write('- ');
|
||||||
if (route is! SymlinkRoute) buf.write('${route.method} ');
|
if (route is! SymlinkRoute) buf.write('${route.method} ');
|
||||||
|
@ -147,7 +148,7 @@ class Router<T> {
|
||||||
buf.writeln();
|
buf.writeln();
|
||||||
dumpRouter(route.router);
|
dumpRouter(route.router);
|
||||||
} else {
|
} else {
|
||||||
buf.writeln(' => ${route.handlers!.length} handler(s)');
|
buf.writeln(' => ${route.handlers.length} handler(s)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,9 +165,8 @@ class Router<T> {
|
||||||
///
|
///
|
||||||
/// Returns the created route.
|
/// Returns the created route.
|
||||||
/// You can also register middleware within the router.
|
/// You can also register middleware within the router.
|
||||||
SymlinkRoute<T> group(String path, void callback(Router<T> router),
|
SymlinkRoute<T> group(String path, void Function(Router<T> router) callback,
|
||||||
{Iterable<T>? middleware, String? name}) {
|
{Iterable<T> middleware = const Iterable.empty(), String name = ''}) {
|
||||||
middleware ??= <T>[];
|
|
||||||
final router = Router<T>().._middleware.addAll(middleware);
|
final router = Router<T>().._middleware.addAll(middleware);
|
||||||
callback(router);
|
callback(router);
|
||||||
return mount(path, router)..name = name;
|
return mount(path, router)..name = name;
|
||||||
|
@ -174,9 +174,9 @@ class Router<T> {
|
||||||
|
|
||||||
/// Asynchronous equivalent of [group].
|
/// Asynchronous equivalent of [group].
|
||||||
Future<SymlinkRoute<T>> groupAsync(
|
Future<SymlinkRoute<T>> groupAsync(
|
||||||
String path, FutureOr<void> callback(Router<T> router),
|
String path, FutureOr<void> Function(Router<T> router) callback,
|
||||||
{Iterable<T>? middleware, String? name}) async {
|
{Iterable<T> middleware = const Iterable.empty(),
|
||||||
middleware ??= <T>[];
|
String name = ''}) async {
|
||||||
final router = Router<T>().._middleware.addAll(middleware);
|
final router = Router<T>().._middleware.addAll(middleware);
|
||||||
await callback(router);
|
await callback(router);
|
||||||
return mount(path, router)..name = name;
|
return mount(path, router)..name = name;
|
||||||
|
@ -210,16 +210,16 @@ class Router<T> {
|
||||||
/// router.navigate(['users/:id', {'id': '1337'}, 'profile']);
|
/// router.navigate(['users/:id', {'id': '1337'}, 'profile']);
|
||||||
/// ```
|
/// ```
|
||||||
String navigate(Iterable linkParams, {bool absolute = true}) {
|
String navigate(Iterable linkParams, {bool absolute = true}) {
|
||||||
final List<String> segments = [];
|
final segments = <String>[];
|
||||||
Router search = this;
|
Router search = this;
|
||||||
Route? lastRoute;
|
Route? lastRoute;
|
||||||
|
|
||||||
for (final param in linkParams) {
|
for (final param in linkParams) {
|
||||||
bool resolved = false;
|
var resolved = false;
|
||||||
|
|
||||||
if (param is String) {
|
if (param is String) {
|
||||||
// Search by name
|
// Search by name
|
||||||
for (Route route in search.routes) {
|
for (var route in search.routes) {
|
||||||
if (route.name == param) {
|
if (route.name == param) {
|
||||||
segments.add(route.path.replaceAll(_straySlashes, ''));
|
segments.add(route.path.replaceAll(_straySlashes, ''));
|
||||||
lastRoute = route;
|
lastRoute = route;
|
||||||
|
@ -236,9 +236,11 @@ class Router<T> {
|
||||||
// Search by path
|
// Search by path
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
var scanner = SpanScanner(param.replaceAll(_straySlashes, ''));
|
var scanner = SpanScanner(param.replaceAll(_straySlashes, ''));
|
||||||
for (Route route in search.routes) {
|
for (var route in search.routes) {
|
||||||
int pos = scanner.position;
|
var pos = scanner.position;
|
||||||
if (route.parser!.parse(scanner)!.successful && scanner.isDone) {
|
var parseResult = route.parser?.parse(scanner);
|
||||||
|
if (parseResult != null) {
|
||||||
|
if (parseResult.successful && scanner.isDone) {
|
||||||
segments.add(route.path.replaceAll(_straySlashes, ''));
|
segments.add(route.path.replaceAll(_straySlashes, ''));
|
||||||
lastRoute = route;
|
lastRoute = route;
|
||||||
|
|
||||||
|
@ -251,6 +253,9 @@ class Router<T> {
|
||||||
} else {
|
} else {
|
||||||
scanner.position = pos;
|
scanner.position = pos;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
scanner.position = pos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,39 +286,44 @@ class Router<T> {
|
||||||
|
|
||||||
/// Finds the first [Route] that matches the given path,
|
/// Finds the first [Route] that matches the given path,
|
||||||
/// with the given method.
|
/// 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}) {
|
{String method = 'GET', bool strip = true}) {
|
||||||
final cleanRelative =
|
final cleanRelative =
|
||||||
strip == false ? relative! : stripStraySlashes(relative!);
|
strip == false ? relative : stripStraySlashes(relative);
|
||||||
var scanner = SpanScanner(cleanRelative);
|
var scanner = SpanScanner(cleanRelative);
|
||||||
|
|
||||||
bool crawl(Router<T> r) {
|
bool crawl(Router<T> r) {
|
||||||
bool success = false;
|
var success = false;
|
||||||
|
|
||||||
for (var route in r.routes) {
|
for (var route in r.routes) {
|
||||||
int pos = scanner.position;
|
var pos = scanner.position;
|
||||||
|
|
||||||
if (route is SymlinkRoute<T>) {
|
if (route is SymlinkRoute<T>) {
|
||||||
if (route.parser!.parse(scanner)!.successful) {
|
if (route.parser != null) {
|
||||||
|
var pp = route.parser!;
|
||||||
|
if (pp.parse(scanner).successful) {
|
||||||
var s = crawl(route.router);
|
var s = crawl(route.router);
|
||||||
if (s) success = true;
|
if (s) success = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scanner.position = pos;
|
scanner.position = pos;
|
||||||
} else if (route.method == '*' || route.method == method) {
|
} else if (route.method == '*' || route.method == method) {
|
||||||
var parseResult = route.parser!.parse(scanner)!;
|
var parseResult = route.parser?.parse(scanner);
|
||||||
|
if (parseResult != null) {
|
||||||
if (parseResult.successful && scanner.isDone) {
|
if (parseResult.successful && scanner.isDone) {
|
||||||
|
var tailResult = parseResult.value?.tail ?? '';
|
||||||
|
print(tailResult);
|
||||||
var result = RoutingResult<T>(
|
var result = RoutingResult<T>(
|
||||||
parseResult: parseResult,
|
parseResult: parseResult,
|
||||||
params: parseResult.value!.params,
|
params: parseResult.value!.params,
|
||||||
shallowRoute: route,
|
shallowRoute: route,
|
||||||
shallowRouter: this,
|
shallowRouter: this,
|
||||||
tail: (parseResult.value!.tail ?? '') + scanner.rest);
|
tail: tailResult + scanner.rest);
|
||||||
out.add(result);
|
out.add(result);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
scanner.position = pos;
|
scanner.position = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,13 +336,13 @@ class Router<T> {
|
||||||
|
|
||||||
/// Returns the result of [resolve] with [path] passed as
|
/// Returns the result of [resolve] with [path] passed as
|
||||||
/// both `absolute` and `relative`.
|
/// both `absolute` and `relative`.
|
||||||
Iterable<RoutingResult<T>> resolveAbsolute(String? path,
|
Iterable<RoutingResult<T>> resolveAbsolute(String path,
|
||||||
{String method = 'GET', bool strip = true}) =>
|
{String method = 'GET', bool strip = true}) =>
|
||||||
resolveAll(path, path, method: method, strip: strip);
|
resolveAll(path, path, method: method, strip: strip);
|
||||||
|
|
||||||
/// Finds every possible [Route] that matches the given path,
|
/// Finds every possible [Route] that matches the given path,
|
||||||
/// with the given method.
|
/// 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}) {
|
{String method = 'GET', bool strip = true}) {
|
||||||
if (_useCache == true) {
|
if (_useCache == true) {
|
||||||
return _cache.putIfAbsent('$method$absolute',
|
return _cache.putIfAbsent('$method$absolute',
|
||||||
|
@ -342,7 +352,7 @@ class Router<T> {
|
||||||
return _resolveAll(absolute, relative, method: method, strip: strip);
|
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}) {
|
{String method = 'GET', bool strip = true}) {
|
||||||
var results = <RoutingResult<T>>[];
|
var results = <RoutingResult<T>>[];
|
||||||
resolve(absolute, relative, results, method: method, strip: strip);
|
resolve(absolute, relative, results, method: method, strip: strip);
|
||||||
|
@ -363,88 +373,95 @@ class Router<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to any request matching the given path.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('*', path, handler, middleware: middleware);
|
return addRoute('*', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a DELETE request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('DELETE', path, handler, middleware: middleware);
|
return addRoute('DELETE', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a GET request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('GET', path, handler, middleware: middleware);
|
return addRoute('GET', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a HEAD request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('HEAD', path, handler, middleware: middleware);
|
return addRoute('HEAD', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a OPTIONS request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('OPTIONS', path, handler, middleware: middleware);
|
return addRoute('OPTIONS', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a POST request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('POST', path, handler, middleware: middleware);
|
return addRoute('POST', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a PATCH request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('PATCH', path, handler, middleware: middleware);
|
return addRoute('PATCH', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a PUT request.
|
/// 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 = const Iterable.empty()}) {
|
||||||
return addRoute('PUT', path, handler, middleware: middleware);
|
return addRoute('PUT', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChainedRouter<T> extends Router<T> {
|
class _ChainedRouter<T> extends Router<T> {
|
||||||
final List<T> _handlers = <T>[];
|
final List<T> _handlers = <T>[];
|
||||||
Router? _root;
|
Router _root;
|
||||||
|
|
||||||
_ChainedRouter.empty();
|
_ChainedRouter.empty() : _root = Router();
|
||||||
|
|
||||||
_ChainedRouter(Router? root, Iterable<T> middleware) {
|
_ChainedRouter(this._root, Iterable<T> middleware) {
|
||||||
this._root = root;
|
|
||||||
_handlers.addAll(middleware);
|
_handlers.addAll(middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Route<T> addRoute(String? method, String path, handler,
|
Route<T> addRoute(String method, String path, handler,
|
||||||
{Iterable<T>? middleware}) {
|
{Iterable<T>? middleware}) {
|
||||||
Route<T> route = super.addRoute(method, path, handler,
|
middleware ??= <T>[];
|
||||||
middleware: []..addAll(_handlers)..addAll(middleware ?? []));
|
var route = super.addRoute(method, path, handler,
|
||||||
|
middleware: [..._handlers, ...middleware]);
|
||||||
//_root._routes.add(route);
|
//_root._routes.add(route);
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SymlinkRoute<T> group(String path, void callback(Router<T> router),
|
SymlinkRoute<T> group(String path, void Function(Router<T> router) callback,
|
||||||
{Iterable<T>? middleware, String? name}) {
|
{Iterable<T> middleware = const Iterable.empty(), String name = ''}) {
|
||||||
final router = _ChainedRouter<T>(
|
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
|
||||||
_root, []..addAll(_handlers)..addAll(middleware ?? []));
|
|
||||||
callback(router);
|
callback(router);
|
||||||
return mount(path, router)..name = name;
|
return mount(path, router)..name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SymlinkRoute<T>> groupAsync(
|
Future<SymlinkRoute<T>> groupAsync(
|
||||||
String path, FutureOr<void> callback(Router<T> router),
|
String path, FutureOr<void> Function(Router<T> router) callback,
|
||||||
{Iterable<T>? middleware, String? name}) async {
|
{Iterable<T> middleware = const Iterable.empty(),
|
||||||
final router = _ChainedRouter<T>(
|
String name = ''}) async {
|
||||||
_root, []..addAll(_handlers)..addAll(middleware ?? []));
|
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
|
||||||
await callback(router);
|
await callback(router);
|
||||||
return mount(path, router)..name = name;
|
return mount(path, router)..name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SymlinkRoute<T> mount(String path, Router<T> router) {
|
SymlinkRoute<T> mount(String path, Router<T> router) {
|
||||||
final SymlinkRoute<T> route = super.mount(path, router);
|
final route = super.mount(path, router);
|
||||||
route.router._middleware.insertAll(0, _handlers);
|
route.router._middleware.insertAll(0, _handlers);
|
||||||
//_root._routes.add(route);
|
//_root._routes.add(route);
|
||||||
return route;
|
return route;
|
||||||
|
@ -453,7 +470,7 @@ class _ChainedRouter<T> extends Router<T> {
|
||||||
@override
|
@override
|
||||||
_ChainedRouter<T> chain(Iterable<T> middleware) {
|
_ChainedRouter<T> chain(Iterable<T> middleware) {
|
||||||
final piped = _ChainedRouter<T>.empty().._root = _root;
|
final piped = _ChainedRouter<T>.empty().._root = _root;
|
||||||
piped._handlers.addAll([]..addAll(_handlers)..addAll(middleware));
|
piped._handlers.addAll([..._handlers, ...middleware]);
|
||||||
var route = SymlinkRoute<T>('/', piped);
|
var route = SymlinkRoute<T>('/', piped);
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
return piped;
|
return piped;
|
||||||
|
@ -473,13 +490,13 @@ Router<T> flatten<T>(Router<T> router) {
|
||||||
var path = route.path.replaceAll(_straySlashes, '');
|
var path = route.path.replaceAll(_straySlashes, '');
|
||||||
var joined = '$base/$path'.replaceAll(_straySlashes, '');
|
var joined = '$base/$path'.replaceAll(_straySlashes, '');
|
||||||
flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''),
|
flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''),
|
||||||
route.handlers!.last,
|
route.handlers.last,
|
||||||
middleware:
|
middleware:
|
||||||
route.handlers!.take(route.handlers!.length - 1).toList());
|
route.handlers.take(route.handlers.length - 1).toList());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
flattened.addRoute(route.method, route.path, route.handlers!.last,
|
flattened.addRoute(route.method, route.path, route.handlers.last,
|
||||||
middleware: route.handlers!.take(route.handlers!.length - 1).toList());
|
middleware: route.handlers.take(route.handlers.length - 1).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,23 +3,23 @@ part of angel_route.src.router;
|
||||||
/// Represents a complex result of navigating to a path.
|
/// Represents a complex result of navigating to a path.
|
||||||
class RoutingResult<T> {
|
class RoutingResult<T> {
|
||||||
/// The parse result that matched the given sub-path.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// The [Route] that answered this sub-path.
|
||||||
///
|
///
|
||||||
/// This is mostly for internal use, and useless in production.
|
/// 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.
|
/// The [Router] that answered this sub-path.
|
||||||
///
|
///
|
||||||
/// Only really for internal use.
|
/// 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.
|
/// The remainder of the full path that was not matched, and was passed to [nested] routes.
|
||||||
final String tail;
|
final String tail;
|
||||||
|
@ -28,22 +28,22 @@ class RoutingResult<T> {
|
||||||
RoutingResult<T> get deepest {
|
RoutingResult<T> get deepest {
|
||||||
var search = this;
|
var search = this;
|
||||||
|
|
||||||
while (search.nested?.isNotEmpty == true) {
|
while (search.nested.isNotEmpty == true) {
|
||||||
search = search.nested!.first;
|
search = search.nested.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The most specific route.
|
/// The most specific route.
|
||||||
Route<T>? get route => deepest.shallowRoute;
|
Route<T> get route => deepest.shallowRoute;
|
||||||
|
|
||||||
/// The most specific router.
|
/// The most specific router.
|
||||||
Router<T>? get router => deepest.shallowRouter;
|
Router<T> get router => deepest.shallowRouter;
|
||||||
|
|
||||||
/// The handlers at this sub-path.
|
/// The handlers at this sub-path.
|
||||||
List<T> get handlers {
|
List<T> get handlers {
|
||||||
return <T>[...shallowRouter!.middleware, ...shallowRoute!.handlers!];
|
return <T>[...shallowRouter.middleware, ...shallowRoute.handlers];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All handlers on this sub-path and its children.
|
/// All handlers on this sub-path and its children.
|
||||||
|
@ -53,8 +53,8 @@ class RoutingResult<T> {
|
||||||
void crawl(RoutingResult<T> result) {
|
void crawl(RoutingResult<T> result) {
|
||||||
handlers.addAll(result.handlers);
|
handlers.addAll(result.handlers);
|
||||||
|
|
||||||
if (result.nested?.isNotEmpty == true) {
|
if (result.nested.isNotEmpty == true) {
|
||||||
for (var r in result.nested!) {
|
for (var r in result.nested) {
|
||||||
crawl(r);
|
crawl(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,14 +66,14 @@ class RoutingResult<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All parameters on this sub-path and its children.
|
/// All parameters on this sub-path and its children.
|
||||||
Map<String?, dynamic> get allParams {
|
Map<String, dynamic> get allParams {
|
||||||
final params = <String?, dynamic>{};
|
final params = <String, dynamic>{};
|
||||||
|
|
||||||
void crawl(RoutingResult result) {
|
void crawl(RoutingResult result) {
|
||||||
params.addAll(result.params);
|
params.addAll(result.params);
|
||||||
|
|
||||||
if (result.nested?.isNotEmpty == true) {
|
if (result.nested.isNotEmpty == true) {
|
||||||
for (var r in result.nested!) {
|
for (var r in result.nested) {
|
||||||
crawl(r);
|
crawl(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,11 +84,11 @@ class RoutingResult<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
RoutingResult(
|
RoutingResult(
|
||||||
{this.parseResult,
|
{required this.parseResult,
|
||||||
Map<String?, dynamic> params = const {},
|
Map<String, dynamic> params = const {},
|
||||||
this.nested,
|
this.nested = const Iterable.empty(),
|
||||||
this.shallowRoute,
|
required this.shallowRoute,
|
||||||
this.shallowRouter,
|
required this.shallowRouter,
|
||||||
required this.tail}) {
|
required this.tail}) {
|
||||||
this.params.addAll(params);
|
this.params.addAll(params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ part of angel_route.src.router;
|
||||||
class SymlinkRoute<T> extends Route<T> {
|
class SymlinkRoute<T> extends Route<T> {
|
||||||
final Router<T> router;
|
final Router<T> router;
|
||||||
SymlinkRoute(String path, this.router)
|
SymlinkRoute(String path, this.router)
|
||||||
: super(path, method: null, handlers: null);
|
: super(path, method: 'GET', handlers: <T>[]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ String stripStray(String haystack, String needle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find last leading index of slash
|
// Find last leading index of slash
|
||||||
for (int i = firstSlash + 1; i < haystack.length; i++) {
|
for (var i = firstSlash + 1; i < haystack.length; i++) {
|
||||||
if (haystack[i] != needle) {
|
if (haystack[i] != needle) {
|
||||||
var sub = haystack.substring(i);
|
var sub = haystack.substring(i);
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ String stripStray(String haystack, String needle) {
|
||||||
|
|
||||||
var lastSlash = sub.lastIndexOf(needle);
|
var lastSlash = sub.lastIndexOf(needle);
|
||||||
|
|
||||||
for (int j = lastSlash - 1; j >= 0; j--) {
|
for (var j = lastSlash - 1; j >= 0; j--) {
|
||||||
if (sub[j] != needle) {
|
if (sub[j] != needle) {
|
||||||
return sub.substring(0, j + 1);
|
return sub.substring(0, j + 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ void main() {
|
||||||
..dumpTree();
|
..dumpTree();
|
||||||
|
|
||||||
test('nested route groups with chain', () {
|
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, isNotNull);
|
||||||
expect(r.handlers, hasLength(4));
|
expect(r.handlers, hasLength(4));
|
||||||
expect(r.handlers, equals(['a', 'c', 'd', 'g']));
|
expect(r.handlers, equals(['a', 'c', 'd', 'g']));
|
||||||
|
|
|
@ -8,7 +8,7 @@ void main() {
|
||||||
router.get('/user/:id', 'GET');
|
router.get('/user/:id', 'GET');
|
||||||
router.get('/first/:first/last/:last', 'GET').name = 'full_name';
|
router.get('/first/:first/last/:last', 'GET').name = 'full_name';
|
||||||
|
|
||||||
navigate(params) {
|
String navigate(params) {
|
||||||
final uri = router.navigate(params as Iterable);
|
final uri = router.navigate(params as Iterable);
|
||||||
print('Uri: $uri');
|
print('Uri: $uri');
|
||||||
return uri;
|
return uri;
|
||||||
|
|
|
@ -26,21 +26,21 @@ void main() {
|
||||||
test('tail explicitly set intermediate', () {
|
test('tail explicitly set intermediate', () {
|
||||||
var results = router.resolveAbsolute('/songs/in_the/key');
|
var results = router.resolveAbsolute('/songs/in_the/key');
|
||||||
var result = results.first;
|
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');
|
expect(result.tail, 'in_the');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tail explicitly set at end', () {
|
test('tail explicitly set at end', () {
|
||||||
var results = router.resolveAbsolute('/isnt/she/epic');
|
var results = router.resolveAbsolute('/isnt/she/epic');
|
||||||
var result = results.first;
|
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');
|
expect(result.tail, 'epic');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tail with trailing', () {
|
test('tail with trailing', () {
|
||||||
var results = router.resolveAbsolute('/isnt/she/epic/fail');
|
var results = router.resolveAbsolute('/isnt/she/epic/fail');
|
||||||
var result = results.first;
|
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');
|
expect(result.tail, 'epic/fail');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,30 @@
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
import 'package:angel_route/browser.dart';
|
import 'package:angel_route/browser.dart';
|
||||||
|
|
||||||
basic(BrowserRouter router) {
|
void basic(BrowserRouter router) {
|
||||||
final $h1 = window.document.querySelector('h1');
|
final $h1 = window.document.querySelector('h1');
|
||||||
final $ul = window.document.getElementById('handlers');
|
final $ul = window.document.getElementById('handlers');
|
||||||
|
|
||||||
router.onResolve.listen((result) {
|
router.onResolve.listen((result) {
|
||||||
final route = result?.route;
|
final route = result.route;
|
||||||
|
|
||||||
if (route == null) {
|
// TODO: Relook at this logic
|
||||||
$h1!.text = 'No Active Route';
|
//if (route == null) {
|
||||||
$ul!.children
|
// $h1!.text = 'No Active Route';
|
||||||
|
// $ul!.children
|
||||||
|
// ..clear()
|
||||||
|
// ..add(LIElement()..text = '(empty)');
|
||||||
|
//} else {
|
||||||
|
if ($h1 != null && $ul != null) {
|
||||||
|
$h1.text = 'Active Route: ${route.name}';
|
||||||
|
$ul.children
|
||||||
..clear()
|
..clear()
|
||||||
..add(LIElement()..text = '(empty)');
|
..addAll(result.allHandlers
|
||||||
} else {
|
|
||||||
$h1!.text = 'Active Route: ${route.name ?? route.path}';
|
|
||||||
$ul!.children
|
|
||||||
..clear()
|
|
||||||
..addAll(result!.allHandlers
|
|
||||||
.map((handler) => LIElement()..text = handler.toString()));
|
.map((handler) => LIElement()..text = handler.toString()));
|
||||||
|
} else {
|
||||||
|
print('No active Route');
|
||||||
}
|
}
|
||||||
|
//}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('a', 'a handler');
|
router.get('a', 'a handler');
|
||||||
|
|
Loading…
Reference in a new issue