platform/lib/src/core/injection.dart

194 lines
6.2 KiB
Dart
Raw Normal View History

2017-09-24 19:43:14 +00:00
part of angel_framework.http.request_context;
2018-08-20 20:53:30 +00:00
const List<Type> _primitiveTypes = [String, int, num, double, Null];
2017-09-24 19:43:14 +00:00
/// Shortcut for calling [preInject], and then [handleContained].
///
/// Use this to instantly create a request handler for a DI-enabled method.
2018-08-21 02:44:32 +00:00
///
/// Calling [ioc] also auto-serializes the result of a [handler].
2018-08-20 20:53:30 +00:00
RequestHandler ioc(Function handler, {Iterable<String> optional: const []}) {
2018-08-21 14:22:41 +00:00
InjectionRequest injection;
RequestHandler contained;
2018-08-21 02:44:32 +00:00
return (req, res) {
2018-08-21 14:22:41 +00:00
if (injection == null) {
injection = preInject(handler, req.app.container.reflector);
injection.optional.addAll(optional ?? []);
contained = handleContained(handler, injection);
}
2018-08-21 02:44:32 +00:00
return req.app.executeHandler(contained, req, res);
};
2017-09-24 19:43:14 +00:00
}
2017-10-10 16:55:42 +00:00
resolveInjection(requirement, InjectionRequest injection, RequestContext req,
2018-10-21 08:00:49 +00:00
ResponseContext res, bool throwOnUnresolved,
[Container container]) {
2017-10-10 16:55:42 +00:00
var propFromApp;
2018-10-21 08:00:39 +00:00
container ??= req?.container ?? res?.app?.container;
2017-10-10 16:55:42 +00:00
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];
2018-08-20 20:53:30 +00:00
} else if ((propFromApp = req.app.findProperty(requirement)) != null)
2017-10-10 16:55:42 +00:00
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.app.configuration.containsKey(key) ||
_primitiveTypes.contains(type)) {
2018-10-21 08:00:49 +00:00
return resolveInjection(
key, injection, req, res, throwOnUnresolved, container);
2017-10-10 16:55:42 +00:00
} else
2018-10-21 08:00:49 +00:00
return resolveInjection(
type, injection, req, res, throwOnUnresolved, container);
2017-10-10 16:55:42 +00:00
} else if (requirement is Type && requirement != dynamic) {
2018-10-21 08:00:39 +00:00
return container.make(requirement);
2017-10-10 16:55:42 +00:00
} 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.
2017-12-06 14:46:35 +00:00
bool suitableForInjection(
RequestContext req, ResponseContext res, InjectionRequest injection) {
2017-10-10 16:55:42 +00:00
return injection.parameters.values.any((p) {
if (p.match == null) return false;
var value = p.getValue(req);
return value == p.match;
});
}
2017-09-24 19:43:14 +00:00
/// Handles a request with a DI-enabled handler.
2018-10-21 08:00:39 +00:00
RequestHandler handleContained(Function handler, InjectionRequest injection,
[Container container]) {
2018-06-08 07:06:26 +00:00
return (RequestContext req, ResponseContext res) {
2017-12-06 14:46:35 +00:00
if (injection.parameters.isNotEmpty &&
injection.parameters.values.any((p) => p.match != null) &&
2018-06-08 07:06:26 +00:00
!suitableForInjection(req, res, injection))
return new Future.value(true);
2017-09-24 19:43:14 +00:00
2017-10-10 16:55:42 +00:00
List args = [];
2017-09-24 19:43:14 +00:00
Map<Symbol, dynamic> named = {};
2017-10-10 16:55:42 +00:00
args.addAll(injection.required
2018-10-21 08:00:39 +00:00
.map((r) => resolveInjection(r, injection, req, res, true, container)));
2017-09-24 19:43:14 +00:00
injection.named.forEach((k, v) {
var name = new Symbol(k);
2018-10-21 08:00:39 +00:00
named[name] =
resolveInjection([k, v], injection, req, res, false, container);
2017-09-24 19:43:14 +00:00
});
2018-06-08 07:06:26 +00:00
return Function.apply(handler, args, named);
2017-09-24 19:43:14 +00:00
};
}
/// Contains a list of the data required for a DI-enabled method to run.
///
/// This improves performance by removing the necessity to reflect a method
/// every time it is requested.
///
/// Regular request handlers can also skip DI entirely, lowering response time
/// and memory use.
class InjectionRequest {
/// Optional, typed data that can be passed to a DI-enabled method.
final Map<String, Type> named;
/// A list of the arguments required for a DI-enabled method to run.
final List required;
/// A list of the arguments that can be null in a DI-enabled method.
final List<String> optional;
2017-10-10 16:55:42 +00:00
/// Extended parameter definitions.
final Map<String, Parameter> parameters;
const InjectionRequest.constant(
{this.named: const {},
this.required: const [],
this.optional: const [],
this.parameters: const {}});
2017-09-24 19:43:14 +00:00
InjectionRequest()
: named = {},
required = [],
2017-10-10 16:55:42 +00:00
optional = [],
parameters = {};
2017-09-24 19:43:14 +00:00
}
/// Predetermines what needs to be injected for a handler to run.
2018-08-21 14:22:41 +00:00
InjectionRequest preInject(Function handler, Reflector reflector) {
2017-09-24 19:43:14 +00:00
var injection = new InjectionRequest();
2018-08-21 14:22:41 +00:00
var closureMirror = reflector.reflectFunction(handler);
2017-09-24 19:43:14 +00:00
2018-08-21 14:22:41 +00:00
if (closureMirror.parameters.isEmpty) return injection;
2017-09-24 19:43:14 +00:00
// Load parameters
2018-08-21 14:22:41 +00:00
for (var parameter in closureMirror.parameters) {
var name = parameter.name;
2017-09-24 19:43:14 +00:00
var type = parameter.type.reflectedType;
2018-08-21 14:22:41 +00:00
var _Parameter = reflector.reflectType(Parameter);
var p = parameter.annotations
2017-10-10 16:55:42 +00:00
.firstWhere((m) => m.type.isAssignableTo(_Parameter),
orElse: () => null)
?.reflectee as Parameter;
2018-07-09 14:40:06 +00:00
//print(p);
2017-10-10 16:55:42 +00:00
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,
);
}
2017-09-24 19:43:14 +00:00
if (!parameter.isNamed) {
2018-08-21 14:22:41 +00:00
if (!parameter.isRequired) injection.optional.add(name);
2017-09-24 19:43:14 +00:00
if (type == RequestContext || type == ResponseContext) {
injection.required.add(type);
} else if (name == 'req') {
injection.required.add(RequestContext);
} else if (name == 'res') {
injection.required.add(ResponseContext);
} else if (type == dynamic) {
injection.required.add(name);
} else {
injection.required.add([name, type]);
}
} else {
injection.named[name] = type;
}
}
return injection;
}