Updated angel framework to NNBD

This commit is contained in:
thomashii 2021-03-20 16:11:18 +08:00
parent 0fe0a5a56b
commit fdf1532074
58 changed files with 860 additions and 812 deletions

View file

@ -8,9 +8,11 @@
* Updated angel_route to 5.0.0
* Updated angel_model to 3.0.0
* Updated angel_container to 3.0.0
* Updated angel_framework to 4.0.0
- merge_map
- mock_request
* Added merge_map and updated to 2.0.0
* Added mock_request and updated to 2.0.0
* Updated angel_framework to 4.0.0 (Revisit TODO)
* Updated angel_auth to 4.0.0 (todo)
* Updated angel_configuration to 4.0.0 (todo)
# 3.0.0 (Non NNBD)
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"

View file

@ -10,17 +10,17 @@ dependencies:
angel_framework:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
ref: sdk-2.12.x_nnbd
path: packages/framework
charcode: ^1.0.0
collection: ^1.0.0
charcode: ^1.2.0
collection: ^1.15.0
crypto: ^3.0.0
http_parser: ^4.0.0
meta: ^1.0.0
quiver_hashcode: ^2.0.0
meta: ^1.3.0
quiver_hashcode: ^3.0.0+1
dev_dependencies:
http: ^0.13.0
http: ^0.13.1
io: ^1.0.0
logging: ^1.0.0
pedantic: ^1.0.0
test: ^1.15.7
pedantic: ^1.11.0
test: ^1.16.8

View file

@ -16,7 +16,7 @@ main() async {
// Simple fallback to throw a 404 on unknown paths.
app.fallback((req, res) {
throw AngelHttpException.notFound(
message: 'Unknown path: "${req.uri.path}"',
message: 'Unknown path: "${req.uri!.path}"',
);
});
@ -40,7 +40,7 @@ class ArtistsController extends Controller {
form(RequestContext req) async {
// Deserialize the body into an artist.
var artist = await req.deserializeBody((m) {
return Artist(name: m['name'] as String ?? '(unknown name)');
return Artist(name: m!['name'] as String? ?? '(unknown name)');
});
// Return it (it will be serialized to JSON).
@ -49,7 +49,7 @@ class ArtistsController extends Controller {
}
class Artist {
final String name;
final String? name;
Artist({this.name});

View file

@ -19,7 +19,7 @@ main() async {
(req, res) => Future.error('Throwing just because I feel like!'));
var http = AngelHttp(app);
var server = await http.startServer('127.0.0.1', 3000);
HttpServer server = await http.startServer('127.0.0.1', 3000);
var url = 'http://${server.address.address}:${server.port}';
print('Listening at $url');
}

View file

@ -29,7 +29,7 @@ main() async {
try {
ctx.setAlpnProtocols(['h2'], true);
} catch (e, st) {
app.logger.severe(
app.logger!.severe(
'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
e,
st);
@ -41,6 +41,6 @@ main() async {
// HTTP/1.x requests will fallback to `AngelHttp`
http2.onHttp1.listen(http1.handleRequest);
var server = await http2.startServer('127.0.0.1', 3000);
SecureServerSocket server = await http2.startServer('127.0.0.1', 3000);
print('Listening at https://${server.address.address}:${server.port}');
}

View file

@ -25,7 +25,7 @@ main() async {
try {
ctx.setAlpnProtocols(['h2'], true);
} catch (e, st) {
app.logger.severe(
app.logger!.severe(
'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
e,
st,

View file

@ -45,7 +45,7 @@ main() async {
try {
ctx.setAlpnProtocols(['h2'], true);
} catch (e, st) {
app.logger.severe(
app.logger!.severe(
'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
e,
st);
@ -57,6 +57,6 @@ main() async {
// HTTP/1.x requests will fallback to `AngelHttp`
http2.onHttp1.listen(http1.handleRequest);
var server = await http2.startServer('127.0.0.1', 3000);
SecureServerSocket server = await http2.startServer('127.0.0.1', 3000);
print('Listening at https://${server.address.address}:${server.port}');
}

View file

@ -48,6 +48,6 @@ serverMain(_) async {
print(e.stackTrace);
};
var server = await http.startServer('127.0.0.1', 3000);
HttpServer server = await http.startServer('127.0.0.1', 3000);
print('Listening at http://${server.address.address}:${server.port}');
}

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
@ -42,12 +44,12 @@ main() async {
// Simple fallback to throw a 404 on unknown paths.
app.fallback((req, res) {
throw AngelHttpException.notFound(
message: 'Unknown path: "${req.uri.path}"',
message: 'Unknown path: "${req.uri!.path}"',
);
});
var http = AngelHttp(app);
var server = await http.startServer('127.0.0.1', 3000);
HttpServer server = await http.startServer('127.0.0.1', 3000);
var url = 'http://${server.address.address}:${server.port}';
print('Listening at $url');
print('Visit these pages to see Angel in action:');

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
@ -12,7 +14,7 @@ main() async {
app.get('/', (req, res) => res.render('index', {'foo': 'bar'}));
var http = AngelHttp(app);
var server = await http.startServer('127.0.0.1', 3000);
HttpServer server = await http.startServer('127.0.0.1', 3000);
var url = 'http://${server.address.address}:${server.port}';
print('Listening at $url');
}

View file

@ -7,19 +7,19 @@ import 'service.dart';
///
/// Well-suited for testing.
class AnonymousService<Id, Data> extends Service<Id, Data> {
FutureOr<List<Data>> Function([Map<String, dynamic>]) _index;
FutureOr<Data> Function(Id, [Map<String, dynamic>]) _read, _remove;
FutureOr<Data> Function(Data, [Map<String, dynamic>]) _create;
FutureOr<Data> Function(Id, Data, [Map<String, dynamic>]) _modify, _update;
FutureOr<List<Data>>? Function([Map<String, dynamic>?])? _index;
FutureOr<Data> Function(Id?, [Map<String, dynamic>?])? _read, _remove;
FutureOr<Data> Function(Data, [Map<String, dynamic>?])? _create;
FutureOr<Data> Function(Id?, Data, [Map<String, dynamic>?])? _modify, _update;
AnonymousService(
{FutureOr<List<Data>> index([Map<String, dynamic> params]),
FutureOr<Data> read(Id id, [Map<String, dynamic> params]),
FutureOr<Data> create(Data data, [Map<String, dynamic> params]),
FutureOr<Data> modify(Id id, Data data, [Map<String, dynamic> params]),
FutureOr<Data> update(Id id, Data data, [Map<String, dynamic> params]),
FutureOr<Data> remove(Id id, [Map<String, dynamic> params]),
FutureOr<Data> Function(RequestContext, ResponseContext) readData})
{FutureOr<List<Data>>? index([Map<String, dynamic>? params])?,
FutureOr<Data> read(Id? id, [Map<String, dynamic>? params])?,
FutureOr<Data> create(Data data, [Map<String, dynamic>? params])?,
FutureOr<Data> modify(Id? id, Data data, [Map<String, dynamic>? params])?,
FutureOr<Data> update(Id? id, Data data, [Map<String, dynamic>? params])?,
FutureOr<Data> remove(Id? id, [Map<String, dynamic>? params])?,
FutureOr<Data> Function(RequestContext, ResponseContext?)? readData})
: super(readData: readData) {
_index = index;
_read = read;
@ -30,30 +30,30 @@ class AnonymousService<Id, Data> extends Service<Id, Data> {
}
@override
index([Map<String, dynamic> params]) =>
Future.sync(() => _index != null ? _index(params) : super.index(params));
index([Map<String, dynamic>? params]) =>
Future.sync(() => _index != null ? _index!(params)! : super.index(params));
@override
read(Id id, [Map<String, dynamic> params]) => Future.sync(
() => _read != null ? _read(id, params) : super.read(id, params));
read(Id? id, [Map<String, dynamic>? params]) => Future.sync(
() => _read != null ? _read!(id, params) : super.read(id, params));
@override
create(Data data, [Map<String, dynamic> params]) => Future.sync(() =>
_create != null ? _create(data, params) : super.create(data, params));
create(Data data, [Map<String, dynamic>? params]) => Future.sync(() =>
_create != null ? _create!(data, params) : super.create(data, params));
@override
modify(Id id, Data data, [Map<String, dynamic> params]) =>
modify(Id? id, Data data, [Map<String, dynamic>? params]) =>
Future.sync(() => _modify != null
? _modify(id, data, params)
? _modify!(id, data, params)
: super.modify(id, data, params));
@override
update(Id id, Data data, [Map<String, dynamic> params]) =>
update(Id? id, Data data, [Map<String, dynamic>? params]) =>
Future.sync(() => _update != null
? _update(id, data, params)
? _update!(id, data, params)
: super.update(id, data, params));
@override
remove(Id id, [Map<String, dynamic> params]) => Future.sync(
() => _remove != null ? _remove(id, params) : super.remove(id, params));
remove(Id? id, [Map<String, dynamic>? params]) => Future.sync(
() => _remove != null ? _remove!(id, params) : super.remove(id, params));
}

View file

@ -9,10 +9,10 @@ import '../core/core.dart';
/// Supports grouping routes with shared functionality.
class Controller {
Angel _app;
Angel? _app;
/// The [Angel] application powering this controller.
Angel get app => _app;
Angel? get app => _app;
/// If `true` (default), this class will inject itself as a singleton into the [app]'s container when bootstrapped.
final bool injectSingleton;
@ -21,12 +21,12 @@ class Controller {
List<RequestHandler> middleware = [];
/// A mapping of route paths to routes, produced from the [Expose] annotations on this class.
Map<String, Route> routeMappings = {};
Map<String?, Route> routeMappings = {};
SymlinkRoute<RequestHandler> _mountPoint;
SymlinkRoute<RequestHandler?>? _mountPoint;
/// The route at which this controller is mounted on the server.
SymlinkRoute<RequestHandler> get mountPoint => _mountPoint;
SymlinkRoute<RequestHandler?>? get mountPoint => _mountPoint;
Controller({this.injectSingleton = true});
@ -36,35 +36,35 @@ class Controller {
_app = app;
if (injectSingleton != false) {
if (!app.container.has(runtimeType)) {
_app.container.registerSingleton(this, as: runtimeType);
if (!app.container!.has(runtimeType)) {
_app!.container!.registerSingleton(this, as: runtimeType);
}
}
var name = await applyRoutes(app, app.container.reflector);
var name = await applyRoutes(app, app.container!.reflector);
app.controllers[name] = this;
return null;
}
/// Applies the routes from this [Controller] to some [router].
Future<String> applyRoutes(
Router<RequestHandler> router, Reflector reflector) async {
Future<String?> applyRoutes(
Router<RequestHandler?> router, Reflector reflector) async {
// Load global expose decl
var classMirror = reflector.reflectClass(this.runtimeType);
Expose exposeDecl = findExpose(reflector);
var classMirror = reflector.reflectClass(this.runtimeType)!;
Expose? exposeDecl = findExpose(reflector);
if (exposeDecl == null) {
throw Exception("All controllers must carry an @Expose() declaration.");
}
var routable = Routable();
_mountPoint = router.mount(exposeDecl.path, routable);
_mountPoint = router.mount(exposeDecl.path!, routable);
var typeMirror = reflector.reflectType(this.runtimeType);
// Pre-reflect methods
var instanceMirror = reflector.reflectInstance(this);
final handlers = <RequestHandler>[]
..addAll(exposeDecl.middleware)
..addAll(exposeDecl.middleware!)
..addAll(middleware);
final routeBuilder =
_routeBuilder(reflector, instanceMirror, routable, handlers);
@ -72,12 +72,12 @@ class Controller {
classMirror.declarations.forEach(routeBuilder);
// Return the name.
return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror.name;
return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name;
}
void Function(ReflectedDeclaration) _routeBuilder(
Reflector reflector,
ReflectedInstance instanceMirror,
ReflectedInstance? instanceMirror,
Routable routable,
Iterable<RequestHandler> handlers) {
return (ReflectedDeclaration decl) {
@ -89,13 +89,13 @@ class Controller {
methodName != 'call' &&
methodName != 'equals' &&
methodName != '==') {
var exposeDecl = decl.function.annotations
var exposeDecl = decl.function!.annotations
.map((m) => m.reflectee)
.firstWhere((r) => r is Expose, orElse: () => null) as Expose;
.firstWhere((r) => r is Expose, orElse: () => null) as Expose?;
if (exposeDecl == null) {
// If this has a @noExpose, return null.
if (decl.function.annotations.any((m) => m.reflectee is NoExpose)) {
if (decl.function!.annotations.any((m) => m.reflectee is NoExpose)) {
return;
} else {
// Otherwise, create an @Expose.
@ -104,15 +104,15 @@ class Controller {
}
var reflectedMethod =
instanceMirror.getField(methodName).reflectee as Function;
instanceMirror!.getField(methodName).reflectee as Function?;
var middleware = <RequestHandler>[]
..addAll(handlers)
..addAll(exposeDecl.middleware);
String name =
..addAll(exposeDecl.middleware!);
String? name =
exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : methodName;
// Check if normal
var method = decl.function;
var method = decl.function!;
if (method.parameters.length == 2 &&
method.parameters[0].type.reflectedType == RequestContext &&
method.parameters[1].type.reflectedType == ResponseContext) {
@ -120,13 +120,13 @@ class Controller {
routeMappings[name] = routable
.addRoute(exposeDecl.method, exposeDecl.path,
(RequestContext req, ResponseContext res) {
var result = reflectedMethod(req, res);
var result = reflectedMethod!(req, res);
return result is RequestHandler ? result(req, res) : result;
}, middleware: middleware);
return;
}
var injection = preInject(reflectedMethod, reflector);
var injection = preInject(reflectedMethod!, reflector);
if (exposeDecl?.allowNull?.isNotEmpty == true) {
injection.optional?.addAll(exposeDecl.allowNull);
@ -148,7 +148,7 @@ class Controller {
var restPath = ReCase(rest.isEmpty ? 'index' : rest)
.snakeCase
.replaceAll(_rgxMultipleUnderscores, '_');
httpMethod = methodMatch[1].toUpperCase();
httpMethod = methodMatch[1]!.toUpperCase();
if (['index', 'by_id'].contains(restPath)) {
parts.add('/');
@ -215,12 +215,12 @@ class Controller {
///
/// If [concreteOnly] is `false`, then if there is no actual
/// [Expose], one will be automatically created.
Expose findExpose(Reflector reflector, {bool concreteOnly = false}) {
Expose? findExpose(Reflector reflector, {bool concreteOnly = false}) {
var existing = reflector
.reflectClass(runtimeType)
.reflectClass(runtimeType)!
.annotations
.map((m) => m.reflectee)
.firstWhere((r) => r is Expose, orElse: () => null) as Expose;
.firstWhere((r) => r is Expose, orElse: () => null) as Expose?;
return existing ??
(concreteOnly
? null

View file

@ -17,11 +17,11 @@ 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;
StreamSubscription<Request> _sub;
Server? _server;
StreamSubscription<Request>? _sub;
/// The function used to bind this instance to a server..
final Future<Server> Function(dynamic, int) serverGenerator;
@ -32,25 +32,27 @@ 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]) {
var host = address ?? '127.0.0.1';
return generateServer(host, port ?? 0).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) {
return handleRawRequest(request, response);
// TODO: To be revisited
handleRawRequest(request, response);
return;
});
});
return _server;
return _server!;
});
});
}
@ -60,8 +62,9 @@ abstract class Driver<
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((_) => _server!));
}
Future<RequestContextType> createRequestContext(
@ -69,9 +72,9 @@ abstract class Driver<
Future<ResponseContextType> createResponseContext(
Request request, Response response,
[RequestContextType correspondingRequest]);
[RequestContextType? correspondingRequest]);
void setHeader(Response response, String key, String value);
void setHeader(Response response, String key, String? value);
void setContentLength(Response response, int length);
@ -97,42 +100,42 @@ abstract class Driver<
var path = req.path;
if (path == '/') path = '';
Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>,
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);
r.resolveAbsolute(path, method: req.method!, strip: false);
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
return Tuple4(
pipeline.handlers,
resolved.fold<Map<String, dynamic>>(
<String, dynamic>{}, (out, r) => out..addAll(r.allParams)),
resolved.fold<Map<String?, dynamic>>(
<String?, dynamic>{}, (out, r) => out..addAll(r.allParams)),
resolved.isEmpty ? null : resolved.first.parseResult,
pipeline,
);
}
var cacheKey = req.method + path;
var tuple = app.environment.isProduction
? app.handlerCache.putIfAbsent(cacheKey, resolveTuple)
var cacheKey = req.method! + path!;
var tuple = app!.environment.isProduction
? app!.handlerCache.putIfAbsent(cacheKey, resolveTuple)
: resolveTuple();
var line = tuple.item4 as MiddlewarePipeline<RequestHandler>;
var it = MiddlewarePipelineIterator<RequestHandler>(line);
var line = tuple.item4 as MiddlewarePipeline<RequestHandler?>;
var it = MiddlewarePipelineIterator<RequestHandler?>(line);
req.params.addAll(tuple.item2);
req.container
..registerSingleton<RequestContext>(req)
?..registerSingleton<RequestContext>(req)
..registerSingleton<ResponseContext>(res)
..registerSingleton<MiddlewarePipeline>(tuple.item4)
..registerSingleton<MiddlewarePipeline<RequestHandler>>(line)
..registerSingleton<MiddlewarePipeline<RequestHandler?>>(line)
..registerSingleton<MiddlewarePipelineIterator>(it)
..registerSingleton<MiddlewarePipelineIterator<RequestHandler>>(it)
..registerSingleton<ParseResult<RouteResult>>(tuple.item3)
..registerSingleton<ParseResult>(tuple.item3);
..registerSingleton<MiddlewarePipelineIterator<RequestHandler?>>(it)
..registerSingleton<ParseResult<RouteResult?>?>(tuple.item3)
..registerSingleton<ParseResult?>(tuple.item3);
if (!app.environment.isProduction && app.logger != null) {
req.container.registerSingleton<Stopwatch>(Stopwatch()..start());
if (!app!.environment.isProduction && app!.logger != null) {
req.container!.registerSingleton<Stopwatch>(Stopwatch()..start());
}
return runPipeline(it, req, res, app)
@ -161,10 +164,10 @@ abstract class Driver<
(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(
@ -173,16 +176,17 @@ 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);
}
},
handleUncaughtError: (self, parent, zone, error, stackTrace) {
var trace = Trace.from(stackTrace ?? StackTrace.current).terse;
var trace = Trace.from(stackTrace).terse;
return Future(() {
// TODO: To be revisited
Future(() {
AngelHttpException e;
if (error is FormatException) {
@ -191,24 +195,22 @@ abstract class Driver<
e = error;
} else {
e = AngelHttpException(error,
stackTrace: stackTrace,
message:
error?.toString() ?? '500 Internal Server Error');
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(
e, trace, req, res, request, response);
}).catchError((e, StackTrace st) {
var trace = Trace.from(st ?? StackTrace.current).terse;
var trace = Trace.from(st).terse;
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
@ -218,12 +220,13 @@ abstract class Driver<
..writeln(trace);
}
});
return;
},
);
var zone = Zone.current.fork(specification: zoneSpec);
req.container.registerSingleton<Zone>(zone);
req.container.registerSingleton<ZoneSpecification>(zoneSpec);
req.container!.registerSingleton<Zone>(zone);
req.container!.registerSingleton<ZoneSpecification>(zoneSpec);
// If a synchronous error is thrown, it's not caught by `zone.run`,
// so use a try/catch, and recover when need be.
@ -240,17 +243,17 @@ abstract class Driver<
}
/// Handles an [AngelHttpException].
Future handleAngelHttpException(
Future? handleAngelHttpException(
AngelHttpException e,
StackTrace st,
RequestContext req,
ResponseContext res,
RequestContext? req,
ResponseContext? res,
Request request,
Response response,
{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);
@ -266,8 +269,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());
});
}
@ -280,11 +283,11 @@ abstract class Driver<
ResponseContext res,
{bool ignoreFinalizers = false}) {
Future<void> _cleanup(_) {
if (!app.environment.isProduction &&
app.logger != null &&
req.container.has<Stopwatch>()) {
var sw = req.container.make<Stopwatch>();
app.logger.info(
if (!app!.environment.isProduction &&
app!.logger != null &&
req.container!.has<Stopwatch>()) {
var sw = req.container!.make<Stopwatch>();
app!.logger!.info(
"${res.statusCode} ${req.method} ${req.uri} (${sw?.elapsedMilliseconds ?? 'unknown'} ms)");
}
return req.close();
@ -294,7 +297,7 @@ abstract class Driver<
Future finalizers = ignoreFinalizers == true
? Future.value()
: Future.forEach(app.responseFinalizers, (f) => f(req, res));
: Future.forEach(app!.responseFinalizers, (dynamic f) => f(req, res));
return finalizers.then((_) {
//if (res.isOpen) res.close();
@ -303,18 +306,18 @@ abstract class Driver<
setHeader(response, key, res.headers[key]);
}
setContentLength(response, res.buffer.length);
setContentLength(response, res.buffer!.length);
setChunkedEncoding(response, res.chunked ?? true);
List<int> outputBuffer = res.buffer.toBytes();
List<int> outputBuffer = res.buffer!.toBytes();
if (res.encoders.isNotEmpty) {
var allowedEncodings = req.headers
var allowedEncodings = req.headers!
.value('accept-encoding')
?.split(',')
?.map((s) => s.trim())
?.where((s) => s.isNotEmpty)
?.map((str) {
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
@ -323,7 +326,7 @@ abstract class Driver<
if (allowedEncodings != null) {
for (var encodingName in allowedEncodings) {
Converter<List<int>, List<int>> encoder;
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (res.encoders.containsKey(encodingName)) {
@ -334,7 +337,7 @@ abstract class Driver<
if (encoder != null) {
setHeader(response, 'content-encoding', key);
outputBuffer = res.encoders[key].convert(outputBuffer);
outputBuffer = res.encoders[key]!.convert(outputBuffer);
setContentLength(response, outputBuffer.length);
break;
}
@ -352,16 +355,16 @@ abstract class Driver<
/// Runs a [MiddlewarePipeline].
static Future<void> runPipeline<RequestContextType extends RequestContext,
ResponseContextType extends ResponseContext>(
MiddlewarePipelineIterator<RequestHandler> it,
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;

View file

@ -5,7 +5,7 @@ const AngelEnvironment angelEnv = AngelEnvironment();
/// Queries the environment's `ANGEL_ENV` value.
class AngelEnvironment {
final String _customValue;
final String? _customValue;
/// You can optionally provide a custom value, in order to override the system's
/// value.

View file

@ -49,20 +49,20 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
FutureOr<Data> Function(RequestContext, ResponseContext) get readData =>
FutureOr<Data>? Function(RequestContext, ResponseContext?)? get readData =>
inner.readData;
RequestContext _getRequest(Map params) {
RequestContext? _getRequest(Map? params) {
if (params == null) return null;
return params['__requestctx'] as RequestContext;
return params['__requestctx'] as RequestContext?;
}
ResponseContext _getResponse(Map params) {
ResponseContext? _getResponse(Map? params) {
if (params == null) return null;
return params['__responsectx'] as ResponseContext;
return params['__responsectx'] as ResponseContext?;
}
Map<String, dynamic> _stripReq(Map<String, dynamic> params) {
Map<String, dynamic>? _stripReq(Map<String, dynamic>? params) {
if (params == null) {
return params;
} else {
@ -95,7 +95,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
/// Adds hooks to this instance.
void addHooks(Angel app) {
var hooks = getAnnotation<Hooks>(inner, app.container.reflector);
var hooks = getAnnotation<Hooks>(inner, app.container!.reflector);
List<HookedServiceEventListener<Id, Data, T>> before = [], after = [];
if (hooks != null) {
@ -105,8 +105,8 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
void applyListeners(
Function fn, HookedServiceEventDispatcher<Id, Data, T> dispatcher,
[bool isAfter]) {
Hooks hooks = getAnnotation<Hooks>(fn, app.container.reflector);
[bool? isAfter]) {
Hooks? hooks = getAnnotation<Hooks>(fn, app.container!.reflector);
final listeners = <HookedServiceEventListener<Id, Data, T>>[]
..addAll(isAfter == true ? after : before);
@ -140,7 +140,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
return true;
});
void addRoutes([Service s]) {
void addRoutes([Service? s]) {
super.addRoutes(s ?? inner);
}
@ -270,7 +270,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<List<Data>> index([Map<String, dynamic> _params]) {
Future<List<Data>> index([Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeIndexed
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -296,7 +296,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<Data> read(Id id, [Map<String, dynamic> _params]) {
Future<Data> read(Id? id, [Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeRead
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -322,7 +322,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<Data> create(Data data, [Map<String, dynamic> _params]) {
Future<Data> create(Data? data, [Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeCreated
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -337,7 +337,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
.then((after) => after.result as Data);
}
return inner.create(before.data, params).then((result) {
return inner.create(before.data!, params).then((result) {
return afterCreated
._emit(HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.created,
@ -348,7 +348,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<Data> modify(Id id, Data data, [Map<String, dynamic> _params]) {
Future<Data> modify(Id? id, Data? data, [Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeModified
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -366,7 +366,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
.then((after) => after.result as Data);
}
return inner.modify(id, before.data, params).then((result) {
return inner.modify(id, before.data!, params).then((result) {
return afterModified
._emit(HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.created,
@ -377,7 +377,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<Data> update(Id id, Data data, [Map<String, dynamic> _params]) {
Future<Data> update(Id? id, Data? data, [Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeUpdated
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -395,7 +395,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
.then((after) => after.result as Data);
}
return inner.update(id, before.data, params).then((result) {
return inner.update(id, before.data!, params).then((result) {
return afterUpdated
._emit(HookedServiceEvent(true, _getRequest(_params),
_getResponse(_params), inner, HookedServiceEvent.updated,
@ -406,7 +406,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
@override
Future<Data> remove(Id id, [Map<String, dynamic> _params]) {
Future<Data> remove(Id? id, [Map<String, dynamic>? _params]) {
var params = _stripReq(_params);
return beforeRemoved
._emit(HookedServiceEvent(false, _getRequest(_params),
@ -434,7 +434,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
/// Fires an `after` event. This will not be propagated to clients,
/// but will be broadcasted to WebSockets, etc.
Future<HookedServiceEvent<Id, Data, T>> fire(String eventName, result,
[HookedServiceEventListener<Id, Data, T> callback]) {
[HookedServiceEventListener<Id, Data, T>? callback]) {
HookedServiceEventDispatcher<Id, Data, T> dispatcher;
switch (eventName) {
@ -469,8 +469,8 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
Future<HookedServiceEvent<Id, Data, T>> fireEvent(
HookedServiceEventDispatcher<Id, Data, T> dispatcher,
HookedServiceEvent<Id, Data, T> event,
[HookedServiceEventListener<Id, Data, T> callback]) {
Future f;
[HookedServiceEventListener<Id, Data, T>? callback]) {
Future? f;
if (callback != null && event?._canceled != true) {
f = Future.sync(() => callback(event));
}
@ -480,7 +480,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}
/// Fired when a hooked service is invoked.
class HookedServiceEvent<Id, Data, T extends Service<Id, Data>> {
class HookedServiceEvent<Id, Data, T extends Service<Id?, Data?>> {
static const String indexed = 'indexed';
static const String read = 'read';
static const String created = 'created';
@ -506,38 +506,38 @@ class HookedServiceEvent<Id, Data, T extends Service<Id, Data>> {
/// Resolves a service from the application.
///
/// Shorthand for `e.service.app.service(...)`.
Service getService(Pattern path) => service.app.findService(path);
Service? getService(Pattern path) => service.app!.findService(path);
bool _canceled = false;
String _eventName;
Id _id;
Id? _id;
bool _isAfter;
Data data;
Map<String, dynamic> _params;
RequestContext _request;
ResponseContext _response;
Data? data;
Map<String, dynamic>? _params;
RequestContext? _request;
ResponseContext? _response;
var result;
String get eventName => _eventName;
Id get id => _id;
Id? get id => _id;
bool get isAfter => _isAfter == true;
bool get isBefore => !isAfter;
Map get params => _params;
Map? get params => _params;
RequestContext get request => _request;
RequestContext? get request => _request;
ResponseContext get response => _response;
ResponseContext? get response => _response;
/// The inner service whose method was hooked.
T service;
HookedServiceEvent(this._isAfter, this._request, this._response, this.service,
this._eventName,
{Id id, this.data, Map<String, dynamic> params, this.result}) {
{Id? id, this.data, Map<String, dynamic>? params, this.result}) {
_id = id;
_params = params ?? {};
}

View file

@ -17,7 +17,7 @@ class HostnameSyntaxParser {
RegExp parse() {
var b = StringBuffer();
var parts = Queue<String>();
var parts = Queue<String?>();
while (!_scanner.isDone) {
if (_scanner.scan('|')) {
@ -25,7 +25,7 @@ class HostnameSyntaxParser {
throw _formatExc('No hostname parts found before "|".');
} else {
var next = _parseHostnamePart();
if (next == null) {
if (next.isEmpty) {
throw _formatExc('No hostname parts found after "|".');
} else {
var prev = parts.removeLast();
@ -33,11 +33,11 @@ class HostnameSyntaxParser {
}
}
} else {
var part = _parseHostnamePart();
if (part != null) {
String part = _parseHostnamePart();
if (part.isNotEmpty) {
if (_scanner.scan('.')) {
var subPart = _parseHostnamePart(shouldThrow: false);
while (subPart != null) {
while (subPart.isNotEmpty) {
part += '\\.' + subPart;
if (_scanner.scan('.')) {
subPart = _parseHostnamePart(shouldThrow: false);
@ -46,7 +46,6 @@ class HostnameSyntaxParser {
}
}
}
parts.add(part);
}
}
@ -71,12 +70,12 @@ class HostnameSyntaxParser {
} else if (_scanner.scan('+')) {
return r'[^$]+';
} else if (_scanner.scan(_safe)) {
return _scanner.lastMatch[0];
return _scanner.lastMatch?[0] ?? "";
} else if (!_scanner.isDone && shouldThrow) {
var s = String.fromCharCode(_scanner.peekChar());
var s = String.fromCharCode(_scanner.peekChar()!);
throw _formatExc('Unexpected character "$s".');
} else {
return null;
return "";
}
}
}

View file

@ -60,10 +60,10 @@ class HostnameRouter {
Map<Pattern, FutureOr<void> Function(Angel)> configurers,
{Reflector reflector = const EmptyReflector(),
AngelEnvironment environment = angelEnv,
Logger logger,
Logger? logger,
bool allowMethodOverrides = true,
FutureOr<String> Function(dynamic) serializer,
ViewGenerator viewGenerator}) {
FutureOr<String> Function(dynamic)? serializer,
ViewGenerator? viewGenerator}) {
var creators = configurers.map((p, c) {
return MapEntry(p, () async {
var app = Angel(
@ -89,17 +89,17 @@ class HostnameRouter {
if (req.hostname != null) {
for (var pattern in _patterns) {
// print('${req.hostname} vs $_creators');
if (pattern.allMatches(req.hostname).isNotEmpty) {
if (pattern.allMatches(req.hostname!).isNotEmpty) {
// Resolve the entire pipeline within the context of the selected app.
var app = _apps[pattern] ??= (await _creators[pattern]());
var app = _apps[pattern] ??= (await _creators[pattern]!());
// print('App for ${req.hostname} = $app from $pattern');
// app.dumpTree();
var r = app.optimizedRouter;
var resolved = r.resolveAbsolute(req.path, method: req.method);
var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
var resolved = r.resolveAbsolute(req.path, method: req.method!);
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
// print('Pipeline: $pipeline');
for (var handler in pipeline.handlers) {
for (var handler in pipeline.handlers!) {
// print(handler);
// Avoid stack overflow.
if (handler == handleRequest) {

View file

@ -8,13 +8,13 @@ const List<Type> _primitiveTypes = [String, int, num, double, Null];
///
/// Calling [ioc] also auto-serializes the result of a [handler].
RequestHandler ioc(Function handler, {Iterable<String> optional = const []}) {
InjectionRequest injection;
RequestHandler contained;
InjectionRequest? injection;
RequestHandler? contained;
return (req, res) {
if (injection == null) {
injection = preInject(handler, req.app.container.reflector);
injection.optional.addAll(optional ?? []);
injection = preInject(handler, req.app.container!.reflector);
injection!.optional.addAll(optional);
contained = handleContained(handler, injection);
}
@ -22,9 +22,9 @@ RequestHandler ioc(Function handler, {Iterable<String> optional = const []}) {
};
}
resolveInjection(requirement, InjectionRequest injection, RequestContext req,
resolveInjection(requirement, InjectionRequest? injection, RequestContext req,
ResponseContext res, bool throwOnUnresolved,
[Container container]) async {
[Container? container]) async {
var propFromApp;
container ??= req?.container ?? res?.app?.container;
@ -33,20 +33,20 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req,
} else if (requirement == ResponseContext) {
return res;
} else if (requirement is String &&
injection.parameters.containsKey(requirement)) {
var param = injection.parameters[requirement];
injection!.parameters.containsKey(requirement)) {
var param = injection.parameters[requirement]!;
var value = param.getValue(req);
if (value == null && param.required != false) throw param.error;
if (value == null && param.required != false) throw param.error as Object;
return value;
} else if (requirement is String) {
if (req.container.hasNamed(requirement)) {
return req.container.findByName(requirement);
if (req.container!.hasNamed(requirement)) {
return req.container!.findByName(requirement);
}
if (req.params.containsKey(requirement)) {
return req.params[requirement];
} else if ((propFromApp = req.app.findProperty(requirement)) != null) {
return propFromApp;
} else if (injection.optional.contains(requirement)) {
} else if (injection!.optional.contains(requirement)) {
return null;
} else if (throwOnUnresolved) {
throw ArgumentError(
@ -69,7 +69,7 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req,
}
} else if (requirement is Type && requirement != dynamic) {
try {
var futureType = container.reflector.reflectFutureOf(requirement);
var futureType = container!.reflector.reflectFutureOf(requirement);
if (container.has(futureType.reflectedType)) {
return await container.make(futureType.reflectedType);
}
@ -77,7 +77,7 @@ resolveInjection(requirement, InjectionRequest injection, RequestContext req,
// Ignore.
}
return await container.make(requirement);
return await container!.make(requirement);
} else if (throwOnUnresolved) {
throw ArgumentError(
'$requirement cannot be injected into a request handler.');
@ -95,10 +95,10 @@ bool suitableForInjection(
}
/// Handles a request with a DI-enabled handler.
RequestHandler handleContained(Function handler, InjectionRequest injection,
[Container container]) {
RequestHandler handleContained(Function? handler, InjectionRequest? injection,
[Container? container]) {
return (RequestContext req, ResponseContext res) async {
if (injection.parameters.isNotEmpty &&
if (injection!.parameters.isNotEmpty &&
injection.parameters.values.any((p) => p.match != null) &&
!suitableForInjection(req, res, injection)) return Future.value(true);
@ -116,7 +116,7 @@ RequestHandler handleContained(Function handler, InjectionRequest injection,
[entry.key, entry.value], injection, req, res, false, container);
}
return Function.apply(handler, args, named);
return Function.apply(handler!, args, named);
};
}
@ -157,7 +157,7 @@ class InjectionRequest {
InjectionRequest preInject(Function handler, Reflector reflector) {
var injection = InjectionRequest();
var closureMirror = reflector.reflectFunction(handler);
var closureMirror = reflector.reflectFunction(handler)!;
if (closureMirror.parameters.isEmpty) return injection;
@ -169,9 +169,8 @@ InjectionRequest preInject(Function handler, Reflector reflector) {
var _Parameter = reflector.reflectType(Parameter);
var p = parameter.annotations
.firstWhere((m) => m.type.isAssignableTo(_Parameter),
orElse: () => null)
?.reflectee as Parameter;
.firstWhereOrNull((m) => m.type.isAssignableTo(_Parameter))
?.reflectee as Parameter?;
//print(p);
if (p != null) {
injection.parameters[name] = Parameter(

View file

@ -5,7 +5,7 @@ import 'package:angel_http_exception/angel_http_exception.dart';
import 'service.dart';
/// A basic service that manages an in-memory list of maps.
class MapService extends Service<String, Map<String, dynamic>> {
class MapService extends Service<String?, Map<String, dynamic>> {
/// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
///
/// `false` by default.
@ -48,14 +48,14 @@ class MapService extends Service<String, Map<String, dynamic>> {
}
@override
Future<List<Map<String, dynamic>>> index([Map<String, dynamic> params]) {
Future<List<Map<String, dynamic>>> index([Map<String, dynamic>? params]) {
if (allowQuery == false || params == null || params['query'] is! Map) {
return Future.value(items);
} else {
var query = params['query'] as Map;
var query = params['query'] as Map?;
return Future.value(items.where((item) {
for (var key in query.keys) {
for (var key in query!.keys) {
if (!item.containsKey(key)) {
return false;
} else if (item[key] != query[key]) return false;
@ -67,15 +67,15 @@ class MapService extends Service<String, Map<String, dynamic>> {
}
@override
Future<Map<String, dynamic>> read(String id, [Map<String, dynamic> params]) {
Future<Map<String, dynamic>> read(String? id, [Map<String, dynamic>? params]) {
return Future.value(items.firstWhere(_matchesId(id),
orElse: () => throw AngelHttpException.notFound(
message: 'No record found for ID $id')));
orElse: (() => throw AngelHttpException.notFound(
message: 'No record found for ID $id')) as Map<String, dynamic> Function()?));
}
@override
Future<Map<String, dynamic>> create(Map<String, dynamic> data,
[Map<String, dynamic> params]) {
[Map<String, dynamic>? params]) {
if (data is! Map) {
throw AngelHttpException.badRequest(
message:
@ -95,8 +95,8 @@ class MapService extends Service<String, Map<String, dynamic>> {
}
@override
Future<Map<String, dynamic>> modify(String id, Map<String, dynamic> data,
[Map<String, dynamic> params]) {
Future<Map<String, dynamic>> modify(String? id, Map<String, dynamic> data,
[Map<String, dynamic>? params]) {
if (data is! Map) {
throw AngelHttpException.badRequest(
message:
@ -119,8 +119,8 @@ class MapService extends Service<String, Map<String, dynamic>> {
}
@override
Future<Map<String, dynamic>> update(String id, Map<String, dynamic> data,
[Map<String, dynamic> params]) {
Future<Map<String, dynamic>> update(String? id, Map<String, dynamic> data,
[Map<String, dynamic>? params]) {
if (data is! Map) {
throw AngelHttpException.badRequest(
message:
@ -149,8 +149,8 @@ class MapService extends Service<String, Map<String, dynamic>> {
}
@override
Future<Map<String, dynamic>> remove(String id,
[Map<String, dynamic> params]) {
Future<Map<String, dynamic>> remove(String? id,
[Map<String, dynamic>? params]) {
if (id == null || id == 'null') {
// Remove everything...
if (!(allowRemoveAll == true ||

View file

@ -45,9 +45,9 @@ const NoExpose noExpose = NoExpose();
/// ```
class Expose {
final String method;
final String path;
final Iterable<RequestHandler> middleware;
final String as;
final String? path;
final Iterable<RequestHandler>? middleware;
final String? as;
final List<String> allowNull;
static const Expose get = Expose(null, method: 'GET'),
@ -71,16 +71,16 @@ class Expose {
/// Used to apply special dependency injections or functionality to a function parameter.
class Parameter {
/// Inject the value of a request cookie.
final String cookie;
final String? cookie;
/// Inject the value of a request header.
final String header;
final String? header;
/// Inject the value of a key from the session.
final String session;
final String? session;
/// Inject the value of a key from the query.
final String query;
final String? query;
/// Only execute the handler if the value of this parameter matches the given value.
final match;
@ -89,7 +89,7 @@ class Parameter {
final defaultValue;
/// If `true` (default), then an error will be thrown if this parameter is not present.
final bool required;
final bool? required;
const Parameter(
{this.cookie,
@ -122,17 +122,17 @@ class Parameter {
/// Obtains a value for this parameter from a [RequestContext].
getValue(RequestContext req) {
if (cookie?.isNotEmpty == true) {
return req.cookies.firstWhere((c) => c.name == cookie)?.value ??
return req.cookies!.firstWhere((c) => c.name == cookie)?.value ??
defaultValue;
}
if (header?.isNotEmpty == true) {
return req.headers.value(header) ?? defaultValue;
return req.headers!.value(header!) ?? defaultValue;
}
if (session?.isNotEmpty == true) {
return req.session[session] ?? defaultValue;
return req.session![session] ?? defaultValue;
}
if (query?.isNotEmpty == true) {
return req.uri.queryParameters[query] ?? defaultValue;
return req.uri!.queryParameters[query!] ?? defaultValue;
}
return defaultValue;
}

View file

@ -17,6 +17,7 @@ import 'package:http_server/http_server.dart';
import 'package:meta/meta.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import 'package:collection/collection.dart';
import 'metadata.dart';
import 'response_context.dart';
@ -31,13 +32,13 @@ abstract class RequestContext<RawRequest> {
/// when a [RequestContext] is done being processed.
final List<FutureOr<void> Function()> shutdownHooks = [];
String _acceptHeaderCache, _extensionCache;
bool _acceptsAllCache, _hasParsedBody = false, _closed = false;
Map<String, dynamic> _bodyFields, _queryParameters;
List _bodyList;
Object _bodyObject;
List<UploadedFile> _uploadedFiles;
MediaType _contentType;
String? _acceptHeaderCache, _extensionCache;
bool? _acceptsAllCache, _hasParsedBody = false, _closed = false;
Map<String?, dynamic>? _bodyFields, _queryParameters;
List? _bodyList;
Object? _bodyObject;
List<UploadedFile>? _uploadedFiles;
MediaType? _contentType;
/// The underlying [RawRequest] provided by the driver.
RawRequest get rawRequest;
@ -46,22 +47,22 @@ abstract class RequestContext<RawRequest> {
final Map<String, dynamic> serviceParams = {};
/// The [Angel] instance that is responding to this request.
Angel app;
late Angel app;
/// Any cookies sent with this request.
List<Cookie> get cookies;
List<Cookie>? get cookies;
/// All HTTP headers sent with this request.
HttpHeaders get headers;
HttpHeaders? get headers;
/// The requested hostname.
String get hostname;
String? get hostname;
/// The IoC container that can be used to provide functionality to produce
/// objects of a given type.
///
/// This is a *child* of the container found in `app`.
Container get container;
Container? get container;
/// The user's IP.
String get ip => remoteAddress.address;
@ -69,24 +70,24 @@ abstract class RequestContext<RawRequest> {
/// This request's HTTP method.
///
/// This may have been processed by an override. See [originalMethod] to get the real method.
String get method;
String? get method;
/// The original HTTP verb sent to the server.
String get originalMethod;
String? get originalMethod;
/// The content type of an incoming request.
MediaType get contentType =>
_contentType ??= MediaType.parse(headers.contentType.toString());
MediaType? get contentType =>
_contentType ??= MediaType.parse(headers!.contentType.toString());
/// The URL parameters extracted from the request URI.
Map<String, dynamic> params = <String, dynamic>{};
Map<String?, dynamic> params = <String?, dynamic>{};
/// The requested path.
String get path;
String? get path;
/// Is this an **XMLHttpRequest**?
bool get isXhr {
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
return headers!.value("X-Requested-With")?.trim()?.toLowerCase() ==
'xmlhttprequest';
}
@ -94,22 +95,22 @@ abstract class RequestContext<RawRequest> {
InternetAddress get remoteAddress;
/// The user's HTTP session.
HttpSession get session;
HttpSession? get session;
/// The [Uri] instance representing the path this request is responding to.
Uri get uri;
Uri? get uri;
/// The [Stream] of incoming binary data sent from the client.
Stream<List<int>> get body;
Stream<List<int>>? get body;
/// Returns `true` if [parseBody] has been called so far.
bool get hasParsedBody => _hasParsedBody;
bool? get hasParsedBody => _hasParsedBody;
/// Returns a *mutable* [Map] of the fields parsed from the request [body].
///
/// Note that [parseBody] must be called first.
Map<String, dynamic> get bodyAsMap {
if (!hasParsedBody) {
Map<String?, dynamic>? get bodyAsMap {
if (!hasParsedBody!) {
throw StateError('The request body has not been parsed yet.');
} else if (_bodyFields == null) {
throw StateError('The request body, $_bodyObject, is not a Map.');
@ -121,13 +122,13 @@ abstract class RequestContext<RawRequest> {
/// This setter allows you to explicitly set the request body **exactly once**.
///
/// Use this if the format of the body is not natively parsed by Angel.
set bodyAsMap(Map<String, dynamic> value) => bodyAsObject = value;
set bodyAsMap(Map<String?, dynamic>? value) => bodyAsObject = value;
/// Returns a *mutable* [List] parsed from the request [body].
///
/// Note that [parseBody] must be called first.
List get bodyAsList {
if (!hasParsedBody) {
List? get bodyAsList {
if (!hasParsedBody!) {
throw StateError('The request body has not been parsed yet.');
} else if (_bodyList == null) {
throw StateError('The request body, $_bodyObject, is not a List.');
@ -139,13 +140,13 @@ abstract class RequestContext<RawRequest> {
/// This setter allows you to explicitly set the request body **exactly once**.
///
/// Use this if the format of the body is not natively parsed by Angel.
set bodyAsList(List value) => bodyAsObject = value;
set bodyAsList(List? value) => bodyAsObject = value;
/// Returns the parsed request body, whatever it may be (typically a [Map] or [List]).
///
/// Note that [parseBody] must be called first.
Object get bodyAsObject {
if (!hasParsedBody) {
Object? get bodyAsObject {
if (!hasParsedBody!) {
throw StateError('The request body has not been parsed yet.');
}
@ -161,7 +162,7 @@ abstract class RequestContext<RawRequest> {
'The request body has already been parsed/set, and cannot be overwritten.');
} else {
if (value is List) _bodyList = value;
if (value is Map<String, dynamic>) _bodyFields = value;
if (value is Map<String?, dynamic>) _bodyFields = value;
_bodyObject = value;
_hasParsedBody = true;
}
@ -170,8 +171,8 @@ abstract class RequestContext<RawRequest> {
/// Returns a *mutable* map of the files parsed from the request [body].
///
/// Note that [parseBody] must be called first.
List<UploadedFile> get uploadedFiles {
if (!hasParsedBody) {
List<UploadedFile>? get uploadedFiles {
if (!hasParsedBody!) {
throw StateError('The request body has not been parsed yet.');
}
@ -179,13 +180,13 @@ abstract class RequestContext<RawRequest> {
}
/// Returns a *mutable* map of the fields contained in the query.
Map<String, dynamic> get queryParameters =>
_queryParameters ??= Map<String, dynamic>.from(uri.queryParameters);
Map<String?, dynamic> get queryParameters =>
_queryParameters ??= Map<String?, dynamic>.from(uri!.queryParameters);
/// Returns the file extension of the requested path, if any.
///
/// Includes the leading `.`, if there is one.
String get extension => _extensionCache ??= p.extension(uri.path);
String get extension => _extensionCache ??= p.extension(uri!.path);
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
///
@ -207,14 +208,14 @@ abstract class RequestContext<RawRequest> {
'RequestContext.accepts expects the `contentType` parameter to NOT be null.');
}
_acceptHeaderCache ??= headers.value('accept');
_acceptHeaderCache ??= headers!.value('accept');
if (_acceptHeaderCache == null) {
return true;
} else if (strict != true && _acceptHeaderCache.contains('*/*')) {
} else if (strict != true && _acceptHeaderCache!.contains('*/*')) {
return true;
} else {
return _acceptHeaderCache.contains(contentTypeString);
return _acceptHeaderCache!.contains(contentTypeString);
}
}
@ -222,14 +223,14 @@ abstract class RequestContext<RawRequest> {
bool get acceptsAll => _acceptsAllCache ??= accepts('*/*');
/// Shorthand for deserializing [bodyAsMap], using some transformer function [f].
Future<T> deserializeBody<T>(FutureOr<T> Function(Map) f,
Future<T> deserializeBody<T>(FutureOr<T> Function(Map?) f,
{Encoding encoding = utf8}) async {
await parseBody(encoding: encoding);
return await f(bodyAsMap);
}
/// Shorthand for decoding [bodyAsMap], using some [codec].
Future<T> decodeBody<T>(Codec<T, Map> codec, {Encoding encoding = utf8}) =>
Future<T> decodeBody<T>(Codec<T, Map?> codec, {Encoding encoding = utf8}) =>
deserializeBody(codec.decode, encoding: encoding);
/// Manually parses the request body, if it has not already been parsed.
@ -238,47 +239,48 @@ abstract class RequestContext<RawRequest> {
throw FormatException('Missing "content-type" header.');
}
if (!_hasParsedBody) {
if (!_hasParsedBody!) {
_hasParsedBody = true;
if (contentType.type == 'application' && contentType.subtype == 'json') {
if (contentType!.type == 'application' &&
contentType!.subtype == 'json') {
_uploadedFiles = [];
var parsed = _bodyObject =
await encoding.decoder.bind(body).join().then(json.decode);
var parsed = (_bodyObject =
await encoding.decoder.bind(body!).join().then(json.decode))!;
if (parsed is Map) {
_bodyFields = Map<String, dynamic>.from(parsed);
_bodyFields = Map<String?, dynamic>.from(parsed);
} else if (parsed is List) {
_bodyList = parsed;
}
} else if (contentType.type == 'application' &&
contentType.subtype == 'x-www-form-urlencoded') {
} else if (contentType!.type == 'application' &&
contentType!.subtype == 'x-www-form-urlencoded') {
_uploadedFiles = [];
var parsed = await encoding.decoder
.bind(body)
.bind(body!)
.join()
.then((s) => Uri.splitQueryString(s, encoding: encoding));
_bodyFields = Map<String, dynamic>.from(parsed);
} else if (contentType.type == 'multipart' &&
contentType.subtype == 'form-data' &&
contentType.parameters.containsKey('boundary')) {
var boundary = contentType.parameters['boundary'];
_bodyFields = Map<String?, dynamic>.from(parsed);
} else if (contentType!.type == 'multipart' &&
contentType!.subtype == 'form-data' &&
contentType!.parameters.containsKey('boundary')) {
var boundary = contentType!.parameters['boundary']!;
var transformer = MimeMultipartTransformer(boundary);
var parts = transformer.bind(body).map((part) =>
var parts = transformer.bind(body!).map((part) =>
HttpMultipartFormData.parse(part, defaultEncoding: encoding));
_bodyFields = {};
_uploadedFiles = [];
await for (var part in parts) {
if (part.isBinary) {
_uploadedFiles.add(UploadedFile(part));
_uploadedFiles!.add(UploadedFile(part));
} else if (part.isText &&
part.contentDisposition.parameters.containsKey('name')) {
// If there is no name, then don't parse it.
var key = part.contentDisposition.parameters['name'];
var value = await part.join();
_bodyFields[key] = value;
_bodyFields![key] = value;
}
}
} else {
@ -291,13 +293,13 @@ abstract class RequestContext<RawRequest> {
/// Disposes of all resources.
@mustCallSuper
Future<void> close() async {
if (!_closed) {
if (!_closed!) {
_closed = true;
_acceptsAllCache = null;
_acceptHeaderCache = null;
serviceParams.clear();
params.clear();
await Future.forEach(shutdownHooks, (hook) => hook());
await Future.forEach(shutdownHooks, (dynamic hook) => hook());
}
}
}
@ -307,7 +309,7 @@ class UploadedFile {
/// The underlying `form-data` item.
final HttpMultipartFormData formData;
MediaType _contentType;
MediaType? _contentType;
UploadedFile(this.formData);
@ -316,22 +318,22 @@ class UploadedFile {
/// The filename associated with the data on the user's system.
/// Returns [:null:] if not present.
String get filename => formData.contentDisposition.parameters['filename'];
String? get filename => formData.contentDisposition.parameters['filename'];
/// The name of the field associated with this data.
/// Returns [:null:] if not present.
String get name => formData.contentDisposition.parameters['name'];
String? get name => formData.contentDisposition.parameters['name'];
/// The parsed [:Content-Type:] header of the [:HttpMultipartFormData:].
/// Returns [:null:] if not present.
MediaType get contentType => _contentType ??= (formData.contentType == null
MediaType? get contentType => _contentType ??= (formData.contentType == null
? null
: MediaType.parse(formData.contentType.toString()));
/// The parsed [:Content-Transfer-Encoding:] header of the
/// [:HttpMultipartFormData:]. This field is used to determine how to decode
/// the data. Returns [:null:] if not present.
HeaderValue get contentTransferEncoding => formData.contentTransferEncoding;
HeaderValue? get contentTransferEncoding => formData.contentTransferEncoding;
/// Reads the contents of the file into a single linear buffer.
///

View file

@ -26,14 +26,14 @@ abstract class ResponseContext<RawResponse>
'server': 'angel',
});
Completer _done;
Completer? _done;
int _statusCode = 200;
/// The [Angel] instance that is sending a response.
Angel app;
Angel? app;
/// Is `Transfer-Encoding` chunked?
bool chunked;
bool? chunked;
/// Any and all cookies to be sent to the user.
final List<Cookie> cookies = [];
@ -49,7 +49,7 @@ abstract class ResponseContext<RawResponse>
final Map<String, dynamic> renderParams = {};
/// Points to the [RequestContext] corresponding to this response.
RequestContext get correspondingRequest;
RequestContext? get correspondingRequest;
@override
Future get done => (_done ?? Completer()).future;
@ -71,12 +71,12 @@ abstract class ResponseContext<RawResponse>
/// ```dart
/// app.injectSerializer(JSON.encode);
/// ```
FutureOr<String> Function(dynamic) serializer = c.json.encode;
FutureOr<String> Function(dynamic)? serializer = c.json.encode;
/// This response's status code.
int get statusCode => _statusCode;
set statusCode(int value) {
set statusCode(int? value) {
if (!isOpen) {
throw closed();
} else {
@ -94,7 +94,7 @@ abstract class ResponseContext<RawResponse>
bool get isBuffered;
/// A set of UTF-8 encoded bytes that will be written to the response.
BytesBuilder get buffer;
BytesBuilder? get buffer;
/// The underlying [RawResponse] under this instance.
RawResponse get rawResponse;
@ -108,14 +108,14 @@ abstract class ResponseContext<RawResponse>
/// Gets or sets the content length to send back to a client.
///
/// Returns `null` if the header is invalidly formatted.
int get contentLength {
return int.tryParse(headers['content-length']);
int? get contentLength {
return int.tryParse(headers['content-length']!);
}
/// Gets or sets the content length to send back to a client.
///
/// If [value] is `null`, then the header will be removed.
set contentLength(int value) {
set contentLength(int? value) {
if (value == null) {
headers.remove('content-length');
} else {
@ -126,7 +126,7 @@ abstract class ResponseContext<RawResponse>
/// Gets or sets the content type to send back to a client.
MediaType get contentType {
try {
return MediaType.parse(headers['content-type']);
return MediaType.parse(headers['content-type']!);
} catch (_) {
return MediaType('text', 'plain');
}
@ -140,18 +140,18 @@ abstract class ResponseContext<RawResponse>
static StateError closed() => StateError('Cannot modify a closed response.');
/// Sends a download as a response.
Future<void> download(File file, {String filename}) async {
Future<void> download(File file, {String? filename}) async {
if (!isOpen) throw closed();
headers["Content-Disposition"] =
'attachment; filename="${filename ?? file.path}"';
contentType = MediaType.parse(lookupMimeType(file.path));
contentType = MediaType.parse(lookupMimeType(file.path)!);
headers['content-length'] = file.lengthSync().toString();
if (!isBuffered) {
await file.openRead().cast<List<int>>().pipe(this);
} else {
buffer.add(file.readAsBytesSync());
buffer!.add(file.readAsBytesSync());
await close();
}
}
@ -162,7 +162,7 @@ abstract class ResponseContext<RawResponse>
(buffer as LockableBytesBuilder).lock();
}
if (_done?.isCompleted == false) _done.complete();
if (_done?.isCompleted == false) _done!.complete();
return Future.value();
}
@ -175,18 +175,18 @@ abstract class ResponseContext<RawResponse>
///
/// You can override the [contentType] sent; by default it is `application/javascript`.
Future<void> jsonp(value,
{String callbackName = "callback", MediaType contentType}) {
{String callbackName = "callback", MediaType? contentType}) {
if (!isOpen) throw closed();
this.contentType = contentType ?? MediaType('application', 'javascript');
write("$callbackName(${serializer(value)})");
write("$callbackName(${serializer!(value)})");
return close();
}
/// Renders a view to the response stream, and closes the response.
Future<void> render(String view, [Map<String, dynamic> data]) {
Future<void> render(String view, [Map<String, dynamic>? data]) {
if (!isOpen) throw closed();
contentType = MediaType('text', 'html', {'charset': 'utf-8'});
return Future<String>.sync(() => app.viewGenerator(
return Future<String>.sync(() => app!.viewGenerator!(
view,
Map<String, dynamic>.from(renderParams)
..addAll(data ?? <String, dynamic>{}))).then((content) {
@ -202,13 +202,13 @@ abstract class ResponseContext<RawResponse>
/// based on the provided params.
///
/// See [Router]#navigate for more. :)
Future<void> redirect(url, {bool absolute = true, int code = 302}) {
Future<void> redirect(url, {bool absolute = true, int? code = 302}) {
if (!isOpen) throw closed();
headers
..['content-type'] = 'text/html'
..['location'] = (url is String || url is Uri)
? url.toString()
: app.navigate(url as Iterable, absolute: absolute);
: app!.navigate(url as Iterable, absolute: absolute);
statusCode = code ?? 302;
write('''
<!DOCTYPE html>
@ -231,9 +231,9 @@ abstract class ResponseContext<RawResponse>
}
/// Redirects to the given named [Route].
Future<void> redirectTo(String name, [Map params, int code]) async {
Future<void> redirectTo(String name, [Map? params, int? code]) async {
if (!isOpen) throw closed();
Route _findRoute(Router r) {
Route? _findRoute(Router r) {
for (Route route in r.routes) {
if (route is SymlinkRoute) {
final m = _findRoute(route.router);
@ -245,11 +245,11 @@ abstract class ResponseContext<RawResponse>
return null;
}
Route matched = _findRoute(app);
Route? matched = _findRoute(app!);
if (matched != null) {
await redirect(
matched.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
matched.makeUri(params!.keys.fold<Map<String, dynamic>>({}, (out, k) {
return out..[k.toString()] = params[k];
})),
code: code);
@ -260,7 +260,7 @@ abstract class ResponseContext<RawResponse>
}
/// Redirects to the given [Controller] action.
Future<void> redirectToAction(String action, [Map params, int code]) {
Future<void> redirectToAction(String action, [Map? params, int? code]) {
if (!isOpen) throw closed();
// UserController@show
List<String> split = action.split("@");
@ -270,14 +270,14 @@ abstract class ResponseContext<RawResponse>
"Controller redirects must take the form of 'Controller@action'. You gave: $action");
}
Controller controller =
app.controllers[split[0].replaceAll(_straySlashes, '')];
Controller? controller =
app!.controllers[split[0].replaceAll(_straySlashes, '')];
if (controller == null) {
throw Exception("Could not find a controller named '${split[0]}'");
}
Route matched = controller.routeMappings[split[1]];
Route? matched = controller.routeMappings[split[1]];
if (matched == null) {
throw Exception(
@ -285,24 +285,26 @@ abstract class ResponseContext<RawResponse>
}
final head = controller
.findExpose(app.container.reflector)
.findExpose(app!.container!.reflector)!
.path
.toString()
.replaceAll(_straySlashes, '');
final tail = matched
.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
return out..[k.toString()] = params[k];
}))
.replaceAll(_straySlashes, '');
String tail = "";
if (params != null) {
tail = matched
.makeUri(params.keys.fold<Map<String, dynamic>>({}, (out, k) {
return out..[k.toString()] = params[k];
}))
.replaceAll(_straySlashes, '');
}
return redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code);
}
/// Serializes data to the response.
Future<bool> serialize(value, {MediaType contentType}) async {
Future<bool> serialize(value, {MediaType? contentType}) async {
if (!isOpen) throw closed();
this.contentType = contentType ?? MediaType('application', 'json');
var text = await serializer(value);
var text = await serializer!(value);
if (text.isEmpty) return true;
write(text);
await close();
@ -314,13 +316,13 @@ abstract class ResponseContext<RawResponse>
/// `HEAD` responses will not actually write data.
Future streamFile(File file) async {
if (!isOpen) throw closed();
var mimeType = app.mimeTypeResolver.lookup(file.path);
var mimeType = app!.mimeTypeResolver.lookup(file.path);
contentLength = await file.length();
contentType = mimeType == null
? MediaType('application', 'octet-stream')
: MediaType.parse(mimeType);
if (correspondingRequest.method != 'HEAD') {
if (correspondingRequest!.method != 'HEAD') {
return this
.addStream(file.openRead().cast<List<int>>())
.then((_) => this.close());
@ -338,16 +340,16 @@ abstract class ResponseContext<RawResponse>
Future addStream(Stream<List<int>> stream);
@override
void addError(Object error, [StackTrace stackTrace]) {
void addError(Object error, [StackTrace? stackTrace]) {
if (_done?.isCompleted == false) {
_done.completeError(error, stackTrace);
_done!.completeError(error, stackTrace);
} else if (_done == null) {
Zone.current.handleUncaughtError(error, stackTrace);
Zone.current.handleUncaughtError(error, stackTrace!);
}
}
/// Writes data to the response.
void write(value, {Encoding encoding}) {
void write(value, {Encoding? encoding}) {
encoding ??= utf8;
if (!isOpen && isBuffered) {
@ -355,7 +357,7 @@ abstract class ResponseContext<RawResponse>
} else if (!isBuffered) {
add(encoding.encode(value.toString()));
} else {
buffer.add(encoding.encode(value.toString()));
buffer!.add(encoding.encode(value.toString()));
}
}
@ -366,12 +368,12 @@ abstract class ResponseContext<RawResponse>
} else if (!isBuffered) {
add([charCode]);
} else {
buffer.addByte(charCode);
buffer!.addByte(charCode);
}
}
@override
void writeln([Object obj = ""]) {
void writeln([Object? obj = ""]) {
write(obj.toString());
write('\r\n');
}

View file

@ -21,7 +21,7 @@ typedef FutureOr RequestHandler(RequestContext req, ResponseContext res);
/// return `true`. Works well with [Router].chain.
RequestHandler chain(Iterable<RequestHandler> handlers) {
return (req, res) {
Future Function() runPipeline;
Future Function()? runPipeline;
for (var handler in handlers) {
if (handler == null) break;
@ -42,19 +42,19 @@ 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<Pattern, Service?> _serviceLookups = {};
final Map configuration = {};
final Container _container;
final Container? _container;
Routable([Reflector reflector])
Routable([Reflector? reflector])
: _container = reflector == null ? null : Container(reflector),
super();
/// A [Container] used to inject dependencies.
Container get container => _container;
Container? get container => _container;
void close() {
_services.clear();
@ -73,34 +73,34 @@ class Routable extends Router<RequestHandler> {
Stream<Service> get onService => _onService.stream;
/// Retrieves the service assigned to the given path.
T findService<T extends Service>(Pattern path) {
T? findService<T extends Service?>(Pattern path) {
return _serviceLookups.putIfAbsent(path, () {
return _services[path] ??
_services[path.toString().replaceAll(_straySlashes, '')];
}) as T;
}) as T?;
}
/// Shorthand for finding a [Service] in a statically-typed manner.
Service<Id, Data> findServiceOf<Id, Data>(Pattern path) {
Service<Id, Data>? findServiceOf<Id, Data>(Pattern path) {
return findService<Service<Id, Data>>(path);
}
/// Shorthand for finding a [HookedService] in a statically-typed manner.
HookedService<dynamic, dynamic, T> findHookedService<T extends Service>(
HookedService<dynamic, dynamic, T>? findHookedService<T extends Service>(
Pattern path) {
return findService(path) as HookedService<dynamic, dynamic, T>;
return findService(path) as HookedService<dynamic, dynamic, T>?;
}
@override
Route<RequestHandler> addRoute(
String method, String path, RequestHandler handler,
{Iterable<RequestHandler> middleware}) {
Route<RequestHandler?> addRoute(
String? method, String? path, RequestHandler? handler,
{Iterable<RequestHandler?>? middleware}) {
middleware ??= [];
final handlers = <RequestHandler>[];
// Merge @Middleware declaration, if any
var reflector = _container?.reflector;
if (reflector != null && reflector is! ThrowingReflector) {
Middleware middlewareDeclaration =
Middleware? middlewareDeclaration =
getAnnotation<Middleware>(handler, _container?.reflector);
if (middlewareDeclaration != null) {
handlers.addAll(middlewareDeclaration.handlers);
@ -108,7 +108,7 @@ class Routable extends Router<RequestHandler> {
}
final handlerSequence = <RequestHandler>[];
handlerSequence.addAll(middleware ?? []);
handlerSequence.addAll(middleware as Iterable<FutureOr<dynamic>? Function(RequestContext<dynamic>, ResponseContext<dynamic>)>? ?? []);
handlerSequence.addAll(handlers);
return super.addRoute(method, path.toString(), handler,

View file

@ -26,21 +26,21 @@ typedef FutureOr<void> AngelConfigurer(Angel app);
/// A function that asynchronously generates a view from the given path and data.
typedef FutureOr<String> ViewGenerator(String path,
[Map<String, dynamic> data]);
[Map<String, dynamic>? data]);
/// A powerful real-time/REST/MVC server class.
class Angel extends Routable {
static ViewGenerator noViewEngineConfigured =
(String view, [Map data]) => 'No view engine has been configured yet.';
(String view, [Map? data]) => 'No view engine has been configured yet.';
final List<Angel> _children = [];
final Map<
String,
Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>,
Tuple4<List?, Map<String?, dynamic>, ParseResult<RouteResult?>?,
MiddlewarePipeline>> handlerCache = HashMap();
Router<RequestHandler> _flattened;
Angel _parent;
Router<RequestHandler?>? _flattened;
Angel? _parent;
/// A global Map of converters that can transform responses bodies.
final Map<String, Converter<List<int>, List<int>>> encoders = {};
@ -51,7 +51,7 @@ class Angel extends Routable {
final MimeTypeResolver mimeTypeResolver = MimeTypeResolver();
/// A middleware to inject a serialize on every request.
FutureOr<String> Function(dynamic) serializer;
FutureOr<String> Function(dynamic)? serializer;
/// A [Map] of dependency data obtained via reflection.
///
@ -59,7 +59,7 @@ class Angel extends Routable {
Map<dynamic, InjectionRequest> get preContained => _preContained;
/// Returns the [flatten]ed version of this router in production.
Router<RequestHandler> get optimizedRouter => _flattened ?? this;
Router<RequestHandler?> get optimizedRouter => _flattened ?? this;
/// Determines whether to allow HTTP request method overrides.
bool allowMethodOverrides = true;
@ -67,10 +67,10 @@ class Angel extends Routable {
/// All child application mounted on this instance.
List<Angel> get children => List<Angel>.unmodifiable(_children);
final Map<Pattern, Controller> _controllers = {};
final Map<Pattern?, Controller> _controllers = {};
/// A set of [Controller] objects that have been loaded into the application.
Map<Pattern, Controller> get controllers => _controllers;
Map<Pattern?, Controller> get controllers => _controllers;
/// Now *deprecated*, in favor of [AngelEnv] and [angelEnv]. Use `app.environment.isProduction`
/// instead.
@ -91,10 +91,10 @@ class Angel extends Routable {
final AngelEnvironment environment;
/// Returns the parent instance of this application, if any.
Angel get parent => _parent;
Angel? get parent => _parent;
/// Outputs diagnostics and debug messages.
Logger logger;
Logger? logger;
/// Plug-ins to be called right before server startup.
///
@ -121,7 +121,7 @@ class Angel extends Routable {
/// A function that renders views.
///
/// Called by [ResponseContext]@`render`.
ViewGenerator viewGenerator = noViewEngineConfigured;
ViewGenerator? viewGenerator = noViewEngineConfigured;
/// The handler currently configured to run on [AngelHttpException]s.
Function(AngelHttpException e, RequestContext req, ResponseContext res)
@ -148,9 +148,9 @@ class Angel extends Routable {
};
@override
Route<RequestHandler> addRoute(
String method, String path, RequestHandler handler,
{Iterable<RequestHandler> middleware}) {
Route<RequestHandler?> addRoute(
String? method, String? path, RequestHandler? handler,
{Iterable<RequestHandler?>? middleware}) {
middleware ??= [];
if (_flattened != null) {
logger?.warning(
@ -163,7 +163,7 @@ class Angel extends Routable {
}
@override
mount(String path, Router<RequestHandler> router) {
mount(String path, Router<RequestHandler?> router) {
if (_flattened != null) {
logger?.warning(
'WARNING: You added mounted a child router ($path) on the router, after it had been optimized.');
@ -182,12 +182,12 @@ class Angel extends Routable {
/// Loads some base dependencies into the service container.
void bootstrapContainer() {
if (runtimeType != Angel) {
container.registerSingleton(this);
container!.registerSingleton(this);
}
container.registerSingleton<Angel>(this);
container.registerSingleton<Routable>(this);
container.registerSingleton<Router>(this);
container!.registerSingleton<Angel>(this);
container!.registerSingleton<Routable>(this);
container!.registerSingleton<Router>(this);
}
/// Shuts down the server, and closes any open [StreamController]s.
@ -216,14 +216,14 @@ class Angel extends Routable {
@override
void dumpTree(
{callback(String tree),
{callback(String tree)?,
String header = 'Dumping route tree:',
String tab = ' ',
bool showMatchers = false}) {
if (environment.isProduction) {
_flattened ??= flatten(this);
_flattened.dumpTree(
_flattened!.dumpTree(
callback: callback,
header: header?.isNotEmpty == true
? header
@ -284,7 +284,7 @@ class Angel extends Routable {
/// Attempts to find a property by the given name within this application.
findProperty(key) {
if (configuration.containsKey(key)) return configuration[key];
return parent != null ? parent.findProperty(key) : null;
return parent != null ? parent!.findProperty(key) : null;
}
/// Runs several optimizations, *if* [angelEnv.isProduction] is `true`.
@ -305,7 +305,7 @@ class Angel extends Routable {
/// If this function has been reflected before, then
/// the execution will be faster, as the injection requirements were stored beforehand.
Future runContained(Function handler, RequestContext req, ResponseContext res,
[Container container]) {
[Container? container]) {
return Future.sync(() {
if (_preContained.containsKey(handler)) {
return handleContained(handler, _preContained[handler], container)(
@ -318,11 +318,11 @@ class Angel extends Routable {
/// Runs with DI, and *always* reflects. Prefer [runContained].
Future runReflected(Function handler, RequestContext req, ResponseContext res,
[Container container]) {
[Container? container]) {
container ??= req?.container ?? res?.app?.container;
var h = handleContained(
handler,
_preContained[handler] = preInject(handler, container.reflector),
_preContained[handler] = preInject(handler, container!.reflector),
container);
return Future.sync(() => h(req, res));
// return closureMirror.apply(args).reflectee;
@ -340,13 +340,13 @@ class Angel extends Routable {
/// provide a [type] argument as well.
///
/// If you are on `Dart >=2.0.0`, simply call `mountController<T>()`.
Future<T> mountController<T extends Controller>([Type type]) {
var controller = container.make<T>(type);
Future<T> mountController<T extends Controller>([Type? type]) {
T controller = container!.make<T>(type)!;
return configure(controller.configureServer).then((_) => controller);
}
/// Shorthand for calling `all('*', handler)`.
Route<RequestHandler> fallback(RequestHandler handler) {
Route<RequestHandler?> fallback(RequestHandler handler) {
return all('*', handler);
}

View file

@ -68,29 +68,31 @@ class Service<Id, Data> extends Routable {
List<RequestHandler> get bootstrappers => [];
/// The [Angel] app powering this service.
Angel app;
Angel? app;
/// Closes this service, including any database connections or stream controllers.
void close() {}
/// An optional [readData] function can be passed to handle non-map/non-json bodies.
Service({FutureOr<Data> Function(RequestContext, ResponseContext) readData}) {
_readData = readData ??
(req, res) {
if (req.bodyAsObject is! Data) {
throw AngelHttpException.badRequest(
message:
'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.');
} else {
return req.bodyAsObject as Data;
}
};
Service(
{FutureOr<Data> Function(RequestContext, ResponseContext?)? readData}) {
_readData = readData;
_readData ??= (req, res) {
if (req.bodyAsObject is! Data) {
throw AngelHttpException.badRequest(
message:
'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.');
} else {
return req.bodyAsObject as Data?;
}
};
}
FutureOr<Data> Function(RequestContext, ResponseContext) _readData;
FutureOr<Data>? Function(RequestContext, ResponseContext?)? _readData;
/// A [Function] that reads the request body and converts it into [Data].
FutureOr<Data> Function(RequestContext, ResponseContext) get readData =>
FutureOr<Data>? Function(RequestContext, ResponseContext?)? get readData =>
_readData;
/// Retrieves the first object from the result of calling [index] with the given [params].
@ -103,7 +105,7 @@ class Service<Id, Data> extends Routable {
///
/// A custom [errorMessage] may be provided.
Future<Data> findOne(
[Map<String, dynamic> params,
[Map<String, dynamic>? params,
String errorMessage = 'No record was found matching the given query.']) {
return index(params).then((result) {
if (result == null) {
@ -119,12 +121,12 @@ class Service<Id, Data> extends Routable {
}
/// Retrieves all resources.
Future<List<Data>> index([Map<String, dynamic> params]) {
Future<List<Data>> index([Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
/// Retrieves the desired resource.
Future<Data> read(Id id, [Map<String, dynamic> params]) {
Future<Data> read(Id? id, [Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
@ -132,27 +134,27 @@ class Service<Id, Data> extends Routable {
///
/// Service implementations should override this to ensure data is fetched within a
/// single round trip.
Future<List<Data>> readMany(List<Id> ids, [Map<String, dynamic> params]) {
Future<List<Data>> readMany(List<Id> ids, [Map<String, dynamic>? params]) {
return Future.wait(ids.map((id) => read(id, params)));
}
/// Creates a resource.
Future<Data> create(Data data, [Map<String, dynamic> params]) {
Future<Data> create(Data data, [Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
/// Modifies a resource.
Future<Data> modify(Id id, Data data, [Map<String, dynamic> params]) {
Future<Data> modify(Id? id, Data data, [Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
/// Overwrites a resource.
Future<Data> update(Id id, Data data, [Map<String, dynamic> params]) {
Future<Data> update(Id? id, Data data, [Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
/// Removes the given resource.
Future<Data> remove(Id id, [Map<String, dynamic> params]) {
Future<Data> remove(Id? id, [Map<String, dynamic>? params]) {
throw AngelHttpException.methodNotAllowed();
}
@ -161,14 +163,15 @@ class Service<Id, Data> extends Routable {
///
/// Handy utility for handling data in a type-safe manner.
Service<Id, U> map<U>(U Function(Data) encoder, Data Function(U) decoder,
{FutureOr<U> Function(RequestContext, ResponseContext) readData}) {
{FutureOr<U> Function(RequestContext, ResponseContext)? readData}) {
readData ??= (req, res) async {
var inner = await this.readData(req, res);
Data inner = await this.readData!(req, res)!;
return encoder(inner);
};
return AnonymousService<Id, U>(
readData: readData,
readData: readData as FutureOr<U> Function(
RequestContext<dynamic>, ResponseContext<dynamic>?)?,
index: ([params]) {
return index(params).then((it) => it.map(encoder).toList());
},
@ -195,7 +198,7 @@ class Service<Id, Data> extends Routable {
/// The single type argument, [T], is used to determine how to parse the [id].
///
/// For example, `parseId<bool>` attempts to parse the value as a [bool].
static T parseId<T>(id) {
static T? parseId<T>(id) {
if (id == 'null' || id == null) {
return null;
} else if (T == String) {
@ -214,7 +217,7 @@ class Service<Id, Data> extends Routable {
}
/// Generates RESTful routes pointing to this class's methods.
void addRoutes([Service service]) {
void addRoutes([Service? service]) {
_addRoutesInner(service ?? this, bootstrappers);
}
@ -223,13 +226,13 @@ class Service<Id, Data> extends Routable {
var handlers = List<RequestHandler>.from(handlerss);
// Add global middleware if declared on the instance itself
Middleware before =
getAnnotation<Middleware>(service, app.container.reflector);
Middleware? before =
getAnnotation<Middleware>(service, app!.container!.reflector);
if (before != null) handlers.addAll(before.handlers);
Middleware indexMiddleware =
getAnnotation<Middleware>(service.index, app.container.reflector);
Middleware? indexMiddleware =
getAnnotation<Middleware>(service.index, app!.container!.reflector);
get('/', (req, res) {
return this.index(mergeMap([
{'query': req.queryParameters},
@ -237,17 +240,19 @@ class Service<Id, Data> extends Routable {
req.serviceParams
]));
},
middleware: <RequestHandler>[]
middleware: []
..addAll(handlers)
..addAll((indexMiddleware == null) ? [] : indexMiddleware.handlers));
..addAll((indexMiddleware == null)
? []
: indexMiddleware.handlers.toList()));
Middleware createMiddleware =
getAnnotation<Middleware>(service.create, app.container.reflector);
Middleware? createMiddleware =
getAnnotation<Middleware>(service.create, app!.container!.reflector);
post('/', (req, ResponseContext res) {
return req.parseBody().then((_) async {
return await this
.create(
await readData(req, res),
(await readData!(req, res))!,
mergeMap([
{'query': req.queryParameters},
restProvider,
@ -261,11 +266,12 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(createMiddleware == null) ? [] : createMiddleware.handlers));
..addAll((createMiddleware == null)
? []
: createMiddleware.handlers.toList()));
Middleware readMiddleware =
getAnnotation<Middleware>(service.read, app.container.reflector);
Middleware? readMiddleware =
getAnnotation<Middleware>(service.read, app!.container!.reflector);
get('/:id', (req, res) {
return this.read(
@ -278,15 +284,18 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
..addAll((readMiddleware == null)
? []
: readMiddleware.handlers.toList()));
Middleware? modifyMiddleware =
getAnnotation<Middleware>(service.modify, app!.container!.reflector);
Middleware modifyMiddleware =
getAnnotation<Middleware>(service.modify, app.container.reflector);
patch('/:id', (req, res) {
return req.parseBody().then((_) async {
return await this.modify(
parseId<Id>(req.params['id']),
await readData(req, res),
(await readData!(req, res))!,
mergeMap([
{'query': req.queryParameters},
restProvider,
@ -296,16 +305,17 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
..addAll((modifyMiddleware == null)
? []
: modifyMiddleware.handlers.toList()));
Middleware updateMiddleware =
getAnnotation<Middleware>(service.update, app.container.reflector);
Middleware? updateMiddleware =
getAnnotation<Middleware>(service.update, app!.container!.reflector);
post('/:id', (req, res) {
return req.parseBody().then((_) async {
return await this.update(
parseId<Id>(req.params['id']),
await readData(req, res),
(await readData!(req, res))!,
mergeMap([
{'query': req.queryParameters},
restProvider,
@ -315,13 +325,15 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
..addAll((updateMiddleware == null)
? []
: updateMiddleware.handlers.toList()));
put('/:id', (req, res) {
return req.parseBody().then((_) async {
return await this.update(
parseId<Id>(req.params['id']),
await readData(req, res),
(await readData!(req, res))!,
mergeMap([
{'query': req.queryParameters},
restProvider,
@ -331,11 +343,12 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
..addAll((updateMiddleware == null)
? []
: updateMiddleware.handlers.toList()));
Middleware removeMiddleware =
getAnnotation<Middleware>(service.remove, app.container.reflector);
Middleware? removeMiddleware =
getAnnotation<Middleware>(service.remove, app!.container!.reflector);
delete('/', (req, res) {
return this.remove(
null,
@ -347,8 +360,10 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
..addAll((removeMiddleware == null)
? []
: removeMiddleware.handlers.toList()));
delete('/:id', (req, res) {
return this.remove(
parseId<Id>(req.params['id']),
@ -360,8 +375,9 @@ class Service<Id, Data> extends Routable {
},
middleware: []
..addAll(handlers)
..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
..addAll((removeMiddleware == null)
? []
: removeMiddleware.handlers.toList()));
// REST compliance
put('/', (req, res) => throw AngelHttpException.notFound());

View file

@ -21,13 +21,13 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
@override
Uri get uri => server == null
? Uri()
: Uri(scheme: 'http', host: server.address.address, port: server.port);
: 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);
}
@ -52,7 +52,7 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
/// the server.
factory AngelHttp.secure(
Angel app, String certificateChainPath, String serverKeyPath,
{String password, bool useZone = true}) {
{String? password, bool useZone = true}) {
var certificateChain =
Platform.script.resolve(certificateChainPath).toFilePath();
var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
@ -64,7 +64,7 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
/// Use [server] instead.
@deprecated
HttpServer get httpServer => server;
HttpServer? get httpServer => server;
Future handleRequest(HttpRequest request) =>
handleRawRequest(request, request.response);
@ -87,17 +87,27 @@ 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
Future<HttpResponseContext> createResponseContext(
HttpRequest request, HttpResponse response,
[HttpRequestContext correspondingRequest]) {
return Future<HttpResponseContext>.value(
HttpResponseContext(response, app, correspondingRequest)
..serializer = (app.serializer ?? json.encode)
..encoders.addAll(app.encoders ?? {}));
[HttpRequestContext? correspondingRequest]) {
// TODO: Refactored to overcome NNBD migration error
HttpResponseContext context =
HttpResponseContext(response, app, correspondingRequest);
if (app!.serializer == null) {
context.serializer = json.encode;
} else {
context.serializer = app!.serializer;
}
return Future<HttpResponseContext>.value(context);
// return Future<HttpResponseContext>.value(
// HttpResponseContext(response, app, correspondingRequest)
// ..serializer = (app.serializer ?? json.encode)
// ..encoders.addAll(app.encoders ?? {}));
}
@override
@ -114,8 +124,8 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
response.headers.contentLength = length;
@override
void setHeader(HttpResponse response, String key, String value) =>
response.headers.set(key, value);
void setHeader(HttpResponse response, String key, String? value) =>
response.headers.set(key, value!);
@override
void setStatusCode(HttpResponse response, int value) =>

View file

@ -7,40 +7,40 @@ import 'package:http_parser/http_parser.dart';
import '../core/core.dart';
/// An implementation of [RequestContext] that wraps a [HttpRequest].
class HttpRequestContext extends RequestContext<HttpRequest> {
Container _container;
MediaType _contentType;
HttpRequest _io;
String _override, _path;
class HttpRequestContext extends RequestContext<HttpRequest?> {
Container? _container;
MediaType? _contentType;
HttpRequest? _io;
String? _override, _path;
@override
Container get container => _container;
Container? get container => _container;
@override
MediaType get contentType {
MediaType? get contentType {
return _contentType;
}
@override
List<Cookie> get cookies {
return rawRequest.cookies;
return rawRequest!.cookies;
}
@override
HttpHeaders get headers {
return rawRequest.headers;
return rawRequest!.headers;
}
@override
String get hostname {
return rawRequest.headers.value('host');
String? get hostname {
return rawRequest!.headers.value('host');
}
/// The underlying [HttpRequest] instance underneath this context.
HttpRequest get rawRequest => _io;
HttpRequest? get rawRequest => _io;
@override
Stream<List<int>> get body => _io;
Stream<List<int>>? get body => _io;
@override
String get method {
@ -49,34 +49,34 @@ class HttpRequestContext extends RequestContext<HttpRequest> {
@override
String get originalMethod {
return rawRequest.method;
return rawRequest!.method;
}
@override
String get path {
String? get path {
return _path;
}
@override
InternetAddress get remoteAddress {
return rawRequest.connectionInfo.remoteAddress;
return rawRequest!.connectionInfo!.remoteAddress;
}
@override
HttpSession get session {
return rawRequest.session;
return rawRequest!.session;
}
@override
Uri get uri {
return rawRequest.uri;
return rawRequest!.uri;
}
/// Magically transforms an [HttpRequest] into a [RequestContext].
static Future<HttpRequestContext> from(
HttpRequest request, Angel app, String path) {
HttpRequestContext ctx = HttpRequestContext()
.._container = app.container.createChild();
.._container = app.container!.createChild();
String override = request.method;

View file

@ -12,11 +12,11 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
/// The underlying [HttpResponse] under this instance.
@override
final HttpResponse rawResponse;
Angel app;
Angel? app;
LockableBytesBuilder _buffer;
LockableBytesBuilder? _buffer;
final HttpRequestContext _correspondingRequest;
final HttpRequestContext? _correspondingRequest;
bool _isDetached = false, _isClosed = false, _streamInitialized = false;
HttpResponseContext(this.rawResponse, this.app, [this._correspondingRequest]);
@ -28,7 +28,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
}
@override
RequestContext get correspondingRequest {
RequestContext? get correspondingRequest {
return _correspondingRequest;
}
@ -41,10 +41,10 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
bool get isBuffered => _buffer != null;
@override
BytesBuilder get buffer => _buffer;
BytesBuilder? get buffer => _buffer;
@override
void addError(Object error, [StackTrace stackTrace]) {
void addError(Object error, [StackTrace? stackTrace]) {
rawResponse.addError(error, stackTrace);
super.addError(error, stackTrace);
}
@ -54,10 +54,10 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
_buffer = LockableBytesBuilder();
}
Iterable<String> __allowedEncodings;
Iterable<String>? __allowedEncodings;
Iterable<String> get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest.headers
Iterable<String>? get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest!.headers!
.value('accept-encoding')
?.split(',')
?.map((s) => s.trim())
@ -89,7 +89,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
headers.forEach(rawResponse.headers.set);
if (headers.containsKey('content-length')) {
rawResponse.contentLength = int.tryParse(headers['content-length']) ??
rawResponse.contentLength = int.tryParse(headers['content-length']!) ??
rawResponse.contentLength;
}
@ -100,8 +100,8 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -134,8 +134,8 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -145,7 +145,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
}
if (encoder != null) {
output = encoders[key].bind(output);
output = encoders[key]!.bind(output);
break;
}
}
@ -165,8 +165,8 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -176,7 +176,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
}
if (encoder != null) {
data = encoders[key].convert(data);
data = encoders[key]!.convert(data);
break;
}
}
@ -186,7 +186,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
rawResponse.add(data);
}
} else {
buffer.add(data);
buffer!.add(data);
}
}
@ -203,7 +203,7 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
// this try/catch prevents a crash.
}
} else {
_buffer.lock();
_buffer!.lock();
}
_isClosed = true;

View file

@ -18,14 +18,14 @@ Future<SecureServerSocket> startSharedHttp2(
/// Adapts `package:http2`'s [ServerTransportConnection] to serve Angel.
class AngelHttp2 extends Driver<Socket, ServerTransportStream,
SecureServerSocket, Http2RequestContext, Http2ResponseContext> {
final ServerSettings settings;
AngelHttp _http;
final ServerSettings? settings;
late AngelHttp _http;
final StreamController<HttpRequest> _onHttp1 = StreamController();
final Map<String, MockHttpSession> _sessions = {};
final Uuid _uuid = Uuid();
_AngelHttp2ServerSocket _artificial;
_AngelHttp2ServerSocket? _artificial;
SecureServerSocket get socket => _artificial;
SecureServerSocket? get socket => _artificial;
AngelHttp2._(
Angel app,
@ -45,7 +45,7 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
}
factory AngelHttp2(Angel app, SecurityContext securityContext,
{bool useZone = true, bool allowHttp1 = false, ServerSettings settings}) {
{bool useZone = true, bool allowHttp1 = false, ServerSettings? settings}) {
return AngelHttp2.custom(app, securityContext, SecureServerSocket.bind,
allowHttp1: allowHttp1, settings: settings);
}
@ -57,7 +57,7 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
address, int port, SecurityContext ctx),
{bool useZone = true,
bool allowHttp1 = false,
ServerSettings settings}) {
ServerSettings? settings}) {
return AngelHttp2._(app, (address, port) {
var addr = address is InternetAddress
? address
@ -70,14 +70,14 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
Stream<HttpRequest> get onHttp1 => _onHttp1.stream;
@override
Future<SecureServerSocket> generateServer([address, int port]) async {
var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
Future<SecureServerSocket> generateServer([address, int? port]) async {
SecureServerSocket s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
return _artificial = _AngelHttp2ServerSocket(s, this);
}
@override
Future<SecureServerSocket> close() async {
await _artificial.close();
await _artificial!.close();
await _http?.close();
return await super.close();
}
@ -98,15 +98,15 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
@override
Future<Http2RequestContext> createRequestContext(
Socket request, ServerTransportStream response) {
return Http2RequestContext.from(response, request, app, _sessions, _uuid);
return Http2RequestContext.from(response, request, app!, _sessions, _uuid);
}
@override
Future<Http2ResponseContext> createResponseContext(
Socket request, ServerTransportStream response,
[Http2RequestContext correspondingRequest]) async {
[Http2RequestContext? correspondingRequest]) async {
return Http2ResponseContext(app, response, correspondingRequest)
..encoders.addAll(app.encoders);
..encoders.addAll(app!.encoders);
}
@override
@ -128,8 +128,8 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
}
@override
void setHeader(ServerTransportStream response, String key, String value) {
response.sendHeaders([Header.ascii(key, value)]);
void setHeader(ServerTransportStream response, String key, String? value) {
response.sendHeaders([Header.ascii(key, value!)]);
}
@override
@ -140,8 +140,8 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
@override
Uri get uri => Uri(
scheme: 'https',
host: server.address.address,
port: server.port != 443 ? server.port : null);
host: server!.address.address,
port: server!.port != 443 ? server!.port : null);
@override
void writeStringToResponse(ServerTransportStream response, String value) {
@ -173,8 +173,8 @@ class _FakeServerSocket extends Stream<Socket> implements ServerSocket {
int get port => angel.port;
@override
StreamSubscription<Socket> listen(void Function(Socket event) onData,
{Function onError, void Function() onDone, bool cancelOnError}) {
StreamSubscription<Socket> listen(void Function(Socket event)? onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
return _ctrl.stream.listen(onData,
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
}
@ -185,8 +185,8 @@ class _AngelHttp2ServerSocket extends Stream<SecureSocket>
final SecureServerSocket socket;
final AngelHttp2 driver;
final _ctrl = StreamController<SecureSocket>();
_FakeServerSocket _fake;
StreamSubscription _sub;
late _FakeServerSocket _fake;
StreamSubscription? _sub;
_AngelHttp2ServerSocket(this.socket, this.driver) {
_fake = _FakeServerSocket(this);
@ -208,7 +208,7 @@ class _AngelHttp2ServerSocket extends Stream<SecureSocket>
},
onDone: _ctrl.close,
onError: (e, st) {
driver.app.logger.warning(
driver.app!.logger!.warning(
'HTTP/2 incoming connection failure: ', e, st as StackTrace);
},
);
@ -227,10 +227,10 @@ class _AngelHttp2ServerSocket extends Stream<SecureSocket>
@override
StreamSubscription<SecureSocket> listen(
void Function(SecureSocket event) onData,
{Function onError,
void Function() onDone,
bool cancelOnError}) {
void Function(SecureSocket event)? onData,
{Function? onError,
void Function()? onDone,
bool? cancelOnError}) {
return _ctrl.stream.listen(onData,
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
}

View file

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:angel_container/src/container.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:http2/transport.dart';
import 'package:mock_request/mock_request.dart';
import 'package:uuid/uuid.dart';
@ -10,16 +11,16 @@ import 'package:uuid/uuid.dart';
final RegExp _comma = RegExp(r',\s*');
final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
class Http2RequestContext extends RequestContext<ServerTransportStream> {
class Http2RequestContext extends RequestContext<ServerTransportStream?> {
final StreamController<List<int>> _body = StreamController();
final Container container;
List<Cookie> _cookies;
HttpHeaders _headers;
String _method, _override, _path;
HttpSession _session;
Socket _socket;
ServerTransportStream _stream;
Uri _uri;
List<Cookie>? _cookies;
HttpHeaders? _headers;
String? _method, _override, _path;
HttpSession? _session;
late Socket _socket;
ServerTransportStream? _stream;
Uri? _uri;
Http2RequestContext._(this.container);
@ -33,7 +34,7 @@ class Http2RequestContext extends RequestContext<ServerTransportStream> {
Map<String, MockHttpSession> sessions,
Uuid uuid) {
var c = Completer<Http2RequestContext>();
var req = Http2RequestContext._(app.container.createChild())
var req = Http2RequestContext._(app.container!.createChild())
..app = app
.._socket = socket
.._stream = stream;
@ -124,7 +125,7 @@ class Http2RequestContext extends RequestContext<ServerTransportStream> {
// Apply session
var dartSessId =
cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null);
cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID');
if (dartSessId == null) {
dartSessId = Cookie('DARTSESSID', uuid.v4());
@ -132,23 +133,23 @@ class Http2RequestContext extends RequestContext<ServerTransportStream> {
req._session = sessions.putIfAbsent(
dartSessId.value,
() => MockHttpSession(id: dartSessId.value),
() => MockHttpSession(id: dartSessId!.value),
);
return c.future;
}
@override
List<Cookie> get cookies => _cookies;
List<Cookie>? get cookies => _cookies;
/// The underlying HTTP/2 [ServerTransportStream].
ServerTransportStream get stream => _stream;
ServerTransportStream? get stream => _stream;
@override
Uri get uri => _uri;
Uri? get uri => _uri;
@override
HttpSession get session {
HttpSession? get session {
return _session;
}
@ -156,25 +157,25 @@ class Http2RequestContext extends RequestContext<ServerTransportStream> {
InternetAddress get remoteAddress => _socket.remoteAddress;
@override
String get path {
String? get path {
return _path;
}
@override
String get originalMethod {
String? get originalMethod {
return _method;
}
@override
String get method {
String? get method {
return _override ?? _method;
}
@override
String get hostname => _headers.value('host');
String? get hostname => _headers!.value('host');
@override
HttpHeaders get headers => _headers;
HttpHeaders? get headers => _headers;
@override
Future close() {
@ -183,5 +184,5 @@ class Http2RequestContext extends RequestContext<ServerTransportStream> {
}
@override
ServerTransportStream get rawRequest => _stream;
ServerTransportStream? get rawRequest => _stream;
}

View file

@ -6,24 +6,24 @@ import 'package:http2/transport.dart';
import 'http2_request_context.dart';
class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
final Angel app;
final Angel? app;
final ServerTransportStream stream;
ServerTransportStream get rawResponse => stream;
LockableBytesBuilder _buffer;
LockableBytesBuilder? _buffer;
final Http2RequestContext _req;
final Http2RequestContext? _req;
bool _isDetached = false,
_isClosed = false,
_streamInitialized = false,
_isPush = false;
Uri _targetUri;
Uri? _targetUri;
Http2ResponseContext(this.app, this.stream, this._req) {
_targetUri = _req.uri;
_targetUri = _req!.uri;
}
final List<Http2ResponseContext> _pushes = [];
@ -43,9 +43,9 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
}
@override
RequestContext get correspondingRequest => _req;
RequestContext? get correspondingRequest => _req;
Uri get targetUri => _targetUri;
Uri? get targetUri => _targetUri;
@override
bool get isOpen {
@ -56,10 +56,10 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
bool get isBuffered => _buffer != null;
@override
BytesBuilder get buffer => _buffer;
BytesBuilder? get buffer => _buffer;
@override
void addError(Object error, [StackTrace stackTrace]) {
void addError(Object error, [StackTrace? stackTrace]) {
super.addError(error, stackTrace);
}
@ -78,8 +78,8 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -98,11 +98,11 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
// Add all normal headers
for (var key in this.headers.keys) {
headers.add(Header.ascii(key.toLowerCase(), this.headers[key]));
headers.add(Header.ascii(key.toLowerCase(), this.headers[key]!));
}
// Persist session ID
cookies.add(Cookie('DARTSESSID', _req.session.id));
cookies.add(Cookie('DARTSESSID', _req!.session!.id));
// Send all cookies
for (var cookie in cookies) {
@ -113,10 +113,10 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
return _streamInitialized = true;
}
Iterable<String> __allowedEncodings;
Iterable<String>? __allowedEncodings;
Iterable<String> get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest.headers
Iterable<String>? get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest!.headers!
.value('accept-encoding')
?.split(',')
?.map((s) => s.trim())
@ -138,8 +138,8 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -149,7 +149,7 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
}
if (encoder != null) {
output = encoders[key].bind(output);
output = encoders[key]!.bind(output);
break;
}
}
@ -169,8 +169,8 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
if (!_isClosed) {
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
for (var encodingName in _allowedEncodings!) {
Converter<List<int>, List<int>>? encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
@ -180,7 +180,7 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
}
if (encoder != null) {
data = encoders[key].convert(data);
data = encoders[key]!.convert(data);
break;
}
}
@ -190,7 +190,7 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
stream.sendData(data);
}
} else {
buffer.add(data);
buffer!.add(data);
}
}
@ -208,7 +208,7 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
/// Pushes a resource to the client.
Http2ResponseContext push(String path,
{Map<String, String> headers = const {}, String method = 'GET'}) {
var targetUri = _req.uri.replace(path: path);
var targetUri = _req!.uri!.replace(path: path);
var h = <Header>[
Header.ascii(':authority', targetUri.authority),
@ -218,7 +218,7 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
];
for (var key in headers.keys) {
h.add(Header.ascii(key, headers[key]));
h.add(Header.ascii(key, headers[key]!));
}
var s = stream.push(h);

View file

@ -12,7 +12,7 @@ abstract class SafeCtrl<T> {
void add(T event);
void addError(error, [StackTrace stackTrace]);
void addError(error, [StackTrace? stackTrace]);
Future close();
@ -20,16 +20,16 @@ abstract class SafeCtrl<T> {
}
class _SingleSafeCtrl<T> implements SafeCtrl<T> {
StreamController<T> _stream;
late StreamController<T> _stream;
bool _hasListener = false, _initialized = false;
_InitCallback _initializer;
_InitCallback? _initializer;
_SingleSafeCtrl() {
_stream = StreamController<T>(onListen: () {
_hasListener = true;
if (!_initialized && _initializer != null) {
_initializer();
_initializer!();
_initialized = true;
}
}, onPause: () {
@ -50,8 +50,8 @@ class _SingleSafeCtrl<T> implements SafeCtrl<T> {
}
@override
void addError(error, [StackTrace stackTrace]) {
if (_hasListener) _stream.addError(error, stackTrace);
void addError(error, [StackTrace? stackTrace]) {
if (_hasListener) _stream.addError(error as Object, stackTrace);
}
@override
@ -65,7 +65,7 @@ class _SingleSafeCtrl<T> implements SafeCtrl<T> {
if (!_hasListener) {
_initializer = callback;
} else {
_initializer();
_initializer!();
_initialized = true;
}
}
@ -73,17 +73,17 @@ class _SingleSafeCtrl<T> implements SafeCtrl<T> {
}
class _BroadcastSafeCtrl<T> implements SafeCtrl<T> {
StreamController<T> _stream;
late StreamController<T> _stream;
int _listeners = 0;
bool _initialized = false;
_InitCallback _initializer;
_InitCallback? _initializer;
_BroadcastSafeCtrl() {
_stream = StreamController<T>.broadcast(onListen: () {
_listeners++;
if (!_initialized && _initializer != null) {
_initializer();
_initializer!();
_initialized = true;
}
}, onCancel: () {
@ -100,8 +100,8 @@ class _BroadcastSafeCtrl<T> implements SafeCtrl<T> {
}
@override
void addError(error, [StackTrace stackTrace]) {
if (_listeners > 0) _stream.addError(error, stackTrace);
void addError(error, [StackTrace? stackTrace]) {
if (_listeners > 0) _stream.addError(error as Object, stackTrace);
}
@override
@ -115,7 +115,7 @@ class _BroadcastSafeCtrl<T> implements SafeCtrl<T> {
if (_listeners <= 0) {
_initializer = callback;
} else {
_initializer();
_initializer!();
_initialized = true;
}
}

View file

@ -2,25 +2,25 @@ import 'package:angel_container/angel_container.dart';
final RegExp straySlashes = RegExp(r'(^/+)|(/+$)');
T matchingAnnotation<T>(List<ReflectedInstance> metadata) {
T? matchingAnnotation<T>(List<ReflectedInstance> metadata) {
for (ReflectedInstance metaDatum in metadata) {
if (metaDatum.type.reflectedType == T) {
return metaDatum.reflectee as T;
return metaDatum.reflectee as T?;
}
}
return null;
}
T getAnnotation<T>(obj, Reflector reflector) {
T? getAnnotation<T>(obj, Reflector? reflector) {
if (reflector == null) {
return null;
} else {
if (obj is Function) {
var methodMirror = reflector.reflectFunction(obj);
var methodMirror = reflector.reflectFunction(obj)!;
return matchingAnnotation<T>(methodMirror.annotations);
} else {
var classMirror = reflector.reflectClass(obj.runtimeType as Type);
var classMirror = reflector.reflectClass(obj.runtimeType as Type)!;
return matchingAnnotation<T>(classMirror.annotations);
}
}

View file

@ -5,7 +5,7 @@ author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework
publish_to: none
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'
dependencies:
angel_container:
git:
@ -60,6 +60,7 @@ dependencies:
string_scanner: ^1.1.0
tuple: ^2.0.0
uuid: ^3.0.1
collection: ^1.15.0
dev_dependencies:
http: ^0.13.1
io: ^1.0.0

View file

@ -8,9 +8,9 @@ import 'package:test/test.dart';
import 'pretty_log.dart';
void main() {
http.IOClient client;
AngelHttp driver;
Logger logger;
late http.IOClient client;
late AngelHttp driver;
late Logger logger;
setUp(() async {
client = http.IOClient();

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
@ -44,7 +45,7 @@ main() {
});
group('disallow null', () {
RequestContext req;
late RequestContext req;
setUp(() async {
req = await acceptContentTypes();
@ -58,8 +59,9 @@ main() {
Future<RequestContext> acceptContentTypes(
[Iterable<String> contentTypes = const []]) {
var headerString = contentTypes.isEmpty ? null : contentTypes.join(',');
var rq = MockHttpRequest('GET', ENDPOINT);
var headerString =
contentTypes.isEmpty ? ContentType.html : contentTypes.join(',');
var rq = MockHttpRequest('GET', ENDPOINT, persistentConnection: false);
rq.headers.set('accept', headerString);
rq.close();
var app = Angel(reflector: MirrorsReflector());

View file

@ -3,7 +3,7 @@ import 'package:test/test.dart';
main() {
test('custom methods', () async {
var svc = AnonymousService<String, String>(
var svc = AnonymousService<String?, String?>(
index: ([p]) async => ['index'],
read: (id, [p]) async => 'read',
create: (data, [p]) async => 'create',

View file

@ -13,9 +13,10 @@ void main() {
Future<RequestContext> request(
{bool asJson = true,
bool parse = true,
Map<String, dynamic> bodyFields,
List bodyList}) async {
var rq = MockHttpRequest('POST', Uri(path: '/'));
Map<String, dynamic>? bodyFields,
List? bodyList}) async {
var rq =
MockHttpRequest('POST', Uri(path: '/'), persistentConnection: false);
if (bodyFields != null) {
if (asJson) {
@ -115,16 +116,16 @@ void main() {
}
class Todo {
String text;
bool completed;
String? text;
bool? completed;
Todo({this.text, this.completed});
static Todo fromMap(Map m) =>
Todo(text: m['text'] as String, completed: m['complete'] as bool);
static Todo fromMap(Map? m) =>
Todo(text: m!['text'] as String?, completed: m['complete'] as bool?);
}
class TodoCodec extends Codec<Todo, Map> {
class TodoCodec extends Codec<Todo, Map?> {
@override
Converter<Map, Todo> get decoder => TodoDecoder();

View file

@ -4,8 +4,8 @@ import 'package:angel_framework/angel_framework.dart';
import 'package:matcher/matcher.dart';
class Todo extends Model {
String text;
String over;
String? text;
String? over;
Todo({this.text, this.over});

View file

@ -70,39 +70,39 @@ bool bar(RequestContext req, ResponseContext res) {
}
main() {
Angel app;
TodoController todoController;
NoExposeController noExposeCtrl;
HttpServer server;
Angel? app;
late TodoController todoController;
late NoExposeController noExposeCtrl;
late HttpServer server;
http.Client client = http.Client();
String url;
String? url;
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}';
@ -122,7 +122,7 @@ main() {
var app = Angel(reflector: MirrorsReflector());
app.get(
'/foo',
ioc(({String bar}) {
ioc(({String? bar}) {
return 2;
}, optional: ['bar']));
var rq = MockHttpRequest('GET', Uri(path: 'foo'));
@ -171,7 +171,7 @@ main() {
group('optional expose', () {
test('removes suffixes from controller names', () {
expect(noExposeCtrl.mountPoint.path, 'no_expose');
expect(noExposeCtrl.mountPoint!.path, 'no_expose');
});
test('mounts correct routes', () {
@ -182,7 +182,7 @@ main() {
test('mounts correct methods', () {
void expectMethod(String name, String method) {
expect(noExposeCtrl.routeMappings[name].method, method);
expect(noExposeCtrl.routeMappings[name]!.method, method);
}
expectMethod('getIndex', 'GET');
@ -194,7 +194,7 @@ main() {
test('mounts correct paths', () {
void expectPath(String name, String path) {
expect(noExposeCtrl.routeMappings[name].path, path);
expect(noExposeCtrl.routeMappings[name]!.path, path);
}
expectPath('getIndex', '/');

View file

@ -5,7 +5,7 @@ import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart';
void main() {
AngelHttp http;
late AngelHttp http;
setUp(() async {
var app = Angel();

View file

@ -15,31 +15,31 @@ final String TEXT = "make your bed";
final String OVER = "never";
main() {
Angel app;
http.Client client;
HttpServer server;
String url;
Angel? app;
http.Client? client;
late HttpServer server;
String? url;
setUp(() async {
app = Angel(reflector: MirrorsReflector());
client = http.Client();
// Inject some todos
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();
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(({Errand singleton, Todo foo, RequestContext req}) =>
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}";
@ -48,7 +48,7 @@ main() {
tearDown(() async {
app = null;
url = null;
client.close();
client!.close();
client = null;
await server.close(force: true);
});
@ -71,33 +71,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 text = await json.decode(response.body) as String;
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 text = await json.decode(response.body) as String;
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'}));
});
}
@ -137,7 +137,7 @@ class Foo {
class Errand {
Todo todo;
String get text => todo.text;
String? get text => todo.text;
Errand(this.todo);
}

View file

@ -17,7 +17,7 @@ Future<List<int>> getBody(MockHttpResponse rs) async {
}
main() {
Angel app;
late Angel app;
setUp(() {
app = Angel(reflector: MirrorsReflector());
@ -43,7 +43,7 @@ main() {
void encodingTests(Angel getApp()) {
group('encoding', () {
Angel app;
AngelHttp http;
late AngelHttp http;
setUp(() {
app = getApp();

View file

@ -7,10 +7,10 @@ import 'package:http/http.dart' as http;
import 'package:test/test.dart';
main() {
Angel app;
http.Client client;
HttpServer server;
String url;
Angel? app;
http.Client? client;
late HttpServer server;
String? url;
setUp(() async {
app = Angel(reflector: MirrorsReflector())
@ -25,13 +25,13 @@ main() {
tearDown(() async {
app = null;
url = null;
client.close();
client!.close();
client = null;
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'}));

View file

@ -6,7 +6,7 @@ main() async {
var zone = Zone.current.fork(
specification: ZoneSpecification(print: (self, parent, zone, line) {
if (line == 'null') {
parent.print(zone, cyan.wrap(StackTrace.current.toString()));
parent.print(zone, cyan.wrap(StackTrace.current.toString())!);
}
}),
);

View file

@ -13,26 +13,26 @@ main() {
'Content-Type': 'application/json'
};
Angel app;
HttpServer server;
String url;
http.Client client;
HookedService todoService;
Angel? app;
late HttpServer server;
String? url;
http.Client? client;
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');
todoService.beforeAllStream().listen((e) {
todoService!.beforeAllStream().listen((e) {
print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}');
});
app.errorHandler = (e, req, res) {
throw e.error;
app!.errorHandler = (e, req, res) {
throw e.error as Object;
};
server = await AngelHttp(app).startServer();
@ -43,7 +43,7 @@ main() {
await server.close(force: true);
app = null;
url = null;
client.close();
client!.close();
client = null;
todoService = null;
});
@ -52,20 +52,20 @@ main() {
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 +73,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 +82,7 @@ main() {
});
test("cancel after", () async {
todoService.afterIndexed
todoService!.afterIndexed
..listen((HookedServiceEvent event) async {
// Hooks can be Futures ;)
event.cancel([
@ -93,20 +93,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,14 +114,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);
@ -137,8 +138,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

View file

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart' hide Header;
import 'package:angel_framework/http2.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:http/src/multipart_file.dart' as http;
import 'package:http/src/multipart_request.dart' as http;
import 'package:http/io_client.dart';
@ -22,10 +23,10 @@ Stream<List<int>> jfkStream() {
void main() {
var client = Http2Client();
IOClient h1c;
late IOClient h1c;
Angel app;
AngelHttp2 http2;
Uri serverRoot;
late AngelHttp2 http2;
late Uri serverRoot;
setUp(() async {
app = Angel(reflector: MirrorsReflector())..encoders['gzip'] = gzip.encoder;
@ -63,11 +64,13 @@ void main() {
app.post('/upload', (req, res) async {
await req.parseBody();
var body = req.bodyAsMap, files = req.uploadedFiles;
var file = files.firstWhere((f) => f.name == 'file');
var body = req.bodyAsMap;
List<UploadedFile> files = req.uploadedFiles ?? [];
UploadedFile file = files.firstWhereOrNull((f) => f.name == 'file')!;
return [
await file.data.map((l) => l.length).reduce((a, b) => a + b),
file.contentType.mimeType,
file.contentType!.mimeType,
body
];
});
@ -103,13 +106,13 @@ void main() {
http2 = AngelHttp2(app, ctx, allowHttp1: true);
var server = await http2.startServer();
SecureServerSocket server = await http2.startServer();
serverRoot = Uri.parse('https://127.0.0.1:${server.port}');
});
tearDown(() async {
await http2.close();
await h1c.close();
h1c.close();
});
test('buffered response', () async {
@ -167,7 +170,7 @@ void main() {
test('json response', () async {
var response = await client.get(serverRoot.replace(path: '/json'));
expect(response.body, json.encode({'foo': 'bar'}));
expect(ContentType.parse(response.headers['content-type']).mimeType,
expect(ContentType.parse(response.headers['content-type']!).mimeType,
ContentType.json.mimeType);
});

View file

@ -89,11 +89,11 @@ class Http2Client extends BaseClient {
var closed = await readResponse(stream, headers, body);
return StreamedResponse(
Stream.fromIterable([body.takeBytes()]),
int.parse(headers[':status']),
int.parse(headers[':status']!),
headers: headers,
isRedirect: headers.containsKey('location'),
contentLength: headers.containsKey('content-length')
? int.parse(headers['content-length'])
? int.parse(headers['content-length']!)
: null,
request: request,
reasonPhrase: null,

View file

@ -24,7 +24,7 @@ void main() {
parameterMetaTests() {
Angel app;
AngelHttp http;
late AngelHttp http;
setUp(() {
app = Angel(reflector: MirrorsReflector());

View file

@ -9,8 +9,8 @@ import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart';
main() {
Angel app;
AngelHttp http;
late Angel app;
late AngelHttp http;
setUp(() {
app = Angel(reflector: MirrorsReflector())

View file

@ -7,10 +7,10 @@ import 'package:test/test.dart';
import 'pretty_log.dart';
void main() {
http.IOClient client;
AngelHttp driver;
Logger logger;
StringBuffer buf;
late http.IOClient client;
late AngelHttp driver;
late Logger logger;
late StringBuffer buf;
setUp(() async {
buf = StringBuffer();

View file

@ -20,7 +20,7 @@ testMiddlewareMetadata(RequestContext req, ResponseContext res) async {
class QueryService extends Service {
@override
@Middleware([interceptor])
read(id, [Map params]) async => params;
read(id, [Map? params]) async => params;
}
void interceptor(RequestContext req, ResponseContext res) {
@ -35,19 +35,19 @@ bool interceptService(RequestContext req, ResponseContext res) {
}
main() {
Angel app;
Angel nested;
Angel todos;
String url;
http.Client client;
Angel? app;
Angel? nested;
Angel? todos;
String? url;
http.Client? client;
setUp(() async {
app = Angel(reflector: MirrorsReflector());
nested = Angel(reflector: MirrorsReflector());
todos = Angel(reflector: MirrorsReflector());
[app, nested, todos].forEach((Angel app) {
app.logger = Logger('routing_test')
[app, nested, todos].forEach((Angel? app) {
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));
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('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,35 +104,35 @@ 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);
client = http.Client();
var server = await AngelHttp(app).startServer('127.0.0.1', 0);
HttpServer server = await AngelHttp(app).startServer('127.0.0.1', 0);
url = "http://${server.address.host}:${server.port}";
});
tearDown(() async {
await app.close();
await app!.close();
app = null;
nested = null;
todos = null;
client.close();
client!.close();
client = null;
url = null;
});
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 +141,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 +160,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 +193,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"');
});

View file

@ -8,10 +8,10 @@ import 'package:http_parser/http_parser.dart';
import 'package:test/test.dart';
main() {
Angel app;
http.Client client;
HttpServer server;
String url;
Angel? app;
http.Client? client;
late HttpServer server;
String? url;
setUp(() async {
app = Angel(reflector: MirrorsReflector())
@ -29,17 +29,17 @@ main() {
tearDown(() async {
app = null;
url = null;
client.close();
client!.close();
client = null;
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'));
});

View file

@ -51,7 +51,7 @@ main() {
await app.errorHandler(e, req, res);
await http.sendResponse(rq, rs, req, res);
expect(
ContentType.parse(rs.headers.value('content-type')).mimeType,
ContentType.parse(rs.headers.value('content-type')!).mimeType,
'text/html',
);
expect(rs.statusCode, e.statusCode);
@ -116,8 +116,8 @@ main() {
var http = AngelHttp(app);
app.responseFinalizers
.add((req, res) => throw AngelHttpException.forbidden());
RequestContext req;
ResponseContext res;
late RequestContext req;
late ResponseContext res;
setUp(() async {
var rq = MockHttpRequest('GET', $foo);
@ -155,8 +155,8 @@ main() {
});
group('handleAngelHttpException', () {
Angel app;
AngelHttp http;
late Angel app;
late AngelHttp http;
setUp(() async {
app = Angel(reflector: MirrorsReflector());
@ -182,7 +182,7 @@ main() {
(http.handleRequest(rq));
await rq.response.toList();
expect(rq.response.statusCode, 403);
expect(rq.response.headers.contentType.mimeType, 'application/json');
expect(rq.response.headers.contentType!.mimeType, 'application/json');
});
test('can throw in finalizer', () async {
@ -192,7 +192,7 @@ main() {
(http.handleRequest(rq));
await rq.response.toList();
expect(rq.response.statusCode, 403);
expect(rq.response.headers.contentType.mimeType, 'application/json');
expect(rq.response.headers.contentType!.mimeType, 'application/json');
});
test('can send html', () async {

View file

@ -3,7 +3,7 @@ import 'package:test/test.dart';
void main() {
MapService inner;
Service<String, Todo> mapped;
late Service<String?, Todo> mapped;
setUp(() {
inner = MapService();
@ -22,8 +22,8 @@ void main() {
});
group('after create', () {
Todo result;
String id;
late Todo result;
String? id;
setUp(() async {
result = await mapped.create(Todo(text: 'hello', complete: false));
@ -55,16 +55,16 @@ void main() {
}
class Todo {
final String id, text;
final bool complete;
final String? id, text;
final bool? complete;
Todo({this.id, this.text, this.complete});
static Todo fromMap(Map<String, dynamic> json) {
return Todo(
id: json['id'] as String,
text: json['text'] as String,
complete: json['complete'] as bool);
id: json['id'] as String?,
text: json['text'] as String?,
complete: json['complete'] as bool?);
}
static Map<String, dynamic> toMap(Todo model) {

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
@ -7,8 +9,8 @@ import 'package:stack_trace/stack_trace.dart';
import 'package:test/test.dart';
class Todo extends Model {
String text;
String over;
String? text;
String? over;
}
main() {
@ -16,35 +18,35 @@ main() {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
Angel app;
MapService service;
String url;
http.Client client;
Angel? app;
late MapService service;
String? url;
http.Client? client;
setUp(() async {
app = Angel(reflector: MirrorsReflector())
..use('/todos', service = MapService())
..errorHandler = (e, req, res) {
if (e.error != null) print('Whoops: ${e.error}');
if (e.stackTrace != null) print(Chain.forTrace(e.stackTrace).terse);
if (e.stackTrace != null) print(Chain.forTrace(e.stackTrace!).terse);
};
var server = await AngelHttp(app).startServer();
HttpServer server = await AngelHttp(app).startServer();
client = http.Client();
url = "http://${server.address.host}:${server.port}";
});
tearDown(() async {
await app.close();
await app!.close();
app = null;
url = null;
client.close();
client!.close();
client = null;
});
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);
@ -53,7 +55,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);
@ -63,9 +65,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);
@ -74,12 +76,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 as Map<String, String>, 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);
@ -88,12 +90,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 as Map<String, String>, 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);
@ -108,18 +110,18 @@ main() {
await service.create({'baz': 'quux'})
];
var ids = items.map((m) => m['id'] as String).toList();
var ids = items.map((m) => m['id'] as String?).toList();
expect(await service.readMany(ids), items);
});
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_);
@ -127,7 +129,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);
});
});

View file

@ -14,8 +14,8 @@ import 'package:test/test.dart';
import 'encoders_buffer_test.dart' show encodingTests;
main() {
Angel app;
AngelHttp http;
late Angel app;
late AngelHttp http;
setUp(() {
app = Angel(reflector: MirrorsReflector());

View file

@ -4,7 +4,7 @@ import 'package:test/test.dart';
main() {
test('default view generator', () async {
var app = Angel();
var view = await app.viewGenerator('foo', {'bar': 'baz'});
var view = await app.viewGenerator!('foo', {'bar': 'baz'});
expect(view, contains('No view engine'));
});
}