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