diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
index 695dedd4..98dd117d 100644
--- a/.idea/libraries/Dart_Packages.xml
+++ b/.idea/libraries/Dart_Packages.xml
@@ -26,14 +26,14 @@
-
+
-
+
@@ -47,7 +47,7 @@
-
+
@@ -131,7 +131,7 @@
-
+
@@ -145,14 +145,14 @@
-
+
-
+
@@ -222,7 +222,7 @@
-
+
@@ -250,7 +250,7 @@
-
+
@@ -271,14 +271,14 @@
-
+
-
+
@@ -327,7 +327,7 @@
-
+
@@ -362,7 +362,7 @@
-
+
@@ -383,7 +383,7 @@
-
+
@@ -397,7 +397,7 @@
-
+
@@ -418,14 +418,14 @@
-
+
-
+
@@ -435,10 +435,10 @@
-
-
+
+
-
+
@@ -450,10 +450,10 @@
-
+
-
-
+
+
@@ -463,36 +463,36 @@
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
-
+
+
diff --git a/.idea/runConfigurations/injects_session_or_throws_in_parameter_meta_test_dart.xml b/.idea/runConfigurations/injects_session_or_throws_in_parameter_meta_test_dart.xml
new file mode 100644
index 00000000..17a6c854
--- /dev/null
+++ b/.idea/runConfigurations/injects_session_or_throws_in_parameter_meta_test_dart.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ed7a7c9c..51b975ac 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -2,24 +2,18 @@
-
-
-
+
+
+
-
-
-
-
+
+
-
-
+
-
-
-
@@ -45,11 +39,19 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -57,44 +59,55 @@
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -102,57 +115,13 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -169,19 +138,6 @@
- print(
- handleaNGE
- stderr
- _AFTERp
- _afterProc
- _beforeP
- _fatalE
- fatalError
- fatale
- FATAL
- error ??
- sendRes
- print
.name
logger
handleRequ
@@ -199,6 +155,19 @@
addStr
Stopwatc
_controllers
+ pipeline
+ handle
+ uncaught
+ header
+ creat
+ crea
+ run(
+ /*
+ runG
+ catchError
+ query
+ value == n
+ handleCont
_isClosed
@@ -215,6 +184,8 @@
if (_isClosed && !_useStream)
logger?.warning
JSON.decode
+ query
+ cookie
C:\Users\thosa\Source\Angel\framework\lib
@@ -236,9 +207,6 @@
@@ -297,9 +268,9 @@
DEFINITION_ORDER
-
+
-
+
@@ -320,8 +291,10 @@
-
+
+
+
@@ -347,27 +320,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
@@ -401,6 +362,11 @@
+
+
+
+
+
@@ -516,6 +482,11 @@
+
+
+
+
+
@@ -542,6 +513,10 @@
+
+
+
+
@@ -566,37 +541,41 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -647,7 +626,11 @@
-
+
+
+
+
+
1481237183504
@@ -919,39 +902,39 @@
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -963,31 +946,31 @@
-
+
+
-
+
-
+
-
-
+
@@ -996,9 +979,9 @@
-
+
@@ -1040,51 +1023,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1171,7 +1109,6 @@
-
@@ -1210,20 +1147,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1231,79 +1154,27 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
@@ -1312,74 +1183,34 @@
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
-
+
@@ -1392,10 +1223,27 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1404,7 +1252,7 @@
-
+
@@ -1413,6 +1261,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 857134df..a8acd86f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 1.1.0-alpha+6
+* Added `@Parameter()` annotations, with support for pattern matching.
+
# 1.1.0-alpha+5
* Closed [#166](https://github.com/angel-dart/framework/issues/166), killing any hanging `Stopwatch` instances when streaming.
* Removed `AngelPlugin` and `AngelMiddleware`, as well as the `@proxy` annotations from `Angel` and `RequestContext`.
diff --git a/lib/src/http/injection.dart b/lib/src/http/injection.dart
index 4b89c7b5..7f4e5f39 100644
--- a/lib/src/http/injection.dart
+++ b/lib/src/http/injection.dart
@@ -12,76 +12,84 @@ RequestHandler createDynamicHandler(handler,
return handleContained(handler, injection);
}
+resolveInjection(requirement, InjectionRequest injection, RequestContext req,
+ ResponseContext res, bool throwOnUnresolved) {
+ var propFromApp;
+
+ if (requirement == RequestContext) {
+ return req;
+ } else if (requirement == ResponseContext) {
+ return res;
+ } else if (requirement is String &&
+ injection.parameters.containsKey(requirement)) {
+ var param = injection.parameters[requirement];
+ var value = param.getValue(req);
+ if (value == null && param.required != false) throw param.error;
+ return value;
+ } else if (requirement is String) {
+ if (req.params.containsKey(requirement)) {
+ return req.params[requirement];
+ } else if (req._injections.containsKey(requirement))
+ return req._injections[requirement];
+ else if (req.properties.containsKey(requirement))
+ return req.properties[requirement];
+ else if ((propFromApp = req.app.findProperty(requirement)) != null)
+ return propFromApp;
+ else if (injection.optional.contains(requirement))
+ return null;
+ else if (throwOnUnresolved) {
+ throw new ArgumentError(
+ "Cannot resolve parameter '$requirement' within handler.");
+ }
+ } else if (requirement is List &&
+ requirement.length == 2 &&
+ requirement.first is String &&
+ requirement.last is Type) {
+ String key = requirement.first;
+ Type type = requirement.last;
+ if (req.params.containsKey(key) ||
+ req._injections.containsKey(key) ||
+ req.properties.containsKey(key) ||
+ req.app.configuration.containsKey(key) ||
+ _primitiveTypes.contains(type)) {
+ return resolveInjection(key, injection, req, res, throwOnUnresolved);
+ } else
+ return resolveInjection(type, injection, req, res, throwOnUnresolved);
+ } else if (requirement is Type && requirement != dynamic) {
+ if (req._injections.containsKey(requirement))
+ return req._injections[requirement];
+ else
+ return req.app.container.make(requirement);
+ } else if (throwOnUnresolved) {
+ throw new ArgumentError(
+ '$requirement cannot be injected into a request handler.');
+ }
+}
+
+/// Checks if an [InjectionRequest] can be sufficiently executed within the current request/response context.
+bool suitableForInjection(RequestContext req, ResponseContext res, InjectionRequest injection) {
+ return injection.parameters.values.any((p) {
+ if (p.match == null) return false;
+ var value = p.getValue(req);
+ return value == p.match;
+ });
+}
+
/// Handles a request with a DI-enabled handler.
RequestHandler handleContained(handler, InjectionRequest injection) {
return (RequestContext req, ResponseContext res) async {
+ if (injection.parameters.isNotEmpty && injection.parameters.values.any((p) => p.match != null) && !suitableForInjection(req, res, injection))
+ return true;
+
List args = [];
- void inject(requirement) {
- var propFromApp;
-
- if (requirement == RequestContext) {
- args.add(req);
- } else if (requirement == ResponseContext) {
- args.add(res);
- } else if (requirement is String) {
- if (req.params.containsKey(requirement)) {
- args.add(req.params[requirement]);
- } else if (req._injections.containsKey(requirement))
- args.add(req._injections[requirement]);
- else if (req.properties.containsKey(requirement))
- args.add(req.properties[requirement]);
- else if ((propFromApp = req.app.findProperty(requirement)) != null)
- args.add(propFromApp);
- else if (injection.optional.contains(requirement))
- args.add(null);
- else {
- throw new ArgumentError(
- "Cannot resolve parameter '$requirement' within handler.");
- }
- } else if (requirement is List &&
- requirement.length == 2 &&
- requirement.first is String &&
- requirement.last is Type) {
- String key = requirement.first;
- Type type = requirement.last;
- if (req.params.containsKey(key) ||
- req._injections.containsKey(key) ||
- req.properties.containsKey(key) ||
- req.app.configuration.containsKey(key) ||
- _primitiveTypes.contains(type)) {
- inject(key);
- } else
- inject(type);
- } else if (requirement is Type && requirement != dynamic) {
- if (req._injections.containsKey(requirement))
- args.add(req._injections[requirement]);
- else
- args.add(req.app.container.make(requirement));
- } else {
- throw new ArgumentError(
- '$requirement cannot be injected into a request handler.');
- }
- }
-
Map named = {};
- injection.required.forEach(inject);
+ args.addAll(injection.required
+ .map((r) => resolveInjection(r, injection, req, res, true)));
injection.named.forEach((k, v) {
var name = new Symbol(k);
- if (req.params.containsKey(k))
- named[name] = v;
- else if (req._injections.containsKey(k))
- named[name] = v;
- else if (req._injections.containsKey(v) && v != dynamic)
- named[name] = v;
- else {
- try {
- named[name] = req.app.container.make(v);
- } catch (e) {
- named[name] = null;
- }
- }
+ named[name] = resolveInjection([k, v], injection, req, res, false);
});
var result = Function.apply(handler, args, named);
@@ -106,14 +114,24 @@ class InjectionRequest {
/// A list of the arguments that can be null in a DI-enabled method.
final List optional;
- const InjectionRequest.constant({this.named, this.required, this.optional});
+ /// Extended parameter definitions.
+ final Map parameters;
+
+ const InjectionRequest.constant(
+ {this.named: const {},
+ this.required: const [],
+ this.optional: const [],
+ this.parameters: const {}});
InjectionRequest()
: named = {},
required = [],
- optional = [];
+ optional = [],
+ parameters = {};
}
+final TypeMirror _Parameter = reflectType(Parameter);
+
/// Predetermines what needs to be injected for a handler to run.
InjectionRequest preInject(Function handler) {
var injection = new InjectionRequest();
@@ -127,6 +145,22 @@ InjectionRequest preInject(Function handler) {
var name = MirrorSystem.getName(parameter.simpleName);
var type = parameter.type.reflectedType;
+ var p = parameter.metadata
+ .firstWhere((m) => m.type.isAssignableTo(_Parameter),
+ orElse: () => null)
+ ?.reflectee as Parameter;
+ if (p != null) {
+ injection.parameters[name] = new Parameter(
+ cookie: p.cookie,
+ header: p.header,
+ query: p.query,
+ session: p.session,
+ match: p.match,
+ defaultValue: p.defaultValue,
+ required: parameter.isNamed ? false : p.required != false,
+ );
+ }
+
if (!parameter.isNamed) {
if (parameter.isOptional) injection.optional.add(name);
diff --git a/lib/src/http/metadata.dart b/lib/src/http/metadata.dart
index c2180efe..0fd38063 100644
--- a/lib/src/http/metadata.dart
+++ b/lib/src/http/metadata.dart
@@ -1,6 +1,8 @@
library angel_framework.http.metadata;
import 'hooked_service.dart' show HookedServiceEventListener;
+import 'package:angel_http_exception/angel_http_exception.dart';
+import 'request_context.dart';
/// Annotation to map middleware onto a handler.
class Middleware {
@@ -31,3 +33,105 @@ class Expose {
this.as: null,
this.allowNull: const []});
}
+
+/// Used to apply special dependency injections or functionality to a function parameter.
+class Parameter {
+ /// Inject the value of a request cookie.
+ final String cookie;
+
+ /// Inject the value of a request header.
+ final String header;
+
+ /// Inject the value of a key from the session.
+ final String session;
+
+ /// Inject the value of a key from the query.
+ final String query;
+
+ /// Only execute the handler if the value of this parameter matches the given value.
+ final match;
+
+ /// Specify a default value.
+ final defaultValue;
+
+ /// If `true` (default), then an error will be thrown if this parameter is not present.
+ final bool required;
+
+ const Parameter(
+ {this.cookie,
+ this.query,
+ this.header,
+ this.session,
+ this.match,
+ this.defaultValue,
+ this.required});
+
+ /// Returns an error that can be thrown when the parameter is not present.
+ get error {
+ if (cookie?.isNotEmpty == true)
+ return new AngelHttpException.badRequest(
+ message: 'Missing required cookie "$cookie".');
+ if (header?.isNotEmpty == true)
+ return new AngelHttpException.badRequest(
+ message: 'Missing required header "$header".');
+ if (query?.isNotEmpty == true)
+ return new AngelHttpException.badRequest(
+ message: 'Missing required query parameter "$query".');
+ if (session?.isNotEmpty == true)
+ return new StateError(
+ 'Session does not contain required key "$session".');
+ }
+
+ /// 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 ??
+ defaultValue;
+ if (header?.isNotEmpty == true)
+ return req.headers.value(header) ?? defaultValue;
+ if (session?.isNotEmpty == true)
+ return req.session[session] ?? defaultValue;
+ if (query?.isNotEmpty == true) return req.query[query] ?? defaultValue;
+ return defaultValue;
+ }
+}
+
+/// Shortcut for declaring a request header [Parameter].
+class Header extends Parameter {
+ const Header(String header, {match, defaultValue, bool required: true})
+ : super(
+ header: header,
+ match: match,
+ defaultValue: defaultValue,
+ required: required);
+}
+
+/// Shortcut for declaring a request session [Parameter].
+class Session extends Parameter {
+ const Session(String session, {match, defaultValue, bool required: true})
+ : super(
+ session: session,
+ match: match,
+ defaultValue: defaultValue,
+ required: required);
+}
+
+/// Shortcut for declaring a request query [Parameter].
+class Query extends Parameter {
+ const Query(String query, {match, defaultValue, bool required: true})
+ : super(
+ query: query,
+ match: match,
+ defaultValue: defaultValue,
+ required: required);
+}
+
+/// Shortcut for declaring a request cookie [Parameter].
+class CookieValue extends Parameter {
+ const CookieValue(String cookie, {match, defaultValue, bool required: true})
+ : super(
+ cookie: cookie,
+ match: match,
+ defaultValue: defaultValue,
+ required: required);
+}
diff --git a/lib/src/http/request_context.dart b/lib/src/http/request_context.dart
index f200d3c3..886b30ba 100644
--- a/lib/src/http/request_context.dart
+++ b/lib/src/http/request_context.dart
@@ -3,8 +3,10 @@ library angel_framework.http.request_context;
import 'dart:async';
import 'dart:io';
import 'dart:mirrors';
+import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:body_parser/body_parser.dart';
import 'package:charcode/charcode.dart';
+import 'metadata.dart';
import 'response_context.dart';
import 'routable.dart';
import 'server.dart' show Angel;
diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart
index 1c2f1b9c..240aaf5c 100644
--- a/lib/src/http/server.dart
+++ b/lib/src/http/server.dart
@@ -347,7 +347,8 @@ class Angel extends AngelBase {
}
res.statusCode = e.statusCode;
- await errorHandler(e, req, res);
+ var result = await errorHandler(e, req, res);
+ await executeHandler(result, req, res);
res.end();
return await sendResponse(request, req, res,
ignoreFinalizers: ignoreFinalizers == true);
@@ -360,7 +361,7 @@ class Angel extends AngelBase {
var zoneSpec = await createZoneForRequest(request, req, res);
var zone = Zone.current.fork(specification: zoneSpec);
- return zone.run(() async {
+ return zone.runGuarded(() async {
String requestedUrl;
// Faster way to get path
@@ -387,6 +388,7 @@ class Angel extends AngelBase {
Router r = isProduction ? (_flattened ??= flatten(this)) : this;
var resolved =
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
+
return new Tuple3(
new MiddlewarePipeline(resolved),
resolved.fold