platform/lib/src/core/injection.dart

186 lines
6.1 KiB
Dart
Raw Normal View History

2017-09-24 19:43:14 +00:00
part of angel_framework.http.request_context;
const List<Type> _primitiveTypes = const [String, int, num, double, Null];
/// Shortcut for calling [preInject], and then [handleContained].
///
/// Use this to instantly create a request handler for a DI-enabled method.
2018-06-23 03:29:38 +00:00
RequestHandler createDynamicHandler(Function handler,
2017-09-24 19:43:14 +00:00
{Iterable<String> optional: const []}) {
var injection = preInject(handler);
injection.optional.addAll(optional ?? []);
return handleContained(handler, injection);
}
2017-10-10 16:55:42 +00:00
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.
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-06-23 03:29:38 +00:00
RequestHandler handleContained(Function handler, InjectionRequest injection) {
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
.map((r) => resolveInjection(r, injection, req, res, true)));
2017-09-24 19:43:14 +00:00
injection.named.forEach((k, v) {
var name = new Symbol(k);
2017-10-10 16:55:42 +00:00
named[name] = resolveInjection([k, v], injection, req, res, false);
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
}
2017-10-10 16:55:42 +00:00
final TypeMirror _Parameter = reflectType(Parameter);
2017-09-24 19:43:14 +00:00
/// Predetermines what needs to be injected for a handler to run.
InjectionRequest preInject(Function handler) {
var injection = new InjectionRequest();
ClosureMirror closureMirror = reflect(handler);
if (closureMirror.function.parameters.isEmpty) return injection;
// Load parameters
for (var parameter in closureMirror.function.parameters) {
var name = MirrorSystem.getName(parameter.simpleName);
var type = parameter.type.reflectedType;
2017-10-10 16:55:42 +00:00
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,
);
}
2017-09-24 19:43:14 +00:00
if (!parameter.isNamed) {
if (parameter.isOptional) injection.optional.add(name);
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;
}