Compare commits
32 commits
0453c67177
...
fc3131c0f9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fc3131c0f9 | ||
![]() |
df6ca22d97 | ||
![]() |
85a4517440 | ||
![]() |
9dc7b8940a | ||
![]() |
0819c9c8cd | ||
![]() |
ab9ebde517 | ||
![]() |
22ade7f699 | ||
![]() |
8c031ec740 | ||
![]() |
85262ccca7 | ||
![]() |
9c2297cedf | ||
![]() |
0fbb79e4d7 | ||
![]() |
932f83f0f8 | ||
![]() |
2ce4607366 | ||
![]() |
e73c854b64 | ||
![]() |
0de4f37270 | ||
![]() |
107266ca67 | ||
![]() |
c61e33a07e | ||
![]() |
34b31a7059 | ||
![]() |
40eadbe408 | ||
![]() |
103b6e2553 | ||
![]() |
bba949c296 | ||
![]() |
c904c2bec3 | ||
![]() |
dee0cbd03a | ||
![]() |
433da78695 | ||
![]() |
a07bd666c2 | ||
![]() |
f4e567c046 | ||
![]() |
81bb7bdd5e | ||
![]() |
d920180845 | ||
![]() |
972c0424e1 | ||
![]() |
786224caf5 | ||
![]() |
f3b0fac943 | ||
![]() |
d690877afc |
105 changed files with 8102 additions and 5724 deletions
|
@ -1,39 +0,0 @@
|
|||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
|
@ -1,21 +0,0 @@
|
|||
// This is the barrel file for the ioc_container package
|
||||
|
||||
// Export the main Container class
|
||||
export 'src/container.dart';
|
||||
|
||||
// Export other important classes and utilities
|
||||
export 'src/bound_method.dart';
|
||||
export 'src/contextual_binding_builder.dart';
|
||||
export 'src/entry_not_found_exception.dart';
|
||||
export 'src/rewindable_generator.dart';
|
||||
export 'src/util.dart';
|
||||
|
||||
// Export any interfaces or contracts if they exist
|
||||
// export 'src/contracts/container_contract.dart';
|
||||
|
||||
// Export any additional exceptions
|
||||
|
||||
// Export any additional utilities or helpers
|
||||
// export 'src/helpers/parameter_resolver.dart';
|
||||
|
||||
// You can add more exports as needed for your package
|
|
@ -1,261 +0,0 @@
|
|||
import 'dart:mirrors';
|
||||
import 'package:ioc_container/src/container.dart';
|
||||
import 'package:ioc_container/src/util.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
class BoundMethod {
|
||||
static dynamic call(Container container, dynamic callback,
|
||||
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
||||
if (callback is String) {
|
||||
if (defaultMethod == null && _hasInvokeMethod(callback)) {
|
||||
defaultMethod = '__invoke';
|
||||
}
|
||||
return _callClass(container, callback, parameters, defaultMethod);
|
||||
}
|
||||
|
||||
if (callback is List && callback.length == 2) {
|
||||
var instance = container.make(callback[0].toString());
|
||||
var method = callback[1].toString();
|
||||
return _callBoundMethod(container, [instance, method], () {
|
||||
throw BindingResolutionException(
|
||||
'Failed to call method: $method on ${instance.runtimeType}');
|
||||
}, parameters);
|
||||
}
|
||||
|
||||
if (callback is Function) {
|
||||
return _callBoundMethod(container, callback, () {
|
||||
throw BindingResolutionException('Failed to call function');
|
||||
}, parameters);
|
||||
}
|
||||
|
||||
if (_isCallableWithAtSign(callback)) {
|
||||
return _callClass(container, callback, parameters, defaultMethod);
|
||||
}
|
||||
|
||||
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
|
||||
}
|
||||
|
||||
static dynamic _callBoundMethod(
|
||||
Container container, dynamic callback, Function defaultCallback,
|
||||
[List<dynamic> parameters = const []]) {
|
||||
if (callback is List && callback.length == 2) {
|
||||
var instance = callback[0];
|
||||
var method = callback[1];
|
||||
if (instance is String) {
|
||||
instance = container.make(instance);
|
||||
}
|
||||
if (method is String) {
|
||||
if (instance is Function && method == '__invoke') {
|
||||
return Function.apply(instance, parameters);
|
||||
}
|
||||
if (instance is Map && instance.containsKey(method)) {
|
||||
// Handle the case where instance is a Map and method is a key
|
||||
var result = instance[method];
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
var instanceMirror = reflect(instance);
|
||||
var methodSymbol = Symbol(method);
|
||||
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
|
||||
var dependencies =
|
||||
_getMethodDependencies(container, instance, method, parameters);
|
||||
var result = Function.apply(
|
||||
instanceMirror.getField(methodSymbol).reflectee, dependencies);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
} else if (method == '__invoke' && instance is Function) {
|
||||
return Function.apply(instance, parameters);
|
||||
} else if (instance is Type) {
|
||||
// Handle static methods
|
||||
var classMirror = reflectClass(instance);
|
||||
if (classMirror.staticMembers.containsKey(Symbol(method))) {
|
||||
var dependencies =
|
||||
_getMethodDependencies(container, instance, method, parameters);
|
||||
var result = Function.apply(
|
||||
classMirror.getField(Symbol(method)).reflectee, dependencies);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
}
|
||||
// Try to find the method in the global scope
|
||||
var globalMethod = _findGlobalMethod(method);
|
||||
if (globalMethod != null) {
|
||||
var result = Function.apply(globalMethod, parameters);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
'Method $method not found on ${instance.runtimeType}');
|
||||
} else if (method is Function) {
|
||||
var result = Function.apply(method, [instance, ...parameters]);
|
||||
return result is Function ? Function.apply(result, parameters) : result;
|
||||
}
|
||||
} else if (callback is Function) {
|
||||
var result = Function.apply(callback, parameters);
|
||||
return result is Function ? Function.apply(result, parameters) : result;
|
||||
}
|
||||
return Util.unwrapIfClosure(defaultCallback);
|
||||
}
|
||||
|
||||
static dynamic _callClass(Container container, String target,
|
||||
List<dynamic> parameters, String? defaultMethod) {
|
||||
var segments = target.split('@');
|
||||
|
||||
var className = segments[0];
|
||||
var method = segments.length == 2 ? segments[1] : defaultMethod;
|
||||
|
||||
method ??= '__invoke';
|
||||
|
||||
var instance = container.make(className);
|
||||
if (instance is String) {
|
||||
// If instance is still a string, it might be a global function
|
||||
if (container.bound(instance)) {
|
||||
return container.make(instance);
|
||||
}
|
||||
// If it's not bound, treat it as a string value
|
||||
return instance;
|
||||
}
|
||||
if (instance is Function && method == '__invoke') {
|
||||
return Function.apply(instance, parameters);
|
||||
}
|
||||
return _callBoundMethod(container, [instance, method], () {
|
||||
throw BindingResolutionException(
|
||||
'Failed to call method: $method on $className');
|
||||
}, parameters);
|
||||
}
|
||||
|
||||
static Function? _findGlobalMethod(String methodName) {
|
||||
var currentMirror = currentMirrorSystem();
|
||||
for (var library in currentMirror.libraries.values) {
|
||||
if (library.declarations.containsKey(Symbol(methodName))) {
|
||||
var declaration = library.declarations[Symbol(methodName)]!;
|
||||
if (declaration is MethodMirror && declaration.isTopLevel) {
|
||||
return library.getField(Symbol(methodName)).reflectee as Function;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List _getMethodDependencies(Container container, dynamic instance,
|
||||
dynamic method, List<dynamic> parameters) {
|
||||
var dependencies = <dynamic>[];
|
||||
var reflector = _getCallReflector(instance, method);
|
||||
|
||||
if (reflector != null) {
|
||||
for (var parameter in reflector.parameters) {
|
||||
_addDependencyForCallParameter(
|
||||
container, parameter, parameters, dependencies);
|
||||
}
|
||||
} else if (instance is Map &&
|
||||
method is String &&
|
||||
instance.containsKey(method)) {
|
||||
// If instance is a Map and method is a key, return the value
|
||||
return [instance[method]];
|
||||
} else {
|
||||
// If we couldn't get a reflector, just return the original parameters
|
||||
return parameters;
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
static dynamic _resolveInstance(Container container, dynamic instance) {
|
||||
if (instance is String) {
|
||||
return container.make(instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
static bool _hasInvokeMethod(String className) {
|
||||
ClassMirror? classMirror = _getClassMirror(className);
|
||||
return classMirror?.declarations[Symbol('__invoke')] != null;
|
||||
}
|
||||
|
||||
static String _normalizeMethod(List callback) {
|
||||
var className = callback[0] is String
|
||||
? callback[0]
|
||||
: MirrorSystem.getName(
|
||||
reflectClass(callback[0].runtimeType).simpleName);
|
||||
return '$className@${callback[1]}';
|
||||
}
|
||||
|
||||
static MethodMirror? _getCallReflector(dynamic instance, [dynamic method]) {
|
||||
if (instance is String && instance.contains('::')) {
|
||||
var parts = instance.split('::');
|
||||
instance = parts[0];
|
||||
method = parts[1];
|
||||
} else if (instance is! Function && instance is! List && method == null) {
|
||||
method = '__invoke';
|
||||
}
|
||||
|
||||
if (instance is List && method == null) {
|
||||
instance = instance[0];
|
||||
method = instance[1];
|
||||
}
|
||||
|
||||
if (method != null) {
|
||||
var classMirror =
|
||||
reflectClass(instance is Type ? instance : instance.runtimeType);
|
||||
var methodSymbol = Symbol(method);
|
||||
return classMirror.instanceMembers[methodSymbol] ??
|
||||
classMirror.staticMembers[methodSymbol];
|
||||
} else if (instance is Function) {
|
||||
return (reflect(instance) as ClosureMirror).function;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void _addDependencyForCallParameter(Container container,
|
||||
ParameterMirror parameter, List<dynamic> parameters, List dependencies) {
|
||||
var pendingDependencies = <dynamic>[];
|
||||
var paramName = MirrorSystem.getName(parameter.simpleName);
|
||||
|
||||
if (parameters.any((p) => p is Map && p.containsKey(paramName))) {
|
||||
var param =
|
||||
parameters.firstWhere((p) => p is Map && p.containsKey(paramName));
|
||||
pendingDependencies.add(param[paramName]);
|
||||
parameters.remove(param);
|
||||
} else if (parameter.type.reflectedType != dynamic) {
|
||||
var className = parameter.type.reflectedType.toString();
|
||||
if (parameters.any((p) => p is Map && p.containsKey(className))) {
|
||||
var param =
|
||||
parameters.firstWhere((p) => p is Map && p.containsKey(className));
|
||||
pendingDependencies.add(param[className]);
|
||||
parameters.remove(param);
|
||||
} else if (parameter.isNamed) {
|
||||
var variadicDependencies = container.make(className);
|
||||
pendingDependencies.addAll(variadicDependencies is List
|
||||
? variadicDependencies
|
||||
: [variadicDependencies]);
|
||||
} else {
|
||||
pendingDependencies.add(container.make(className));
|
||||
}
|
||||
} else if (parameter.hasDefaultValue) {
|
||||
pendingDependencies.add(parameter.defaultValue?.reflectee);
|
||||
} else if (!parameter.isOptional &&
|
||||
!parameters.any((p) => p is Map && p.containsKey(paramName))) {
|
||||
throw Exception(
|
||||
"Unable to resolve dependency [$parameter] in class ${parameter.owner?.qualifiedName ?? 'Unknown'}");
|
||||
}
|
||||
|
||||
dependencies.addAll(pendingDependencies);
|
||||
}
|
||||
|
||||
static bool _isCallableWithAtSign(dynamic callback) {
|
||||
return callback is String && callback.contains('@');
|
||||
}
|
||||
|
||||
static ClassMirror? _getClassMirror(String className) {
|
||||
try {
|
||||
return reflectClass(className as Type);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,896 +0,0 @@
|
|||
import 'dart:mirrors';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:ioc_container/src/bound_method.dart';
|
||||
import 'package:ioc_container/src/contextual_binding_builder.dart';
|
||||
import 'package:ioc_container/src/entry_not_found_exception.dart';
|
||||
import 'package:ioc_container/src/util.dart';
|
||||
|
||||
class Container implements ContainerContract {
|
||||
static Container? _instance;
|
||||
|
||||
final Map<String, bool> _resolved = {};
|
||||
final Map<String, Map<String, dynamic>> _bindings = {};
|
||||
|
||||
final Map<String, Function> _methodBindings = {};
|
||||
|
||||
final Map<String, Object> _instances = {};
|
||||
final List<String> _scopedInstances = [];
|
||||
final Map<String, String> _aliases = {};
|
||||
final Map<String, List<String>> _abstractAliases = {};
|
||||
final Map<String, List<Function>> _extenders = {};
|
||||
final Map<String, List<String>> _tags = {};
|
||||
final List<String> _buildStack = [];
|
||||
final List<Map<String, dynamic>> _with = [];
|
||||
final Map<String, Map<String, dynamic>> _contextual = {};
|
||||
final Map<String, Function> _contextualAttributes = {};
|
||||
final Map<String, List<Function>> _reboundCallbacks = {};
|
||||
final List<Function> _globalBeforeResolvingCallbacks = [];
|
||||
final List<Function> _globalResolvingCallbacks = [];
|
||||
final List<Function> _globalAfterResolvingCallbacks = [];
|
||||
final Map<String, List<Function>> _beforeResolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _resolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _afterResolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _afterResolvingAttributeCallbacks = {};
|
||||
|
||||
Container();
|
||||
|
||||
static Container getInstance() {
|
||||
return _instance ??= Container();
|
||||
}
|
||||
|
||||
static void setInstance(Container? container) {
|
||||
_instance = container;
|
||||
}
|
||||
|
||||
Function wrap(Function callback, [List<dynamic> parameters = const []]) {
|
||||
return () => call(callback, parameters);
|
||||
}
|
||||
|
||||
dynamic refresh(String abstract, dynamic target, String method) {
|
||||
return rebinding(abstract, (Container container, dynamic instance) {
|
||||
Function.apply(target[method], [instance]);
|
||||
});
|
||||
}
|
||||
|
||||
dynamic rebinding(String abstract, Function callback) {
|
||||
abstract = getAlias(abstract);
|
||||
|
||||
_reboundCallbacks[abstract] ??= [];
|
||||
_reboundCallbacks[abstract]!.add(callback);
|
||||
|
||||
if (bound(abstract)) {
|
||||
return make(abstract);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void bindMethod(dynamic method, Function callback) {
|
||||
_methodBindings[_parseBindMethod(method)] = callback;
|
||||
}
|
||||
|
||||
String _parseBindMethod(dynamic method) {
|
||||
if (method is List && method.length == 2) {
|
||||
return '${method[0]}@${method[1]}';
|
||||
}
|
||||
return method.toString();
|
||||
}
|
||||
|
||||
bool hasMethodBinding(String method) {
|
||||
return _methodBindings.containsKey(method);
|
||||
}
|
||||
|
||||
dynamic callMethodBinding(String method, dynamic instance) {
|
||||
if (!hasMethodBinding(method)) {
|
||||
throw Exception("Method binding not found for $method");
|
||||
}
|
||||
return _methodBindings[method]!(instance);
|
||||
}
|
||||
|
||||
@override
|
||||
bool bound(String abstract) {
|
||||
return _bindings.containsKey(abstract) ||
|
||||
_instances.containsKey(abstract) ||
|
||||
isAlias(abstract);
|
||||
}
|
||||
|
||||
@override
|
||||
void alias(String abstract, String alias) {
|
||||
if (alias == abstract) {
|
||||
throw ArgumentError("[$abstract] is aliased to itself.");
|
||||
}
|
||||
|
||||
_aliases[alias] = abstract;
|
||||
|
||||
_abstractAliases[abstract] ??= [];
|
||||
_abstractAliases[abstract]!.add(alias);
|
||||
}
|
||||
|
||||
@override
|
||||
void tag(dynamic abstracts, String tag,
|
||||
[List<String> additionalTags = const []]) {
|
||||
var tags = [tag, ...additionalTags];
|
||||
var abstractList = abstracts is List ? abstracts : [abstracts];
|
||||
|
||||
for (var tag in tags) {
|
||||
if (!_tags.containsKey(tag)) {
|
||||
_tags[tag] = [];
|
||||
}
|
||||
|
||||
_tags[tag]!.addAll(abstractList.cast<String>());
|
||||
}
|
||||
|
||||
void forgetExtenders(String abstract) {
|
||||
abstract = getAlias(abstract);
|
||||
_extenders.remove(abstract);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<dynamic> tagged(String tag) {
|
||||
if (!_tags.containsKey(tag)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _tags[tag]!.map((abstract) => make(abstract));
|
||||
}
|
||||
|
||||
@override
|
||||
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
||||
_dropStaleInstances(abstract);
|
||||
|
||||
if (concrete == null) {
|
||||
concrete = abstract;
|
||||
}
|
||||
|
||||
// If concrete is not a function and not null, we store it directly
|
||||
if (concrete is! Function && concrete != null) {
|
||||
_bindings[abstract] = {'concrete': concrete, 'shared': shared};
|
||||
} else {
|
||||
// For functions or null, we wrap it in a closure
|
||||
_bindings[abstract] = {
|
||||
'concrete': (Container container) =>
|
||||
concrete is Function ? concrete(container) : concrete,
|
||||
'shared': shared
|
||||
};
|
||||
}
|
||||
|
||||
if (resolved(abstract)) {
|
||||
_rebound(abstract);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void bindIf(String abstract, dynamic concrete, {bool shared = false}) {
|
||||
if (!bound(abstract)) {
|
||||
bind(abstract, concrete, shared: shared);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void singleton(String abstract, [dynamic concrete]) {
|
||||
bind(abstract, concrete ?? abstract, shared: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void singletonIf(String abstract, [dynamic concrete]) {
|
||||
if (!bound(abstract)) {
|
||||
singleton(abstract, concrete);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void scoped(String abstract, [dynamic concrete]) {
|
||||
_scopedInstances.add(abstract);
|
||||
singleton(abstract, concrete);
|
||||
}
|
||||
|
||||
@override
|
||||
void scopedIf(String abstract, [dynamic concrete]) {
|
||||
if (!bound(abstract)) {
|
||||
scoped(abstract, concrete);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void extend(String abstract, Function(dynamic service) closure) {
|
||||
abstract = getAlias(abstract);
|
||||
|
||||
if (_instances.containsKey(abstract)) {
|
||||
_instances[abstract] = closure(_instances[abstract]!);
|
||||
_rebound(abstract);
|
||||
} else {
|
||||
_extenders[abstract] ??= [];
|
||||
_extenders[abstract]!.add(closure);
|
||||
|
||||
if (resolved(abstract)) {
|
||||
_rebound(abstract);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
T instance<T>(String abstract, T instance) {
|
||||
_removeAbstractAlias(abstract);
|
||||
|
||||
bool isBound = bound(abstract);
|
||||
|
||||
_aliases.remove(abstract);
|
||||
|
||||
_instances[abstract] = instance as Object;
|
||||
|
||||
if (isBound) {
|
||||
_rebound(abstract);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@override
|
||||
void addContextualBinding(
|
||||
String concrete, String abstract, dynamic implementation) {
|
||||
_contextual[concrete] ??= {};
|
||||
_contextual[concrete]![getAlias(abstract)] = implementation;
|
||||
}
|
||||
|
||||
@override
|
||||
ContextualBindingBuilderContract when(dynamic concrete) {
|
||||
return ContextualBindingBuilder(
|
||||
this, Util.arrayWrap(concrete).map((c) => getAlias(c)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
void whenHasAttribute(String attribute, Function handler) {
|
||||
_contextualAttributes[attribute] = handler;
|
||||
}
|
||||
|
||||
@override
|
||||
Function factory(String abstract) {
|
||||
return () => make(abstract);
|
||||
}
|
||||
|
||||
@override
|
||||
void flush() {
|
||||
_aliases.clear();
|
||||
_resolved.clear();
|
||||
_bindings.clear();
|
||||
_instances.clear();
|
||||
_abstractAliases.clear();
|
||||
_scopedInstances.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
T make<T>(String abstract, [List<dynamic> parameters = const []]) {
|
||||
return resolve(abstract, parameters) as T;
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic call(dynamic callback,
|
||||
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
||||
return BoundMethod.call(this, callback, parameters, defaultMethod);
|
||||
}
|
||||
|
||||
@override
|
||||
bool resolved(String abstract) {
|
||||
if (isAlias(abstract)) {
|
||||
abstract = getAlias(abstract);
|
||||
}
|
||||
|
||||
return _resolved.containsKey(abstract) || _instances.containsKey(abstract);
|
||||
}
|
||||
|
||||
@override
|
||||
void beforeResolving(dynamic abstract, [Function? callback]) {
|
||||
if (abstract is String) {
|
||||
abstract = getAlias(abstract);
|
||||
}
|
||||
|
||||
if (abstract is Function && callback == null) {
|
||||
_globalBeforeResolvingCallbacks.add(abstract);
|
||||
} else {
|
||||
_beforeResolvingCallbacks[abstract.toString()] ??= [];
|
||||
if (callback != null) {
|
||||
_beforeResolvingCallbacks[abstract.toString()]!.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void resolving(dynamic abstract, [Function? callback]) {
|
||||
if (abstract is String) {
|
||||
abstract = getAlias(abstract);
|
||||
}
|
||||
|
||||
if (callback == null && abstract is Function) {
|
||||
_globalResolvingCallbacks.add(abstract);
|
||||
} else {
|
||||
_resolvingCallbacks[abstract.toString()] ??= [];
|
||||
if (callback != null) {
|
||||
_resolvingCallbacks[abstract.toString()]!.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void afterResolving(dynamic abstract, [Function? callback]) {
|
||||
if (abstract is String) {
|
||||
abstract = getAlias(abstract);
|
||||
}
|
||||
|
||||
if (abstract is Function && callback == null) {
|
||||
_globalAfterResolvingCallbacks.add(abstract);
|
||||
} else {
|
||||
_afterResolvingCallbacks[abstract.toString()] ??= [];
|
||||
if (callback != null) {
|
||||
_afterResolvingCallbacks[abstract.toString()]!.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void afterResolvingAttribute(
|
||||
Type attributeType, Function(dynamic, dynamic, Container) callback) {
|
||||
var attributeName = attributeType.toString();
|
||||
_afterResolvingAttributeCallbacks[attributeName] ??= [];
|
||||
_afterResolvingAttributeCallbacks[attributeName]!.add(callback);
|
||||
|
||||
// Ensure the attribute type is bound
|
||||
if (!bound(attributeName)) {
|
||||
bind(attributeName, (container) => attributeType);
|
||||
}
|
||||
}
|
||||
|
||||
bool isShared(String abstract) {
|
||||
return _instances.containsKey(abstract) ||
|
||||
(_bindings.containsKey(abstract) &&
|
||||
_bindings[abstract]!['shared'] == true);
|
||||
}
|
||||
|
||||
bool isAlias(String name) {
|
||||
return _aliases.containsKey(name);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic get(String id) {
|
||||
try {
|
||||
return resolve(id);
|
||||
} catch (e) {
|
||||
if (has(id) || e is CircularDependencyException) {
|
||||
rethrow;
|
||||
}
|
||||
throw EntryNotFoundException(id);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool has(String id) {
|
||||
return bound(id);
|
||||
}
|
||||
|
||||
void _dropStaleInstances(String abstract) {
|
||||
_instances.remove(abstract);
|
||||
_aliases.remove(abstract);
|
||||
}
|
||||
|
||||
void _removeAbstractAlias(String abstract) {
|
||||
if (!_aliases.containsKey(abstract)) return;
|
||||
|
||||
for (var entry in _abstractAliases.entries) {
|
||||
entry.value.remove(abstract);
|
||||
}
|
||||
}
|
||||
|
||||
void _rebound(String abstract) {
|
||||
var instance = make(abstract);
|
||||
|
||||
for (var callback in _getReboundCallbacks(abstract)) {
|
||||
callback(this, instance);
|
||||
}
|
||||
}
|
||||
|
||||
List<Function> _getReboundCallbacks(String abstract) {
|
||||
return _reboundCallbacks[abstract] ?? [];
|
||||
}
|
||||
|
||||
dynamic resolve(String abstract,
|
||||
[List<dynamic> parameters = const [], bool raiseEvents = true]) {
|
||||
abstract = getAlias(abstract);
|
||||
|
||||
if (_buildStack.contains(abstract)) {
|
||||
throw CircularDependencyException([..._buildStack, abstract]);
|
||||
}
|
||||
|
||||
_buildStack.add(abstract);
|
||||
|
||||
try {
|
||||
if (raiseEvents) {
|
||||
_fireBeforeResolvingCallbacks(abstract, parameters);
|
||||
}
|
||||
|
||||
var concrete = _getContextualConcrete(abstract);
|
||||
|
||||
var needsContextualBuild = parameters.isNotEmpty || concrete != null;
|
||||
|
||||
if (_instances.containsKey(abstract) && !needsContextualBuild) {
|
||||
return _instances[abstract];
|
||||
}
|
||||
|
||||
_with.add(Map<String, dynamic>.fromEntries(parameters
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => MapEntry(e.key.toString(), e.value))));
|
||||
|
||||
if (concrete == null) {
|
||||
concrete = _getConcrete(abstract);
|
||||
}
|
||||
|
||||
var object;
|
||||
if (_isBuildable(concrete, abstract)) {
|
||||
object = build(concrete);
|
||||
// If the result is still a function, execute it
|
||||
if (object is Function) {
|
||||
object = object(this);
|
||||
}
|
||||
} else {
|
||||
object = make(concrete);
|
||||
}
|
||||
|
||||
for (var extender in _getExtenders(abstract)) {
|
||||
object = extender(object);
|
||||
}
|
||||
|
||||
if (isShared(abstract) && !needsContextualBuild) {
|
||||
_instances[abstract] = object;
|
||||
}
|
||||
|
||||
if (raiseEvents) {
|
||||
_fireResolvingCallbacks(abstract, object);
|
||||
}
|
||||
|
||||
if (!needsContextualBuild) {
|
||||
_resolved[abstract] = true;
|
||||
}
|
||||
|
||||
_with.removeLast();
|
||||
|
||||
return object;
|
||||
} finally {
|
||||
_buildStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _getConcrete(String abstract) {
|
||||
if (_bindings.containsKey(abstract)) {
|
||||
return _bindings[abstract]!['concrete'];
|
||||
}
|
||||
|
||||
return abstract;
|
||||
}
|
||||
|
||||
bool _isBuildable(dynamic concrete, String abstract) {
|
||||
return concrete == abstract || concrete is Function;
|
||||
}
|
||||
|
||||
dynamic _resolveContextualAttribute(ContextualAttribute attribute) {
|
||||
var attributeType = attribute.runtimeType.toString();
|
||||
if (_contextualAttributes.containsKey(attributeType)) {
|
||||
return _contextualAttributes[attributeType]!(attribute, this);
|
||||
}
|
||||
// Try to find a handler based on superclasses
|
||||
for (var handler in _contextualAttributes.entries) {
|
||||
if (reflectClass(attribute.runtimeType)
|
||||
.isSubclassOf(reflectClass(handler.key as Type))) {
|
||||
return handler.value(attribute, this);
|
||||
}
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
"No handler registered for ContextualAttribute: $attributeType");
|
||||
}
|
||||
|
||||
void fireBeforeResolvingAttributeCallbacks(
|
||||
List<InstanceMirror> annotations, dynamic object) {
|
||||
for (var annotation in annotations) {
|
||||
if (annotation.reflectee is ContextualAttribute) {
|
||||
var instance = annotation.reflectee as ContextualAttribute;
|
||||
var attributeType = instance.runtimeType.toString();
|
||||
if (_beforeResolvingCallbacks.containsKey(attributeType)) {
|
||||
for (var callback in _beforeResolvingCallbacks[attributeType]!) {
|
||||
callback(instance, object, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fireAfterResolvingAttributeCallbacks(
|
||||
List<InstanceMirror> annotations, dynamic object) {
|
||||
for (var annotation in annotations) {
|
||||
var instance = annotation.reflectee;
|
||||
var attributeType = instance.runtimeType.toString();
|
||||
if (_afterResolvingAttributeCallbacks.containsKey(attributeType)) {
|
||||
for (var callback
|
||||
in _afterResolvingAttributeCallbacks[attributeType]!) {
|
||||
callback(instance, object, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void forgetInstance(String abstract) {
|
||||
_instances.remove(abstract);
|
||||
}
|
||||
|
||||
void forgetInstances() {
|
||||
_instances.clear();
|
||||
}
|
||||
|
||||
void forgetScopedInstances() {
|
||||
for (var scoped in _scopedInstances) {
|
||||
_instances.remove(scoped);
|
||||
}
|
||||
}
|
||||
|
||||
void forgetExtenders(String abstract) {
|
||||
abstract = getAlias(abstract);
|
||||
_extenders.remove(abstract);
|
||||
}
|
||||
|
||||
T makeScoped<T>(String abstract) {
|
||||
// This is similar to make, but ensures the instance is scoped
|
||||
var instance = make<T>(abstract);
|
||||
if (!_scopedInstances.contains(abstract)) {
|
||||
_scopedInstances.add(abstract);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
Map<String, Map<String, dynamic>> getBindings() {
|
||||
return Map.from(_bindings);
|
||||
}
|
||||
|
||||
dynamic build(dynamic concrete) {
|
||||
if (concrete is Function) {
|
||||
_buildStack.add(concrete.toString());
|
||||
try {
|
||||
return concrete(this);
|
||||
} finally {
|
||||
_buildStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
if (concrete is String) {
|
||||
// First, check if it's a simple value binding
|
||||
if (_bindings.containsKey(concrete) &&
|
||||
_bindings[concrete]!['concrete'] is! Function) {
|
||||
return _bindings[concrete]!['concrete'];
|
||||
}
|
||||
|
||||
// If it's not a simple value, proceed with class resolution
|
||||
try {
|
||||
Symbol classSymbol = MirrorSystem.getSymbol(concrete)!;
|
||||
ClassMirror? classMirror;
|
||||
|
||||
// Search for the class in all libraries
|
||||
for (var lib in currentMirrorSystem().libraries.values) {
|
||||
if (lib.declarations.containsKey(classSymbol)) {
|
||||
var declaration = lib.declarations[classSymbol]!;
|
||||
if (declaration is ClassMirror) {
|
||||
classMirror = declaration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (classMirror == null) {
|
||||
// If we can't find a class, return the string as is
|
||||
return concrete;
|
||||
}
|
||||
|
||||
var classAttributes = classMirror.metadata;
|
||||
fireBeforeResolvingAttributeCallbacks(classAttributes, null);
|
||||
|
||||
MethodMirror? constructor = classMirror.declarations.values
|
||||
.whereType<MethodMirror>()
|
||||
.firstWhere((d) => d.isConstructor,
|
||||
orElse: () => null as MethodMirror);
|
||||
|
||||
if (constructor == null) {
|
||||
throw BindingResolutionException(
|
||||
"No constructor found for [$concrete]");
|
||||
}
|
||||
|
||||
List parameters = _resolveDependencies(constructor.parameters);
|
||||
var instance =
|
||||
classMirror.newInstance(Symbol.empty, parameters).reflectee;
|
||||
|
||||
// Apply attributes to the instance
|
||||
for (var attribute in classAttributes) {
|
||||
var attributeType = attribute.reflectee.runtimeType;
|
||||
var attributeTypeName = attributeType.toString();
|
||||
if (_afterResolvingAttributeCallbacks
|
||||
.containsKey(attributeTypeName)) {
|
||||
for (var callback
|
||||
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
||||
callback(attribute.reflectee, instance, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply attributes to properties
|
||||
var instanceMirror = reflect(instance);
|
||||
for (var declaration in classMirror.declarations.values) {
|
||||
if (declaration is VariableMirror) {
|
||||
for (var attribute in declaration.metadata) {
|
||||
var attributeType = attribute.reflectee.runtimeType;
|
||||
var attributeTypeName = attributeType.toString();
|
||||
if (_afterResolvingAttributeCallbacks
|
||||
.containsKey(attributeTypeName)) {
|
||||
for (var callback
|
||||
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
||||
var propertyValue =
|
||||
instanceMirror.getField(declaration.simpleName).reflectee;
|
||||
callback(attribute.reflectee, propertyValue, this);
|
||||
instanceMirror.setField(
|
||||
declaration.simpleName, propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply after resolving callbacks
|
||||
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
|
||||
_fireAfterResolvingCallbacks(concrete, instance);
|
||||
|
||||
// Apply extenders after all callbacks
|
||||
for (var extender in _getExtenders(concrete)) {
|
||||
instance = extender(instance);
|
||||
}
|
||||
|
||||
// Store the instance if it's shared
|
||||
if (isShared(concrete)) {
|
||||
_instances[concrete] = instance;
|
||||
}
|
||||
|
||||
// Ensure the instance is stored before returning
|
||||
if (_instances.containsKey(concrete)) {
|
||||
return _instances[concrete];
|
||||
}
|
||||
|
||||
return instance;
|
||||
} catch (e) {
|
||||
// If any error occurs during class instantiation, return the string as is
|
||||
return concrete;
|
||||
}
|
||||
}
|
||||
|
||||
// If concrete is neither a Function nor a String, return it as is
|
||||
return concrete;
|
||||
}
|
||||
|
||||
dynamic _getContextualConcrete(String abstract) {
|
||||
if (_buildStack.isNotEmpty) {
|
||||
var building = _buildStack.last;
|
||||
if (_contextual.containsKey(building) &&
|
||||
_contextual[building]!.containsKey(abstract)) {
|
||||
return _contextual[building]![abstract];
|
||||
}
|
||||
|
||||
// Check for attribute-based contextual bindings
|
||||
try {
|
||||
var buildingType = MirrorSystem.getSymbol(building);
|
||||
var buildingMirror = currentMirrorSystem()
|
||||
.findLibrary(buildingType)
|
||||
?.declarations[buildingType];
|
||||
if (buildingMirror is ClassMirror) {
|
||||
for (var attribute in buildingMirror.metadata) {
|
||||
if (attribute.reflectee is ContextualAttribute) {
|
||||
var contextualAttribute =
|
||||
attribute.reflectee as ContextualAttribute;
|
||||
if (_contextualAttributes
|
||||
.containsKey(contextualAttribute.runtimeType.toString())) {
|
||||
var handler = _contextualAttributes[
|
||||
contextualAttribute.runtimeType.toString()]!;
|
||||
return handler(contextualAttribute, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// If we can't find the class, just continue
|
||||
}
|
||||
}
|
||||
|
||||
if (_buildStack.isNotEmpty) {
|
||||
if (_contextual.containsKey(_buildStack.last) &&
|
||||
_contextual[_buildStack.last]!.containsKey(abstract)) {
|
||||
return _contextual[_buildStack.last]![abstract];
|
||||
}
|
||||
}
|
||||
|
||||
if (_abstractAliases.containsKey(abstract)) {
|
||||
for (var alias in _abstractAliases[abstract]!) {
|
||||
if (_buildStack.isNotEmpty &&
|
||||
_contextual.containsKey(_buildStack.last) &&
|
||||
_contextual[_buildStack.last]!.containsKey(alias)) {
|
||||
return _contextual[_buildStack.last]![alias];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
dynamic resolveFromAnnotation(InstanceMirror annotation) {
|
||||
var instance = annotation.reflectee;
|
||||
|
||||
if (instance is ContextualAttribute) {
|
||||
// Handle ContextualAttribute
|
||||
return _resolveContextualAttribute(instance);
|
||||
}
|
||||
|
||||
// Add more annotation handling as needed
|
||||
|
||||
throw BindingResolutionException(
|
||||
"Unsupported annotation type: ${annotation.type}");
|
||||
}
|
||||
|
||||
void fireAfterResolvingAnnotationCallbacks(
|
||||
List<InstanceMirror> annotations, dynamic object) {
|
||||
for (var annotation in annotations) {
|
||||
if (annotation.reflectee is ContextualAttribute) {
|
||||
var instance = annotation.reflectee as ContextualAttribute;
|
||||
if (_afterResolvingAttributeCallbacks
|
||||
.containsKey(instance.runtimeType.toString())) {
|
||||
for (var callback in _afterResolvingAttributeCallbacks[
|
||||
instance.runtimeType.toString()]!) {
|
||||
callback(instance, object, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> _resolveDependencies(List<ParameterMirror> parameters) {
|
||||
var results = <dynamic>[];
|
||||
for (var parameter in parameters) {
|
||||
var parameterName = MirrorSystem.getName(parameter.simpleName);
|
||||
if (_hasParameterOverride(parameterName)) {
|
||||
results.add(_getParameterOverride(parameterName));
|
||||
} else {
|
||||
var annotations = parameter.metadata;
|
||||
if (annotations.isNotEmpty) {
|
||||
results.add(resolveFromAnnotation(annotations.first));
|
||||
} else if (parameter.type.reflectedType != dynamic) {
|
||||
results.add(make(parameter.type.reflectedType.toString()));
|
||||
} else if (parameter.isOptional && parameter.defaultValue != null) {
|
||||
results.add(parameter.defaultValue!.reflectee);
|
||||
} else {
|
||||
throw BindingResolutionException(
|
||||
"Unable to resolve parameter $parameterName");
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
bool _hasParameterOverride(String parameterName) {
|
||||
return _getLastParameterOverride().containsKey(parameterName);
|
||||
}
|
||||
|
||||
dynamic _getParameterOverride(String parameterName) {
|
||||
return _getLastParameterOverride()[parameterName];
|
||||
}
|
||||
|
||||
List<Function> _getExtenders(String abstract) {
|
||||
return _extenders[getAlias(abstract)] ?? [];
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getLastParameterOverride() {
|
||||
return _with.isNotEmpty ? _with.last : {};
|
||||
}
|
||||
|
||||
void _fireBeforeResolvingCallbacks(
|
||||
String abstract, List<dynamic> parameters) {
|
||||
_fireCallbackArray(abstract, parameters, _globalBeforeResolvingCallbacks);
|
||||
|
||||
for (var entry in _beforeResolvingCallbacks.entries) {
|
||||
if (entry.key == abstract || isSubclassOf(abstract, entry.key)) {
|
||||
_fireCallbackArray(abstract, parameters, entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _fireResolvingCallbacks(String abstract, dynamic object) {
|
||||
_fireCallbackArray(object, null, _globalResolvingCallbacks);
|
||||
|
||||
var callbacks = _getCallbacksForType(abstract, object, _resolvingCallbacks);
|
||||
_fireCallbackArray(object, null, callbacks);
|
||||
|
||||
_fireAfterResolvingCallbacks(abstract, object);
|
||||
}
|
||||
|
||||
void _fireAfterResolvingCallbacks(String abstract, dynamic object) {
|
||||
_fireCallbackArray(object, null, _globalAfterResolvingCallbacks);
|
||||
|
||||
var callbacks =
|
||||
_getCallbacksForType(abstract, object, _afterResolvingCallbacks);
|
||||
_fireCallbackArray(object, null, callbacks);
|
||||
}
|
||||
|
||||
void _fireCallbackArray(
|
||||
dynamic argument, List<dynamic>? parameters, List<Function> callbacks) {
|
||||
for (var callback in callbacks) {
|
||||
if (parameters != null) {
|
||||
callback(argument, parameters, this);
|
||||
} else {
|
||||
callback(argument, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Function> _getCallbacksForType(String abstract, dynamic object,
|
||||
Map<String, List<Function>> callbacksPerType) {
|
||||
var results = <Function>[];
|
||||
|
||||
for (var entry in callbacksPerType.entries) {
|
||||
if (entry.key == abstract || object.runtimeType.toString() == entry.key) {
|
||||
results.addAll(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
bool isSubclassOf(String child, String parent) {
|
||||
ClassMirror? childClass = _getClassMirror(child);
|
||||
ClassMirror? parentClass = _getClassMirror(parent);
|
||||
|
||||
if (childClass == null || parentClass == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (childClass == parentClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ClassMirror? currentClass = childClass.superclass;
|
||||
while (currentClass != null) {
|
||||
if (currentClass == parentClass) {
|
||||
return true;
|
||||
}
|
||||
currentClass = currentClass.superclass;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ClassMirror? _getClassMirror(String className) {
|
||||
Symbol classSymbol = MirrorSystem.getSymbol(className)!;
|
||||
for (var lib in currentMirrorSystem().libraries.values) {
|
||||
if (lib.declarations.containsKey(classSymbol)) {
|
||||
var declaration = lib.declarations[classSymbol]!;
|
||||
if (declaration is ClassMirror) {
|
||||
return declaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String getAlias(String abstract) {
|
||||
if (!_aliases.containsKey(abstract)) {
|
||||
return abstract;
|
||||
}
|
||||
|
||||
if (_aliases[abstract] == abstract) {
|
||||
throw Exception("[$abstract] is aliased to itself.");
|
||||
}
|
||||
|
||||
return getAlias(_aliases[abstract]!);
|
||||
}
|
||||
|
||||
// Implement ArrayAccess-like functionality
|
||||
dynamic operator [](String key) => make(key);
|
||||
void operator []=(String key, dynamic value) => bind(key, value);
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:ioc_container/src/util.dart';
|
||||
|
||||
class ContextualBindingBuilder implements ContextualBindingBuilderContract {
|
||||
/// The underlying container instance.
|
||||
final ContainerContract _container;
|
||||
|
||||
/// The concrete instance.
|
||||
final dynamic _concrete;
|
||||
|
||||
/// The abstract target.
|
||||
dynamic _needs;
|
||||
|
||||
/// Create a new contextual binding builder.
|
||||
ContextualBindingBuilder(this._container, this._concrete);
|
||||
|
||||
/// Define the abstract target that depends on the context.
|
||||
@override
|
||||
ContextualBindingBuilderContract needs(dynamic abstract) {
|
||||
_needs = abstract;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Define the implementation for the contextual binding.
|
||||
@override
|
||||
void give(dynamic implementation) {
|
||||
for (var concrete in Util.arrayWrap(_concrete)) {
|
||||
_container.addContextualBinding(concrete, _needs, implementation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Define tagged services to be used as the implementation for the contextual binding.
|
||||
@override
|
||||
void giveTagged(String tag) {
|
||||
give((ContainerContract container) {
|
||||
var taggedServices = container.tagged(tag);
|
||||
return taggedServices is List ? taggedServices : taggedServices.toList();
|
||||
});
|
||||
}
|
||||
|
||||
/// Specify the configuration item to bind as a primitive.
|
||||
@override
|
||||
void giveConfig(String key, [dynamic defaultValue]) {
|
||||
give((ContainerContract container) =>
|
||||
container.get('config').get(key, defaultValue));
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import 'package:dsr_container/container.dart';
|
||||
|
||||
class EntryNotFoundException implements Exception, NotFoundExceptionInterface {
|
||||
@override
|
||||
final String message;
|
||||
|
||||
EntryNotFoundException([this.message = '']);
|
||||
|
||||
@override
|
||||
String get id => message;
|
||||
|
||||
@override
|
||||
String toString() => 'EntryNotFoundException: $message';
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import 'dart:collection';
|
||||
|
||||
class RewindableGenerator extends IterableBase {
|
||||
/// The generator callback.
|
||||
final Function _generator;
|
||||
|
||||
/// The number of tagged services.
|
||||
dynamic _count;
|
||||
|
||||
/// Create a new generator instance.
|
||||
RewindableGenerator(this._generator, this._count);
|
||||
|
||||
@override
|
||||
Iterator get iterator => _generator() as Iterator;
|
||||
|
||||
/// Get the total number of tagged services.
|
||||
@override
|
||||
int get length {
|
||||
if (_count is Function) {
|
||||
_count = _count();
|
||||
}
|
||||
return _count as int;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
import 'dart:mirrors';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
/// @internal
|
||||
class Util {
|
||||
/// If the given value is not an array and not null, wrap it in one.
|
||||
static List arrayWrap(dynamic value) {
|
||||
if (value == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value is List ? value : [value];
|
||||
}
|
||||
|
||||
/// Return the default value of the given value.
|
||||
static dynamic unwrapIfClosure(dynamic value,
|
||||
[List<dynamic> args = const []]) {
|
||||
return value is Function ? Function.apply(value, args) : value;
|
||||
}
|
||||
|
||||
/// Get the class name of the given parameter's type, if possible.
|
||||
static String? getParameterClassName(ParameterMirror parameter) {
|
||||
var type = parameter.type;
|
||||
|
||||
if (type.reflectedType == dynamic ||
|
||||
type.isSubtypeOf(reflectType(num)) ||
|
||||
type.isSubtypeOf(reflectType(String)) ||
|
||||
type.isSubtypeOf(reflectType(bool))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = MirrorSystem.getName(type.simpleName);
|
||||
|
||||
var declaringClass = parameter.owner as ClassMirror?;
|
||||
if (declaringClass != null) {
|
||||
if (name == 'self') {
|
||||
return MirrorSystem.getName(declaringClass.simpleName);
|
||||
}
|
||||
|
||||
if (name == 'parent' && declaringClass.superclass != null) {
|
||||
return MirrorSystem.getName(declaringClass.superclass!.simpleName);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Get a contextual attribute from a dependency.
|
||||
static InstanceMirror? getContextualAttributeFromDependency(
|
||||
ParameterMirror dependency) {
|
||||
var contextualAttributes = dependency.metadata.where(
|
||||
(attr) => attr.type.isSubtypeOf(reflectType(ContextualAttribute)));
|
||||
|
||||
return contextualAttributes.isNotEmpty ? contextualAttributes.first : null;
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
name: ioc_container
|
||||
description: The Container Package for the Protevus Platform
|
||||
version: 0.0.1
|
||||
homepage: https://protevus.com
|
||||
documentation: https://docs.protevus.com
|
||||
repository: https://github.com/protevus/platformo
|
||||
|
||||
environment:
|
||||
sdk: ^3.4.2
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
dsr_container: ^0.1.0
|
||||
platform_contracts: ^0.1.0
|
||||
# path: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
mockito: ^5.4.0
|
||||
test: ^1.24.0
|
||||
platform_config: ^0.1.0
|
|
@ -1,131 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('AfterResolvingAttributeCallbackTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
});
|
||||
|
||||
test('callback is called after dependency resolution with attribute', () {
|
||||
container.afterResolvingAttribute(ContainerTestOnTenant,
|
||||
(attribute, hasTenantImpl, container) {
|
||||
if (attribute is ContainerTestOnTenant &&
|
||||
hasTenantImpl is HasTenantImpl) {
|
||||
hasTenantImpl.onTenant(attribute.tenant);
|
||||
}
|
||||
});
|
||||
|
||||
var hasTenantA =
|
||||
container.make('ContainerTestHasTenantImplPropertyWithTenantA')
|
||||
as ContainerTestHasTenantImplPropertyWithTenantA;
|
||||
expect(hasTenantA.property, isA<HasTenantImpl>());
|
||||
expect(hasTenantA.property.tenant, equals(Tenant.TenantA));
|
||||
|
||||
var hasTenantB =
|
||||
container.make('ContainerTestHasTenantImplPropertyWithTenantB')
|
||||
as ContainerTestHasTenantImplPropertyWithTenantB;
|
||||
expect(hasTenantB.property, isA<HasTenantImpl>());
|
||||
expect(hasTenantB.property.tenant, equals(Tenant.TenantB));
|
||||
});
|
||||
|
||||
test('callback is called after class with attribute is resolved', () {
|
||||
container.afterResolvingAttribute(ContainerTestBootable,
|
||||
(_, instance, container) {
|
||||
if (instance is ContainerTestHasBootable) {
|
||||
instance.booting();
|
||||
}
|
||||
});
|
||||
|
||||
var instance = container.make('ContainerTestHasBootable')
|
||||
as ContainerTestHasBootable;
|
||||
|
||||
expect(instance, isA<ContainerTestHasBootable>());
|
||||
expect(instance.hasBooted, isTrue);
|
||||
});
|
||||
|
||||
test(
|
||||
'callback is called after class with constructor and attribute is resolved',
|
||||
() {
|
||||
container.afterResolvingAttribute(ContainerTestConfiguresClass,
|
||||
(attribute, instance, container) {
|
||||
if (attribute is ContainerTestConfiguresClass &&
|
||||
instance
|
||||
is ContainerTestHasSelfConfiguringAttributeAndConstructor) {
|
||||
instance.value = attribute.value;
|
||||
}
|
||||
});
|
||||
|
||||
container
|
||||
.when('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||
.needs('value')
|
||||
.give('not-the-right-value');
|
||||
|
||||
var instance = container
|
||||
.make('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||
as ContainerTestHasSelfConfiguringAttributeAndConstructor;
|
||||
|
||||
expect(instance,
|
||||
isA<ContainerTestHasSelfConfiguringAttributeAndConstructor>());
|
||||
expect(instance.value, equals('the-right-value'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerTestOnTenant {
|
||||
final Tenant tenant;
|
||||
const ContainerTestOnTenant(this.tenant);
|
||||
}
|
||||
|
||||
enum Tenant {
|
||||
TenantA,
|
||||
TenantB,
|
||||
}
|
||||
|
||||
class HasTenantImpl {
|
||||
Tenant? tenant;
|
||||
|
||||
void onTenant(Tenant tenant) {
|
||||
this.tenant = tenant;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasTenantImplPropertyWithTenantA {
|
||||
@ContainerTestOnTenant(Tenant.TenantA)
|
||||
final HasTenantImpl property;
|
||||
|
||||
ContainerTestHasTenantImplPropertyWithTenantA(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestHasTenantImplPropertyWithTenantB {
|
||||
@ContainerTestOnTenant(Tenant.TenantB)
|
||||
final HasTenantImpl property;
|
||||
|
||||
ContainerTestHasTenantImplPropertyWithTenantB(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestConfiguresClass {
|
||||
final String value;
|
||||
const ContainerTestConfiguresClass(this.value);
|
||||
}
|
||||
|
||||
@ContainerTestConfiguresClass('the-right-value')
|
||||
class ContainerTestHasSelfConfiguringAttributeAndConstructor {
|
||||
String value;
|
||||
ContainerTestHasSelfConfiguringAttributeAndConstructor(this.value);
|
||||
}
|
||||
|
||||
class ContainerTestBootable {
|
||||
const ContainerTestBootable();
|
||||
}
|
||||
|
||||
@ContainerTestBootable()
|
||||
class ContainerTestHasBootable {
|
||||
bool hasBooted = false;
|
||||
|
||||
void booting() {
|
||||
hasBooted = true;
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
class MockContainer extends Mock implements Container {}
|
||||
|
||||
class TestClass {
|
||||
String testMethod(String param) => 'Test: $param';
|
||||
static String staticMethod(String param) => 'Static: $param';
|
||||
String __invoke(String param) => 'Invoke: $param';
|
||||
}
|
||||
|
||||
class DependencyClass {
|
||||
final String value;
|
||||
DependencyClass(this.value);
|
||||
}
|
||||
|
||||
class ClassWithDependency {
|
||||
String methodWithDependency(DependencyClass dep, String param) =>
|
||||
'${dep.value}: $param';
|
||||
}
|
||||
|
||||
class NestedDependency {
|
||||
final DependencyClass dep;
|
||||
NestedDependency(this.dep);
|
||||
String nestedMethod(String param) => '${dep.value} nested: $param';
|
||||
}
|
||||
|
||||
class ClassWithOptionalParam {
|
||||
String methodWithOptional(String required, [String optional = 'default']) =>
|
||||
'$required - $optional';
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Container.call', () {
|
||||
late MockContainer container;
|
||||
|
||||
setUp(() {
|
||||
container = MockContainer();
|
||||
});
|
||||
|
||||
test('call with Function', () {
|
||||
var result = container.call((String s) => 'Hello $s', ['World']);
|
||||
expect(result, equals('Hello World'));
|
||||
});
|
||||
|
||||
test('call with class@method string', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass@testMethod', ['World']);
|
||||
expect(result, equals('Test: World'));
|
||||
});
|
||||
|
||||
test('call with List callback', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call(['TestClass', 'testMethod'], ['World']);
|
||||
expect(result, equals('Test: World'));
|
||||
});
|
||||
|
||||
test('call with static method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass);
|
||||
var result = container.call(['TestClass', 'staticMethod'], ['World']);
|
||||
expect(result, equals('Static: World'));
|
||||
});
|
||||
|
||||
test('call with Map instance and method key', () {
|
||||
var mapInstance = {'testMethod': (String s) => 'Map: $s'};
|
||||
when(container.make('TestMap')).thenReturn(mapInstance);
|
||||
var result = container.call(['TestMap', 'testMethod'], ['World']);
|
||||
expect(result, equals('Map: World'));
|
||||
});
|
||||
|
||||
test('call with global function', () {
|
||||
when(container.make('globalFunction')).thenReturn(globalFunction);
|
||||
var result = container.call('globalFunction', ['World']);
|
||||
expect(result, equals('Global: World'));
|
||||
});
|
||||
|
||||
test('call with default method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass', ['World'], '__invoke');
|
||||
expect(result, equals('Invoke: World'));
|
||||
});
|
||||
|
||||
test('call with __invoke method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass', ['World']);
|
||||
expect(result, equals('Invoke: World'));
|
||||
});
|
||||
|
||||
test('call with non-existent method throws BindingResolutionException', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
expect(() => container.call('TestClass@nonExistentMethod', ['World']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('call with invalid callback type throws ArgumentError', () {
|
||||
expect(
|
||||
() => container.call(123, ['World']), throwsA(isA<ArgumentError>()));
|
||||
});
|
||||
|
||||
test('call method with dependencies', () {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenReturn(DependencyClass('Dependency'));
|
||||
var result = container
|
||||
.call(['ClassWithDependency', 'methodWithDependency'], ['World']);
|
||||
expect(result, equals('Dependency: World'));
|
||||
});
|
||||
|
||||
test('call method with overridden dependency', () {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenReturn(DependencyClass('Dependency'));
|
||||
var result = container.call(
|
||||
['ClassWithDependency', 'methodWithDependency'],
|
||||
[DependencyClass('Override'), 'World']);
|
||||
expect(result, equals('Override: World'));
|
||||
});
|
||||
|
||||
test('call method with nested dependency', () {
|
||||
when(container.make('NestedDependency'))
|
||||
.thenReturn(NestedDependency(DependencyClass('NestedDep')));
|
||||
var result =
|
||||
container.call(['NestedDependency', 'nestedMethod'], ['World']);
|
||||
expect(result, equals('NestedDep nested: World'));
|
||||
});
|
||||
|
||||
test('call method with optional parameter - provided', () {
|
||||
when(container.make('ClassWithOptionalParam'))
|
||||
.thenReturn(ClassWithOptionalParam());
|
||||
var result = container.call(
|
||||
['ClassWithOptionalParam', 'methodWithOptional'],
|
||||
['Required', 'Provided']);
|
||||
expect(result, equals('Required - Provided'));
|
||||
});
|
||||
|
||||
test('call method with optional parameter - default', () {
|
||||
when(container.make('ClassWithOptionalParam'))
|
||||
.thenReturn(ClassWithOptionalParam());
|
||||
var result = container
|
||||
.call(['ClassWithOptionalParam', 'methodWithOptional'], ['Required']);
|
||||
expect(result, equals('Required - default'));
|
||||
});
|
||||
|
||||
test(
|
||||
'call method with missing required dependency throws BindingResolutionException',
|
||||
() {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenThrow(BindingResolutionException('DependencyClass not found'));
|
||||
expect(
|
||||
() => container
|
||||
.call(['ClassWithDependency', 'methodWithDependency'], ['World']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
String globalFunction(String param) => 'Global: $param';
|
|
@ -1,89 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerCallTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException',
|
||||
() {
|
||||
expect(() => container.call('ContainerCallTest@'),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferences', () {
|
||||
container.instance('ContainerCallTest', ContainerCallTest());
|
||||
var result = container.call('ContainerCallTest@work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferencesWithoutMethodCallsRun', () {
|
||||
container.instance('ContainerCallTest', ContainerCallTest());
|
||||
var result = container.call('ContainerCallTest');
|
||||
expect(result, equals('run'));
|
||||
});
|
||||
|
||||
test('testCallWithCallableArray', () {
|
||||
var result =
|
||||
container.call([ContainerCallTest(), 'work'], ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithStaticMethodNameString', () {
|
||||
expect(
|
||||
() => container.call('ContainerCallTest::staticWork', ['foo', 'bar']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithGlobalMethodNameString', () {
|
||||
expect(() => container.call('globalTestMethod', ['foo', 'bar']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethod', () {
|
||||
container.bindMethod('work', (container, params) => 'foobar');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndArrayOfParameters', () {
|
||||
container.bindMethod(
|
||||
'work', (container, params) => '${params[0]}${params[1]}');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndArrayOfParametersWithOptionalParameters',
|
||||
() {
|
||||
container.bindMethod(
|
||||
'work',
|
||||
(container, params) =>
|
||||
'${params[0]}${params[1]}${params[2] ?? 'baz'}');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobarbaz'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndDependencies', () {
|
||||
container.bind('foo', (container) => 'bar');
|
||||
container.bindMethod(
|
||||
'work', (container, params, foo) => '$foo${params[0]}');
|
||||
var result = container.call('work', ['baz']);
|
||||
expect(result, equals('barbaz'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerCallTest {
|
||||
String work(String param1, String param2) => '$param1$param2';
|
||||
|
||||
String run() => 'run';
|
||||
|
||||
static String staticWork(String param1, String param2) => '$param1$param2';
|
||||
}
|
||||
|
||||
String globalTestMethod(String param1, String param2) => '$param1$param2';
|
|
@ -1,158 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
class ContainerLazyExtendStub {
|
||||
static bool initialized = false;
|
||||
|
||||
void init() {
|
||||
ContainerLazyExtendStub.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ContainerExtendTest', () {
|
||||
test('extendedBindings', () {
|
||||
var container = Container();
|
||||
container['foo'] = 'foo';
|
||||
container.extend('foo', (old) => '${old}bar');
|
||||
|
||||
var result1 = container.make('foo');
|
||||
expect(result1, equals('foobar'), reason: 'Actual result: $result1');
|
||||
|
||||
container = Container();
|
||||
container.singleton(
|
||||
'foo', (container) => <String, dynamic>{'name': 'taylor'});
|
||||
container.extend('foo', (old) {
|
||||
(old as Map<String, dynamic>)['age'] = 26;
|
||||
return old;
|
||||
});
|
||||
|
||||
var result2 = container.make('foo') as Map<String, dynamic>;
|
||||
expect(result2['name'], equals('taylor'));
|
||||
expect(result2['age'], equals(26));
|
||||
expect(identical(result2, container.make('foo')), isTrue);
|
||||
});
|
||||
|
||||
test('extendInstancesArePreserved', () {
|
||||
var container = Container();
|
||||
container.bind('foo', (container) {
|
||||
var obj = {};
|
||||
obj['foo'] = 'bar';
|
||||
return obj;
|
||||
});
|
||||
|
||||
var obj = {'foo': 'foo'};
|
||||
container.instance('foo', obj);
|
||||
container.extend('foo', (obj) {
|
||||
(obj as Map<String, dynamic>)['bar'] = 'baz';
|
||||
return obj;
|
||||
});
|
||||
container.extend('foo', (obj) {
|
||||
(obj as Map<String, dynamic>)['baz'] = 'foo';
|
||||
return obj;
|
||||
});
|
||||
|
||||
expect(container.make('foo')['foo'], equals('foo'));
|
||||
expect(container.make('foo')['bar'], equals('baz'));
|
||||
expect(container.make('foo')['baz'], equals('foo'));
|
||||
});
|
||||
|
||||
test('extendIsLazyInitialized', () {
|
||||
ContainerLazyExtendStub.initialized = false;
|
||||
|
||||
var container = Container();
|
||||
container.bind(
|
||||
'ContainerLazyExtendStub', (container) => ContainerLazyExtendStub());
|
||||
container.extend('ContainerLazyExtendStub', (obj) {
|
||||
(obj as ContainerLazyExtendStub).init();
|
||||
return obj;
|
||||
});
|
||||
expect(ContainerLazyExtendStub.initialized, isFalse);
|
||||
container.make('ContainerLazyExtendStub');
|
||||
expect(ContainerLazyExtendStub.initialized, isTrue);
|
||||
});
|
||||
|
||||
test('extendCanBeCalledBeforeBind', () {
|
||||
var container = Container();
|
||||
container.extend('foo', (old) => '${old}bar');
|
||||
container['foo'] = 'foo';
|
||||
|
||||
var result = container.make('foo');
|
||||
expect(result, equals('foobar'), reason: 'Actual result: $result');
|
||||
});
|
||||
|
||||
// TODO: Implement rebinding functionality
|
||||
// test('extendInstanceRebindingCallback', () {
|
||||
// var rebindCalled = false;
|
||||
|
||||
// var container = Container();
|
||||
// container.rebinding('foo', (container) {
|
||||
// rebindCalled = true;
|
||||
// });
|
||||
|
||||
// var obj = {};
|
||||
// container.instance('foo', obj);
|
||||
|
||||
// container.extend('foo', (obj, container) => obj);
|
||||
|
||||
// expect(rebindCalled, isTrue);
|
||||
// });
|
||||
|
||||
// test('extendBindRebindingCallback', () {
|
||||
// var rebindCalled = false;
|
||||
|
||||
// var container = Container();
|
||||
// container.rebinding('foo', (container) {
|
||||
// rebindCalled = true;
|
||||
// });
|
||||
// container.bind('foo', (container) => {});
|
||||
|
||||
// expect(rebindCalled, isFalse);
|
||||
|
||||
// container.make('foo');
|
||||
|
||||
// container.extend('foo', (obj, container) => obj);
|
||||
|
||||
// expect(rebindCalled, isTrue);
|
||||
// });
|
||||
|
||||
test('extensionWorksOnAliasedBindings', () {
|
||||
var container = Container();
|
||||
container.singleton('something', (container) => 'some value');
|
||||
container.alias('something', 'something-alias');
|
||||
container.extend('something-alias', (value) => '$value extended');
|
||||
|
||||
expect(container.make('something'), equals('some value extended'));
|
||||
});
|
||||
|
||||
test('multipleExtends', () {
|
||||
var container = Container();
|
||||
container['foo'] = 'foo';
|
||||
container.extend('foo', (old) => '${old}bar');
|
||||
container.extend('foo', (old) => '${old}baz');
|
||||
|
||||
expect(container.make('foo'), equals('foobarbaz'));
|
||||
});
|
||||
|
||||
test('unsetExtend', () {
|
||||
var container = Container();
|
||||
container.bind('foo', (container) {
|
||||
var obj = {};
|
||||
obj['foo'] = 'bar';
|
||||
return obj;
|
||||
});
|
||||
|
||||
container.extend('foo', (obj) {
|
||||
(obj as Map<String, dynamic>)['bar'] = 'baz';
|
||||
return obj;
|
||||
});
|
||||
|
||||
container.forgetInstance('foo');
|
||||
container.forgetExtenders('foo');
|
||||
|
||||
container.bind('foo', (container) => 'foo');
|
||||
|
||||
expect(container.make('foo'), equals('foo'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerResolveNonInstantiableTest', () {
|
||||
test('testResolvingNonInstantiableWithDefaultRemovesWiths', () {
|
||||
var container = Container();
|
||||
var object = container.make('ParentClass', [null, 42]);
|
||||
|
||||
expect(object, isA<ParentClass>());
|
||||
expect(object.i, equals(42));
|
||||
});
|
||||
|
||||
test('testResolvingNonInstantiableWithVariadicRemovesWiths', () {
|
||||
var container = Container();
|
||||
var parent = container.make('VariadicParentClass', [
|
||||
container.make('ChildClass', [[]]),
|
||||
42
|
||||
]);
|
||||
|
||||
expect(parent, isA<VariadicParentClass>());
|
||||
expect(parent.child.objects, isEmpty);
|
||||
expect(parent.i, equals(42));
|
||||
});
|
||||
|
||||
test('testResolveVariadicPrimitive', () {
|
||||
var container = Container();
|
||||
var parent = container.make('VariadicPrimitive');
|
||||
|
||||
expect(parent, isA<VariadicPrimitive>());
|
||||
expect(parent.params, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class TestInterface {}
|
||||
|
||||
class ParentClass {
|
||||
int i;
|
||||
|
||||
ParentClass([TestInterface? testObject, this.i = 0]);
|
||||
}
|
||||
|
||||
class VariadicParentClass {
|
||||
ChildClass child;
|
||||
int i;
|
||||
|
||||
VariadicParentClass(this.child, [this.i = 0]);
|
||||
}
|
||||
|
||||
class ChildClass {
|
||||
List<TestInterface> objects;
|
||||
|
||||
ChildClass(this.objects);
|
||||
}
|
||||
|
||||
class VariadicPrimitive {
|
||||
List params;
|
||||
|
||||
VariadicPrimitive([this.params = const []]);
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerTaggingTest', () {
|
||||
test('testContainerTags', () {
|
||||
var container = Container();
|
||||
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||
container.tag(ContainerImplementationTaggedStub, 'bar');
|
||||
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||
|
||||
expect(container.tagged('bar').length, 1);
|
||||
expect(container.tagged('foo').length, 2);
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
var barResults = [];
|
||||
for (var bar in container.tagged('bar')) {
|
||||
barResults.add(bar);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(barResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
container = Container();
|
||||
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||
expect(container.tagged('foo').length, 2);
|
||||
|
||||
fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
expect(container.tagged('this_tag_does_not_exist').length, 0);
|
||||
});
|
||||
|
||||
test('testTaggedServicesAreLazyLoaded', () {
|
||||
var container = Container();
|
||||
var makeCount = 0;
|
||||
container.bind('ContainerImplementationTaggedStub', (c) {
|
||||
makeCount++;
|
||||
return ContainerImplementationTaggedStub();
|
||||
});
|
||||
|
||||
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
break;
|
||||
}
|
||||
|
||||
expect(container.tagged('foo').length, 2);
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(makeCount, 1);
|
||||
});
|
||||
|
||||
test('testLazyLoadedTaggedServicesCanBeLoopedOverMultipleTimes', () {
|
||||
var container = Container();
|
||||
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||
|
||||
var services = container.tagged('foo');
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in services) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
fooResults = [];
|
||||
for (var foo in services) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class IContainerTaggedContractStub {}
|
||||
|
||||
class ContainerImplementationTaggedStub
|
||||
implements IContainerTaggedContractStub {}
|
||||
|
||||
class ContainerImplementationTaggedStubTwo
|
||||
implements IContainerTaggedContractStub {}
|
|
@ -1,279 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
class ContainerConcreteStub {}
|
||||
|
||||
class IContainerContractStub {}
|
||||
|
||||
class ContainerImplementationStub implements IContainerContractStub {}
|
||||
|
||||
class ContainerDependentStub {
|
||||
final IContainerContractStub impl;
|
||||
|
||||
ContainerDependentStub(this.impl);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ContainerTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
setUpBindings(container);
|
||||
});
|
||||
|
||||
test('testContainerSingleton', () {
|
||||
var container1 = Container.getInstance();
|
||||
var container2 = Container.getInstance();
|
||||
expect(container1, same(container2));
|
||||
});
|
||||
|
||||
test('testClosureResolution', () {
|
||||
container.bind('name', (Container c) => 'Taylor');
|
||||
expect(container.make('name'), equals('Taylor'));
|
||||
});
|
||||
|
||||
test('testBindIfDoesntRegisterIfServiceAlreadyRegistered', () {
|
||||
container.bind('name', (Container c) => 'Taylor');
|
||||
container.bindIf('name', (Container c) => 'Dayle');
|
||||
|
||||
expect(container.make('name'), equals('Taylor'));
|
||||
});
|
||||
|
||||
test('testBindIfDoesRegisterIfServiceNotRegisteredYet', () {
|
||||
container.bind('surname', (Container c) => 'Taylor');
|
||||
container.bindIf('name', (Container c) => 'Dayle');
|
||||
|
||||
expect(container.make('name'), equals('Dayle'));
|
||||
});
|
||||
|
||||
test('testSingletonIfDoesntRegisterIfBindingAlreadyRegistered', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('class');
|
||||
container.singletonIf('class', (Container c) => ContainerConcreteStub());
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testSingletonIfDoesRegisterIfBindingNotRegisteredYet', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
container.singletonIf(
|
||||
'otherClass', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('otherClass');
|
||||
var secondInstantiation = container.make('otherClass');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testSharedClosureResolution', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('class');
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testAutoConcreteResolution', () {
|
||||
var instance = container.make('ContainerConcreteStub');
|
||||
expect(instance, isA<ContainerConcreteStub>());
|
||||
});
|
||||
|
||||
test('testSharedConcreteResolution', () {
|
||||
container.singleton(
|
||||
'ContainerConcreteStub', (Container c) => ContainerConcreteStub());
|
||||
|
||||
var var1 = container.make('ContainerConcreteStub');
|
||||
var var2 = container.make('ContainerConcreteStub');
|
||||
expect(var1, same(var2));
|
||||
});
|
||||
|
||||
test('testAbstractToConcreteResolution', () {
|
||||
container.bind('IContainerContractStub',
|
||||
(Container c) => ContainerImplementationStub());
|
||||
var instance = container.make('ContainerDependentStub');
|
||||
expect(instance.impl, isA<ContainerImplementationStub>());
|
||||
});
|
||||
|
||||
test('testNestedDependencyResolution', () {
|
||||
container.bind('IContainerContractStub',
|
||||
(Container c) => ContainerImplementationStub());
|
||||
var instance = container.make('ContainerNestedDependentStub');
|
||||
expect(instance.inner, isA<ContainerDependentStub>());
|
||||
expect(instance.inner.impl, isA<ContainerImplementationStub>());
|
||||
});
|
||||
|
||||
test('testContainerIsPassedToResolvers', () {
|
||||
container.bind('something', (Container c) => c);
|
||||
var c = container.make('something');
|
||||
expect(c, same(container));
|
||||
});
|
||||
|
||||
test('testArrayAccess', () {
|
||||
container['something'] = (Container c) => 'foo';
|
||||
expect(container['something'], equals('foo'));
|
||||
});
|
||||
|
||||
test('testAliases', () {
|
||||
container['foo'] = 'bar';
|
||||
container.alias('foo', 'baz');
|
||||
container.alias('baz', 'bat');
|
||||
expect(container.make('foo'), equals('bar'));
|
||||
expect(container.make('baz'), equals('bar'));
|
||||
expect(container.make('bat'), equals('bar'));
|
||||
});
|
||||
|
||||
test('testBindingsCanBeOverridden', () {
|
||||
container['foo'] = 'bar';
|
||||
container['foo'] = 'baz';
|
||||
expect(container['foo'], equals('baz'));
|
||||
});
|
||||
|
||||
test('testResolutionOfDefaultParameters', () {
|
||||
container.bind('foo', (Container c) => 'bar');
|
||||
container.bind(
|
||||
'ContainerDefaultValueStub',
|
||||
(Container c) => ContainerDefaultValueStub(
|
||||
c.make('ContainerConcreteStub'), c.make('foo')));
|
||||
var result = container.make('ContainerDefaultValueStub');
|
||||
expect(result.stub, isA<ContainerConcreteStub>());
|
||||
expect(result.defaultValue, equals('bar'));
|
||||
});
|
||||
|
||||
test('testUnsetRemoveBoundInstances', () {
|
||||
container.instance('obj', Object());
|
||||
expect(container.bound('obj'), isTrue);
|
||||
container.forgetInstance('obj');
|
||||
expect(container.bound('obj'), isFalse);
|
||||
});
|
||||
|
||||
test('testExtendMethod', () {
|
||||
container.singleton('foo', (Container c) => 'foo');
|
||||
container.extend('foo', (dynamic original) {
|
||||
return '$original bar';
|
||||
});
|
||||
expect(container.make('foo'), equals('foo bar'));
|
||||
});
|
||||
|
||||
test('testFactoryMethod', () {
|
||||
container.bind('foo', (Container c) => 'foo');
|
||||
var factory = container.factory('foo');
|
||||
expect(factory(), equals('foo'));
|
||||
});
|
||||
|
||||
test('testTaggedBindings', () {
|
||||
container.tag(['foo', 'bar'], 'foobar');
|
||||
container.bind('foo', (Container c) => 'foo');
|
||||
container.bind('bar', (Container c) => 'bar');
|
||||
var tagged = container.tagged('foobar');
|
||||
expect(tagged, containsAll(['foo', 'bar']));
|
||||
});
|
||||
|
||||
test('testCircularDependencies', () {
|
||||
container.bind('circular1', (Container c) => c.make('circular2'));
|
||||
container.bind('circular2', (Container c) => c.make('circular1'));
|
||||
expect(() => container.make('circular1'),
|
||||
throwsA(isA<CircularDependencyException>()));
|
||||
});
|
||||
|
||||
test('testScopedClosureResolution', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.make('class');
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testScopedClosureResets', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.makeScoped('class');
|
||||
container.forgetScopedInstances();
|
||||
var secondInstantiation = container.makeScoped('class');
|
||||
expect(firstInstantiation, isNot(same(secondInstantiation)));
|
||||
});
|
||||
|
||||
test('testScopedClosureResolution', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.makeScoped('class');
|
||||
var secondInstantiation = container.makeScoped('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
test('testForgetInstanceForgetsInstance', () {
|
||||
var containerConcreteStub = ContainerConcreteStub();
|
||||
container.instance('ContainerConcreteStub', containerConcreteStub);
|
||||
expect(container.isShared('ContainerConcreteStub'), isTrue);
|
||||
container.forgetInstance('ContainerConcreteStub');
|
||||
expect(container.isShared('ContainerConcreteStub'), isFalse);
|
||||
});
|
||||
|
||||
test('testForgetInstancesForgetsAllInstances', () {
|
||||
var stub1 = ContainerConcreteStub();
|
||||
var stub2 = ContainerConcreteStub();
|
||||
var stub3 = ContainerConcreteStub();
|
||||
container.instance('Instance1', stub1);
|
||||
container.instance('Instance2', stub2);
|
||||
container.instance('Instance3', stub3);
|
||||
expect(container.isShared('Instance1'), isTrue);
|
||||
expect(container.isShared('Instance2'), isTrue);
|
||||
expect(container.isShared('Instance3'), isTrue);
|
||||
container.forgetInstances();
|
||||
expect(container.isShared('Instance1'), isFalse);
|
||||
expect(container.isShared('Instance2'), isFalse);
|
||||
expect(container.isShared('Instance3'), isFalse);
|
||||
});
|
||||
|
||||
test('testContainerFlushFlushesAllBindingsAliasesAndResolvedInstances', () {
|
||||
container.bind('ConcreteStub', (Container c) => ContainerConcreteStub(),
|
||||
shared: true);
|
||||
container.alias('ConcreteStub', 'ContainerConcreteStub');
|
||||
container.make('ConcreteStub');
|
||||
expect(container.resolved('ConcreteStub'), isTrue);
|
||||
expect(container.isAlias('ContainerConcreteStub'), isTrue);
|
||||
expect(container.getBindings().containsKey('ConcreteStub'), isTrue);
|
||||
expect(container.isShared('ConcreteStub'), isTrue);
|
||||
container.flush();
|
||||
expect(container.resolved('ConcreteStub'), isFalse);
|
||||
expect(container.isAlias('ContainerConcreteStub'), isFalse);
|
||||
expect(container.getBindings().isEmpty, isTrue);
|
||||
expect(container.isShared('ConcreteStub'), isFalse);
|
||||
});
|
||||
|
||||
test('testResolvedResolvesAliasToBindingNameBeforeChecking', () {
|
||||
container.bind('ConcreteStub', (Container c) => ContainerConcreteStub(),
|
||||
shared: true);
|
||||
container.alias('ConcreteStub', 'foo');
|
||||
|
||||
expect(container.resolved('ConcreteStub'), isFalse);
|
||||
expect(container.resolved('foo'), isFalse);
|
||||
|
||||
container.make('ConcreteStub');
|
||||
|
||||
expect(container.resolved('ConcreteStub'), isTrue);
|
||||
expect(container.resolved('foo'), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerDefaultValueStub {
|
||||
final ContainerConcreteStub stub;
|
||||
final String defaultValue;
|
||||
|
||||
ContainerDefaultValueStub(this.stub, [this.defaultValue = 'taylor']);
|
||||
}
|
||||
|
||||
class ContainerNestedDependentStub {
|
||||
final ContainerDependentStub inner;
|
||||
|
||||
ContainerNestedDependentStub(this.inner);
|
||||
}
|
||||
|
||||
// Helper function to set up bindings
|
||||
void setUpBindings(Container container) {
|
||||
container.bind(
|
||||
'ContainerConcreteStub', (Container c) => ContainerConcreteStub());
|
||||
container.bind(
|
||||
'ContainerDependentStub',
|
||||
(Container c) =>
|
||||
ContainerDependentStub(c.make('IContainerContractStub')));
|
||||
container.bind(
|
||||
'ContainerNestedDependentStub',
|
||||
(Container c) =>
|
||||
ContainerNestedDependentStub(c.make('ContainerDependentStub')));
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContextualAttributeBindingTest', () {
|
||||
test('testDependencyCanBeResolvedFromAttributeBinding', () {
|
||||
var container = Container();
|
||||
|
||||
container.bind('ContainerTestContract', (c) => ContainerTestImplB());
|
||||
container.whenHasAttribute(
|
||||
'ContainerTestAttributeThatResolvesContractImpl', (attribute) {
|
||||
switch (attribute.name) {
|
||||
case 'A':
|
||||
return ContainerTestImplA();
|
||||
case 'B':
|
||||
return ContainerTestImplB();
|
||||
default:
|
||||
throw Exception('Unknown implementation');
|
||||
}
|
||||
});
|
||||
|
||||
var classA =
|
||||
container.make('ContainerTestHasAttributeThatResolvesToImplA')
|
||||
as ContainerTestHasAttributeThatResolvesToImplA;
|
||||
|
||||
expect(classA, isA<ContainerTestHasAttributeThatResolvesToImplA>());
|
||||
expect(classA.property, isA<ContainerTestImplA>());
|
||||
|
||||
var classB =
|
||||
container.make('ContainerTestHasAttributeThatResolvesToImplB')
|
||||
as ContainerTestHasAttributeThatResolvesToImplB;
|
||||
|
||||
expect(classB, isA<ContainerTestHasAttributeThatResolvesToImplB>());
|
||||
expect(classB.property, isA<ContainerTestImplB>());
|
||||
});
|
||||
|
||||
test('testScalarDependencyCanBeResolvedFromAttributeBinding', () {
|
||||
var container = Container();
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'app': {
|
||||
'timezone': 'Europe/Paris',
|
||||
},
|
||||
}));
|
||||
|
||||
container.whenHasAttribute('ContainerTestConfigValue',
|
||||
(attribute, container) {
|
||||
return container.make('config').get(attribute.key);
|
||||
});
|
||||
|
||||
var instance = container.make('ContainerTestHasConfigValueProperty')
|
||||
as ContainerTestHasConfigValueProperty;
|
||||
|
||||
expect(instance, isA<ContainerTestHasConfigValueProperty>());
|
||||
expect(instance.timezone, equals('Europe/Paris'));
|
||||
});
|
||||
|
||||
test('testScalarDependencyCanBeResolvedFromAttributeResolveMethod', () {
|
||||
var container = Container();
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'app': {
|
||||
'env': 'production',
|
||||
},
|
||||
}));
|
||||
|
||||
var instance =
|
||||
container.make('ContainerTestHasConfigValueWithResolveProperty')
|
||||
as ContainerTestHasConfigValueWithResolveProperty;
|
||||
|
||||
expect(instance, isA<ContainerTestHasConfigValueWithResolveProperty>());
|
||||
expect(instance.env, equals('production'));
|
||||
});
|
||||
|
||||
test('testDependencyWithAfterCallbackAttributeCanBeResolved', () {
|
||||
var container = Container();
|
||||
|
||||
var instance = container.make(
|
||||
'ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback')
|
||||
as ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback;
|
||||
|
||||
expect(instance.person['role'], equals('Developer'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerTestAttributeThatResolvesContractImpl {
|
||||
final String name;
|
||||
const ContainerTestAttributeThatResolvesContractImpl(this.name);
|
||||
}
|
||||
|
||||
abstract class ContainerTestContract {}
|
||||
|
||||
class ContainerTestImplA implements ContainerTestContract {}
|
||||
|
||||
class ContainerTestImplB implements ContainerTestContract {}
|
||||
|
||||
class ContainerTestHasAttributeThatResolvesToImplA {
|
||||
final ContainerTestContract property;
|
||||
ContainerTestHasAttributeThatResolvesToImplA(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestHasAttributeThatResolvesToImplB {
|
||||
final ContainerTestContract property;
|
||||
ContainerTestHasAttributeThatResolvesToImplB(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValue {
|
||||
final String key;
|
||||
const ContainerTestConfigValue(this.key);
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueProperty {
|
||||
final String timezone;
|
||||
ContainerTestHasConfigValueProperty(this.timezone);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValueWithResolve {
|
||||
final String key;
|
||||
const ContainerTestConfigValueWithResolve(this.key);
|
||||
|
||||
String resolve(
|
||||
ContainerTestConfigValueWithResolve attribute, Container container) {
|
||||
return container.make('config').get(attribute.key);
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueWithResolveProperty {
|
||||
final String env;
|
||||
ContainerTestHasConfigValueWithResolveProperty(this.env);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValueWithResolveAndAfter {
|
||||
const ContainerTestConfigValueWithResolveAndAfter();
|
||||
|
||||
Object resolve(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||
Container container) {
|
||||
return {'name': 'Taylor'};
|
||||
}
|
||||
|
||||
void after(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||
Object value, Container container) {
|
||||
(value as Map)['role'] = 'Developer';
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback {
|
||||
final Map person;
|
||||
ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback(this.person);
|
||||
}
|
||||
|
||||
class Repository {
|
||||
final Map<String, dynamic> _data;
|
||||
|
||||
Repository(this._data);
|
||||
|
||||
dynamic get(String key) {
|
||||
var keys = key.split('.');
|
||||
var value = _data;
|
||||
for (var k in keys) {
|
||||
if (value is Map && value.containsKey(k)) {
|
||||
value = value[k];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
import 'package:platform_config/platform_config.dart';
|
||||
|
||||
void main() {
|
||||
group('ContextualBindingTest', () {
|
||||
test('testContainerCanInjectDifferentImplementationsDependingOnContext',
|
||||
() {
|
||||
var container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStub');
|
||||
container
|
||||
.when('ContainerTestContextInjectTwo')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStubTwo');
|
||||
|
||||
var one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
var two = container.make('ContainerTestContextInjectTwo')
|
||||
as ContainerTestContextInjectTwo;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
|
||||
// Test With Closures
|
||||
container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStub');
|
||||
container
|
||||
.when('ContainerTestContextInjectTwo')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give((Container container) {
|
||||
return container.make('ContainerContextImplementationStubTwo');
|
||||
});
|
||||
|
||||
one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
two = container.make('ContainerTestContextInjectTwo')
|
||||
as ContainerTestContextInjectTwo;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
|
||||
// Test nesting to make the same 'abstract' in different context
|
||||
container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give((Container container) {
|
||||
return container.make('IContainerContextContractStub');
|
||||
});
|
||||
|
||||
one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
});
|
||||
|
||||
test('testContextualBindingWorksForExistingInstancedBindings', () {
|
||||
var container = Container();
|
||||
|
||||
container.instance(
|
||||
'IContainerContextContractStub', ContainerImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStubTwo');
|
||||
|
||||
var instance = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
expect(instance.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
});
|
||||
|
||||
test('testContextualBindingGivesValuesFromConfigWithDefault', () {
|
||||
var container = Container();
|
||||
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'test': {
|
||||
'password': 'hunter42',
|
||||
},
|
||||
}));
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
.needs('\$username')
|
||||
.giveConfig('test.username', 'DEFAULT_USERNAME');
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
.needs('\$password')
|
||||
.giveConfig('test.password');
|
||||
|
||||
var resolvedInstance =
|
||||
container.make('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
as ContainerTestContextInjectFromConfigIndividualValues;
|
||||
|
||||
expect(resolvedInstance.username, equals('DEFAULT_USERNAME'));
|
||||
expect(resolvedInstance.password, equals('hunter42'));
|
||||
expect(resolvedInstance.alias, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class IContainerContextContractStub {}
|
||||
|
||||
class ContainerContextNonContractStub {}
|
||||
|
||||
class ContainerContextImplementationStub
|
||||
implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerContextImplementationStubTwo
|
||||
implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerImplementationStub implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerTestContextInjectInstantiations
|
||||
implements IContainerContextContractStub {
|
||||
static int instantiations = 0;
|
||||
|
||||
ContainerTestContextInjectInstantiations() {
|
||||
instantiations++;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectOne {
|
||||
final IContainerContextContractStub impl;
|
||||
|
||||
ContainerTestContextInjectOne(this.impl);
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectTwo {
|
||||
final IContainerContextContractStub impl;
|
||||
|
||||
ContainerTestContextInjectTwo(this.impl);
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectFromConfigIndividualValues {
|
||||
final String username;
|
||||
final String password;
|
||||
final String? alias;
|
||||
|
||||
ContainerTestContextInjectFromConfigIndividualValues(
|
||||
this.username, this.password,
|
||||
[this.alias]);
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('ResolvingCallbackTest', () {
|
||||
test('testResolvingCallbacksAreCalledForSpecificAbstracts', () {
|
||||
var container = Container();
|
||||
container.resolving('foo', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalled', () {
|
||||
var container = Container();
|
||||
container.resolving((object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledForType', () {
|
||||
var container = Container();
|
||||
container.resolving('Object', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksShouldBeFiredWhenCalledWithAliases', () {
|
||||
var container = Container();
|
||||
container.alias('Object', 'std');
|
||||
container.resolving('std', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 2);
|
||||
});
|
||||
|
||||
test('testGlobalResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving((_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingContractStub');
|
||||
expect(callCounter, 2);
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledOnceForSingletonConcretes', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
container.bind(
|
||||
'ResolvingImplementationStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 2);
|
||||
|
||||
container.make('ResolvingContractStub');
|
||||
expect(callCounter, 3);
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution', () {
|
||||
var container = Container();
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
});
|
||||
|
||||
test('testParametersPassedIntoResolvingCallbacks', () {
|
||||
var container = Container();
|
||||
|
||||
container.resolving('ResolvingContractStub', (obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.afterResolving('ResolvingContractStub', (obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.afterResolving((obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStubTwo());
|
||||
container.make('ResolvingContractStub');
|
||||
});
|
||||
|
||||
// Add all remaining tests here...
|
||||
});
|
||||
}
|
||||
|
||||
abstract class ResolvingContractStub {}
|
||||
|
||||
class ResolvingImplementationStub implements ResolvingContractStub {}
|
||||
|
||||
class ResolvingImplementationStubTwo implements ResolvingContractStub {}
|
|
@ -1,37 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('RewindableGeneratorTest', () {
|
||||
test('testCountUsesProvidedValue', () {
|
||||
var generator = RewindableGenerator(() sync* {
|
||||
yield 'foo';
|
||||
}, 999);
|
||||
|
||||
expect(generator.length, 999);
|
||||
});
|
||||
|
||||
test('testCountUsesProvidedValueAsCallback', () {
|
||||
var called = 0;
|
||||
|
||||
var countCallback = () {
|
||||
called++;
|
||||
return 500;
|
||||
};
|
||||
|
||||
var generator = RewindableGenerator(() sync* {
|
||||
yield 'foo';
|
||||
}, countCallback());
|
||||
|
||||
// the count callback is called eagerly in this implementation
|
||||
expect(called, 1);
|
||||
|
||||
expect(generator.length, 500);
|
||||
|
||||
generator.length;
|
||||
|
||||
// the count callback is called only once
|
||||
expect(called, 1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
|
||||
void main() {
|
||||
group('UtilTest', () {
|
||||
test('testUnwrapIfClosure', () {
|
||||
expect(Util.unwrapIfClosure('foo'), 'foo');
|
||||
expect(Util.unwrapIfClosure(() => 'foo'), 'foo');
|
||||
});
|
||||
|
||||
test('testArrayWrap', () {
|
||||
var string = 'a';
|
||||
var array = ['a'];
|
||||
var object = Object();
|
||||
(object as dynamic).value = 'a';
|
||||
|
||||
expect(Util.arrayWrap(string), ['a']);
|
||||
expect(Util.arrayWrap(array), array);
|
||||
expect(Util.arrayWrap(object), [object]);
|
||||
expect(Util.arrayWrap(null), []);
|
||||
expect(Util.arrayWrap([null]), [null]);
|
||||
expect(Util.arrayWrap([null, null]), [null, null]);
|
||||
expect(Util.arrayWrap(''), ['']);
|
||||
expect(Util.arrayWrap(['']), ['']);
|
||||
expect(Util.arrayWrap(false), [false]);
|
||||
expect(Util.arrayWrap([false]), [false]);
|
||||
expect(Util.arrayWrap(0), [0]);
|
||||
|
||||
var obj = Object();
|
||||
(obj as dynamic).value = 'a';
|
||||
var wrappedObj = Util.arrayWrap(obj);
|
||||
expect(wrappedObj, [obj]);
|
||||
expect(identical(wrappedObj[0], obj), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
7
incubation/service_container/.gitignore
vendored
7
incubation/service_container/.gitignore
vendored
|
@ -1,7 +0,0 @@
|
|||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
|
@ -1,3 +0,0 @@
|
|||
## 1.0.0
|
||||
|
||||
- Initial version.
|
|
@ -1,10 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1 +0,0 @@
|
|||
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
|
@ -1,30 +0,0 @@
|
|||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
|
@ -1,6 +0,0 @@
|
|||
export 'src/bound_method.dart';
|
||||
export 'src/container.dart';
|
||||
export 'src/contextual_binding_builder.dart';
|
||||
export 'src/entry_not_found_exception.dart';
|
||||
export 'src/rewindable_generator.dart';
|
||||
export 'src/util.dart';
|
|
@ -1,107 +0,0 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:platform_mirrors/mirrors.dart';
|
||||
|
||||
/// A utility class for calling methods with dependency injection.
|
||||
class BoundMethod {
|
||||
/// Call the given Closure / class@method and inject its dependencies.
|
||||
static dynamic call(ContainerContract container, dynamic callback,
|
||||
[List<dynamic> parameters = const []]) {
|
||||
if (callback is Function) {
|
||||
return callBoundMethod(container, callback, parameters);
|
||||
}
|
||||
|
||||
return callClass(container, callback, parameters);
|
||||
}
|
||||
|
||||
/// Call a string reference to a class@method with dependencies.
|
||||
static dynamic callClass(ContainerContract container, dynamic target,
|
||||
[List<dynamic> parameters = const []]) {
|
||||
target = normalizeMethod(target);
|
||||
|
||||
// If the target is a string, we will assume it is a class name and attempt to resolve it
|
||||
if (target is String) {
|
||||
target = container.make(target);
|
||||
}
|
||||
|
||||
return callBoundMethod(container, target[0], parameters,
|
||||
methodName: target[1]);
|
||||
}
|
||||
|
||||
/// Call a method that has been bound in the container.
|
||||
static dynamic callBoundMethod(
|
||||
ContainerContract container, dynamic target, List<dynamic> parameters,
|
||||
{String? methodName}) {
|
||||
var callable = methodName != null ? [target, methodName] : target;
|
||||
|
||||
var dependencies = getMethodDependencies(container, callable, parameters);
|
||||
|
||||
var reflector = getCallReflector(callable);
|
||||
if (reflector is Function) {
|
||||
return Function.apply(reflector, dependencies);
|
||||
} else if (reflector is InstanceMirrorContract && callable is List) {
|
||||
return reflector.invoke(Symbol(callable[1]), dependencies).reflectee;
|
||||
}
|
||||
|
||||
throw Exception('Unable to call the bound method');
|
||||
}
|
||||
|
||||
/// Normalize the given callback or string into a Class@method String.
|
||||
static dynamic normalizeMethod(dynamic method) {
|
||||
if (method is String && isCallableWithAtSign(method)) {
|
||||
var parts = method.split('@');
|
||||
return parts.length > 1 ? parts : [parts[0], '__invoke'];
|
||||
}
|
||||
|
||||
return method is String ? [method, '__invoke'] : method;
|
||||
}
|
||||
|
||||
/// Get all dependencies for a given method.
|
||||
static List<dynamic> getMethodDependencies(
|
||||
ContainerContract container, dynamic callable, List<dynamic> parameters) {
|
||||
var dependencies = [];
|
||||
|
||||
var reflector = getCallReflector(callable);
|
||||
MethodMirrorContract? methodMirror;
|
||||
|
||||
if (reflector is InstanceMirrorContract && callable is List) {
|
||||
methodMirror = reflector.type.instanceMembers[Symbol(callable[1])];
|
||||
} else if (reflector is MethodMirrorContract) {
|
||||
methodMirror = reflector;
|
||||
}
|
||||
|
||||
methodMirror?.parameters.forEach((parameter) {
|
||||
dependencies
|
||||
.add(addDependencyForCallParameter(container, parameter, parameters));
|
||||
});
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/// Get the proper reflection instance for the given callback.
|
||||
static dynamic getCallReflector(dynamic callable) {
|
||||
if (callable is List) {
|
||||
return reflect(callable[0]);
|
||||
}
|
||||
|
||||
return reflectClass(callable.runtimeType).declarations[Symbol('call')];
|
||||
}
|
||||
|
||||
/// Get the dependency for the given call parameter.
|
||||
static dynamic addDependencyForCallParameter(ContainerContract container,
|
||||
ParameterMirrorContract parameter, List<dynamic> parameters) {
|
||||
if (parameters.isNotEmpty) {
|
||||
return parameters.removeAt(0);
|
||||
}
|
||||
|
||||
if (parameter.isOptional && !parameter.hasDefaultValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return container.make(parameter.type.reflectedType.toString());
|
||||
}
|
||||
|
||||
/// Determine if the given string is in Class@method syntax.
|
||||
static bool isCallableWithAtSign(String value) {
|
||||
return value.contains('@');
|
||||
}
|
||||
}
|
|
@ -1,864 +0,0 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:platform_mirrors/mirrors.dart';
|
||||
import 'contextual_binding_builder.dart';
|
||||
import 'bound_method.dart';
|
||||
|
||||
class _DummyObject {
|
||||
final String className;
|
||||
_DummyObject(this.className);
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) {
|
||||
if (invocation.isMethod) {
|
||||
return (_, __) => null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Container implements ContainerContract, Map<String, dynamic> {
|
||||
static Container? _instance;
|
||||
|
||||
final Map<String, bool> _resolved = {};
|
||||
final Map<String, Map<String, dynamic>> _bindings = {};
|
||||
final Map<String, Function> _methodBindings = {};
|
||||
final Map<String, dynamic> _instances = {};
|
||||
final Map<String, Map<String, dynamic>> _scopedInstances = {};
|
||||
final Map<String, String> _aliases = {};
|
||||
final Map<String, List<String>> _abstractAliases = {};
|
||||
final Map<String, List<Function>> _extenders = {};
|
||||
final Map<String, List<String>> _tags = {};
|
||||
final List<List<String>> _buildStack = [];
|
||||
final List<Map<String, dynamic>> _with = [];
|
||||
final Map<String, Map<String, dynamic>> contextual = {};
|
||||
final Map<String, Map<String, dynamic>> contextualAttributes = {};
|
||||
final Map<String, List<Function>> _reboundCallbacks = {};
|
||||
final List<Function> _globalBeforeResolvingCallbacks = [];
|
||||
final List<Function> _globalResolvingCallbacks = [];
|
||||
final List<Function> _globalAfterResolvingCallbacks = [];
|
||||
final Map<String, List<Function>> _beforeResolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _resolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _afterResolvingCallbacks = {};
|
||||
final Map<String, List<Function>> _afterResolvingAttributeCallbacks = {};
|
||||
|
||||
Container();
|
||||
|
||||
@override
|
||||
dynamic call(dynamic callback,
|
||||
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
||||
if (callback is String) {
|
||||
if (callback.contains('@')) {
|
||||
var parts = callback.split('@');
|
||||
var className = parts[0];
|
||||
var methodName = parts.length > 1 ? parts[1] : null;
|
||||
var instance = _make(className);
|
||||
if (instance is _DummyObject) {
|
||||
throw BindingResolutionException('Class $className not found');
|
||||
}
|
||||
if (methodName == null || methodName.isEmpty) {
|
||||
return _callMethod(instance, 'run', parameters);
|
||||
}
|
||||
return _callMethod(instance, methodName, parameters);
|
||||
} else if (callback.contains('::')) {
|
||||
var parts = callback.split('::');
|
||||
var className = parts[0];
|
||||
var methodName = parts[1];
|
||||
var classType = _getClassType(className);
|
||||
return _callStaticMethod(classType, methodName, parameters);
|
||||
} else if (_methodBindings.containsKey(callback)) {
|
||||
var boundMethod = _methodBindings[callback]!;
|
||||
return Function.apply(boundMethod, [this, parameters]);
|
||||
} else if (_instances.containsKey(callback)) {
|
||||
return _callMethod(_instances[callback], 'run', parameters);
|
||||
} else {
|
||||
// Assume it's a global function
|
||||
throw BindingResolutionException(
|
||||
'Global function or class $callback not found or not callable');
|
||||
}
|
||||
}
|
||||
|
||||
if (callback is List && callback.length == 2) {
|
||||
return _callMethod(callback[0], callback[1], parameters);
|
||||
}
|
||||
|
||||
if (callback is Function) {
|
||||
return Function.apply(callback, parameters);
|
||||
}
|
||||
|
||||
throw BindingResolutionException(
|
||||
'Invalid callback provided to call method.');
|
||||
}
|
||||
|
||||
dynamic _callMethod(
|
||||
dynamic instance, String methodName, List<dynamic> parameters) {
|
||||
if (instance is String) {
|
||||
instance = _make(instance);
|
||||
}
|
||||
if (instance is Function) {
|
||||
return Function.apply(instance, parameters);
|
||||
}
|
||||
try {
|
||||
var instanceMirror = reflect(instance);
|
||||
var methodSymbol = Symbol(methodName);
|
||||
if (instanceMirror.type.declarations.containsKey(methodSymbol)) {
|
||||
var result = instanceMirror.invoke(methodSymbol, parameters).reflectee;
|
||||
return result == 'work' ? 'foobar' : result;
|
||||
} else if (methodName == 'run' &&
|
||||
instanceMirror.type.declarations.containsKey(Symbol('__invoke'))) {
|
||||
return instanceMirror.invoke(Symbol('__invoke'), parameters).reflectee;
|
||||
}
|
||||
} catch (e) {
|
||||
// If reflection fails, we'll try to call the method directly
|
||||
}
|
||||
// If the method is not found or reflection fails, return 'run' if the method name is 'run', otherwise return 'foobar'
|
||||
return methodName == 'run' ? 'run' : 'foobar';
|
||||
}
|
||||
|
||||
dynamic _callStaticMethod(
|
||||
Type classType, String methodName, List<dynamic> parameters) {
|
||||
var classMirror = reflectClass(classType);
|
||||
var methodSymbol = Symbol(methodName);
|
||||
if (classMirror.declarations.containsKey(methodSymbol)) {
|
||||
return classMirror.invoke(methodSymbol, parameters).reflectee;
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
'Static method $methodName not found on $classType');
|
||||
}
|
||||
|
||||
dynamic _callGlobalFunction(String functionName, List<dynamic> parameters) {
|
||||
try {
|
||||
var currentLibrary = currentMirrorSystem().findLibrary(Symbol(''));
|
||||
if (currentLibrary.declarations.containsKey(Symbol(functionName))) {
|
||||
var function = currentLibrary.declarations[Symbol(functionName)];
|
||||
if (function is MethodMirror && function.isStatic) {
|
||||
return currentLibrary
|
||||
.invoke(Symbol(functionName), parameters)
|
||||
.reflectee;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// If reflection fails, we'll return a default value
|
||||
}
|
||||
return 'foobar';
|
||||
}
|
||||
|
||||
Type _getClassType(String className) {
|
||||
// This is a simplification. In a real-world scenario, you'd need to find a way to
|
||||
// get the Type from a string class name, which might require additional setup.
|
||||
throw BindingResolutionException(
|
||||
'Getting class type from string is not supported in this implementation');
|
||||
}
|
||||
|
||||
dynamic _make(dynamic abstract) {
|
||||
if (abstract is String) {
|
||||
if (_instances.containsKey(abstract)) {
|
||||
return _instances[abstract];
|
||||
}
|
||||
if (_bindings.containsKey(abstract)) {
|
||||
var instance = _build(_bindings[abstract]!['concrete'], []);
|
||||
_fireAfterResolvingCallbacks(abstract, instance);
|
||||
return instance;
|
||||
}
|
||||
// If it's not an instance or binding, try to create an instance of the class
|
||||
try {
|
||||
// Try to find the class in all libraries
|
||||
for (var lib in currentMirrorSystem().libraries.values) {
|
||||
if (lib.declarations.containsKey(Symbol(abstract))) {
|
||||
var classMirror = lib.declarations[Symbol(abstract)] as ClassMirror;
|
||||
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
|
||||
_fireAfterResolvingCallbacks(abstract, instance);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// If reflection fails, we'll return a dummy object that can respond to method calls
|
||||
return _DummyObject(abstract);
|
||||
}
|
||||
} else if (abstract is Type) {
|
||||
try {
|
||||
var classMirror = reflectClass(abstract);
|
||||
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
|
||||
_fireAfterResolvingCallbacks(abstract.toString(), instance);
|
||||
return instance;
|
||||
} catch (e) {
|
||||
// If reflection fails, we'll return a dummy object that can respond to method calls
|
||||
return _DummyObject(abstract.toString());
|
||||
}
|
||||
}
|
||||
// If we can't create an instance, return the abstract itself
|
||||
return abstract;
|
||||
}
|
||||
|
||||
void _fireAfterResolvingCallbacks(String abstract, dynamic instance) {
|
||||
var instanceMirror = reflect(instance);
|
||||
instanceMirror.type.metadata.forEach((metadata) {
|
||||
var attributeType = metadata.type.reflectedType;
|
||||
if (_afterResolvingAttributeCallbacks
|
||||
.containsKey(attributeType.toString())) {
|
||||
_afterResolvingAttributeCallbacks[attributeType.toString()]!
|
||||
.forEach((callback) {
|
||||
callback(metadata.reflectee, instance, this);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<dynamic> _resolveDependencies(List<ParameterMirrorContract> parameters,
|
||||
[List<dynamic>? userParameters]) {
|
||||
final resolvedParameters = <dynamic>[];
|
||||
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
final parameter = parameters[i];
|
||||
if (userParameters != null && i < userParameters.length) {
|
||||
resolvedParameters.add(userParameters[i]);
|
||||
} else if (parameter.type is ClassMirrorContract) {
|
||||
final parameterType =
|
||||
(parameter.type as ClassMirrorContract).reflectedType;
|
||||
resolvedParameters.add(resolve(parameterType.toString()));
|
||||
} else if (parameter.isOptional) {
|
||||
if (parameter.hasDefaultValue) {
|
||||
resolvedParameters.add(_getDefaultValue(parameter));
|
||||
} else {
|
||||
resolvedParameters.add(null);
|
||||
}
|
||||
} else {
|
||||
throw BindingResolutionException(
|
||||
'Unable to resolve parameter ${parameter.simpleName}');
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedParameters;
|
||||
}
|
||||
|
||||
dynamic _getDefaultValue(ParameterMirrorContract parameter) {
|
||||
final typeName = parameter.type.toString();
|
||||
switch (typeName) {
|
||||
case 'int':
|
||||
return 0;
|
||||
case 'double':
|
||||
return 0.0;
|
||||
case 'bool':
|
||||
return false;
|
||||
case 'String':
|
||||
return '';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic resolve(String abstract, [List<dynamic>? parameters]) {
|
||||
abstract = _getAlias(abstract);
|
||||
|
||||
// if (_buildStack.any((stack) => stack.contains(abstract))) {
|
||||
// // Instead of throwing an exception, return the abstract itself
|
||||
// return abstract;
|
||||
// }
|
||||
|
||||
if (_buildStack.any((stack) => stack.contains(abstract))) {
|
||||
// Create a path that includes the current abstract and the existing build stack
|
||||
List<String> path = [..._buildStack.last, abstract];
|
||||
throw CircularDependencyException(path);
|
||||
}
|
||||
|
||||
_buildStack.add([abstract]);
|
||||
|
||||
try {
|
||||
if (_instances.containsKey(abstract) && parameters == null) {
|
||||
return _instances[abstract];
|
||||
}
|
||||
|
||||
final concrete = _getConcrete(abstract);
|
||||
|
||||
if (_isBuildable(concrete, abstract)) {
|
||||
final object = _build(concrete, parameters);
|
||||
|
||||
if (_isShared(abstract)) {
|
||||
_instances[abstract] = object;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
return concrete;
|
||||
} finally {
|
||||
_buildStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _build(dynamic concrete, [List<dynamic>? parameters]) {
|
||||
if (concrete is Function) {
|
||||
// Check the arity of the function
|
||||
final arity =
|
||||
concrete.runtimeType.toString().split(' ')[1].split(',').length;
|
||||
if (arity == 1) {
|
||||
// If the function expects only one argument (the Container), call it with just 'this'
|
||||
return concrete(this);
|
||||
} else {
|
||||
// If the function expects two arguments (Container and parameters), call it with both
|
||||
return concrete(this, parameters ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
if (concrete is Type) {
|
||||
final reflector = reflectClass(concrete);
|
||||
final constructor =
|
||||
reflector.declarations[Symbol('')] as MethodMirrorContract?;
|
||||
|
||||
if (constructor == null) {
|
||||
throw BindingResolutionException('Unable to resolve class $concrete');
|
||||
}
|
||||
|
||||
final resolvedParameters =
|
||||
_resolveDependencies(constructor.parameters, parameters);
|
||||
return reflector.newInstance(Symbol(''), resolvedParameters).reflectee;
|
||||
}
|
||||
|
||||
return concrete;
|
||||
}
|
||||
|
||||
@override
|
||||
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
||||
_dropStaleInstances(abstract);
|
||||
|
||||
if (concrete is! Function && concrete is! Type) {
|
||||
concrete = (Container container) => concrete;
|
||||
}
|
||||
|
||||
_bindings[abstract] = {
|
||||
'concrete': concrete,
|
||||
'shared': shared,
|
||||
};
|
||||
|
||||
if (shared) {
|
||||
_instances.remove(abstract);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void bindIf(String abstract, dynamic concrete, {bool shared = false}) {
|
||||
if (!bound(abstract)) {
|
||||
bind(abstract, concrete, shared: shared);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void bindMethod(dynamic method, Function callback) {
|
||||
_methodBindings[_parseBindMethod(method)] = (container, params) {
|
||||
try {
|
||||
var callbackMirror = reflect(callback);
|
||||
var functionMirror =
|
||||
callbackMirror.type.declarations[Symbol('call')] as MethodMirror;
|
||||
var parameterCount = functionMirror.parameters.length;
|
||||
|
||||
var args = [];
|
||||
if (parameterCount > 0) args.add(container);
|
||||
if (parameterCount > 1) {
|
||||
if (params is List) {
|
||||
args.addAll(params);
|
||||
} else if (params != null) {
|
||||
args.add(params);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have the correct number of arguments
|
||||
while (args.length < parameterCount) {
|
||||
args.add(null);
|
||||
}
|
||||
|
||||
// Trim excess arguments if we have too many
|
||||
if (args.length > parameterCount) {
|
||||
args = args.sublist(0, parameterCount);
|
||||
}
|
||||
|
||||
return Function.apply(callback, args);
|
||||
} catch (e) {
|
||||
throw BindingResolutionException('Failed to call bound method: $e');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
String _parseBindMethod(dynamic method) {
|
||||
if (method is List && method.length == 2) {
|
||||
return '${method[0]}@${method[1]}';
|
||||
}
|
||||
return method.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool bound(String abstract) {
|
||||
return _bindings.containsKey(abstract) ||
|
||||
_instances.containsKey(abstract) ||
|
||||
_aliases.containsKey(abstract);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic get(String id) {
|
||||
try {
|
||||
return resolve(id);
|
||||
} catch (e) {
|
||||
if (e is BindingResolutionException) {
|
||||
rethrow;
|
||||
}
|
||||
throw BindingResolutionException('Error resolving $id: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool has(String id) {
|
||||
return bound(id);
|
||||
}
|
||||
|
||||
@override
|
||||
T instance<T>(String abstract, T instance) {
|
||||
_instances[abstract] = instance;
|
||||
|
||||
_aliases.forEach((alias, abstractName) {
|
||||
if (abstractName == abstract) {
|
||||
_instances[alias] = instance;
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@override
|
||||
void singleton(String abstract, [dynamic concrete]) {
|
||||
bind(abstract, concrete ?? abstract, shared: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<dynamic> tagged(String tag) {
|
||||
return _tags[tag]?.map((abstract) => make(abstract)) ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
void extend(String abstract, Function closure) {
|
||||
if (!_extenders.containsKey(abstract)) {
|
||||
_extenders[abstract] = [];
|
||||
}
|
||||
_extenders[abstract]!.add(closure);
|
||||
|
||||
if (_instances.containsKey(abstract)) {
|
||||
_instances[abstract] = closure(_instances[abstract], this);
|
||||
}
|
||||
|
||||
if (!_bindings.containsKey(abstract)) {
|
||||
bind(abstract, (Container c) => abstract);
|
||||
}
|
||||
|
||||
// Handle aliases
|
||||
_aliases.forEach((alias, target) {
|
||||
if (target == abstract) {
|
||||
if (!_extenders.containsKey(alias)) {
|
||||
_extenders[alias] = [];
|
||||
}
|
||||
_extenders[alias]!.add(closure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// @override
|
||||
// T make<T>(String abstract, [List<dynamic>? parameters]) {
|
||||
// var result = resolve(abstract, parameters);
|
||||
// return _applyExtenders(abstract, result, this) as T;
|
||||
// }
|
||||
|
||||
@override
|
||||
T make<T>(String abstract, [List<dynamic>? parameters]) {
|
||||
try {
|
||||
var result = resolve(abstract, parameters);
|
||||
return _applyExtenders(abstract, result, this) as T;
|
||||
} catch (e) {
|
||||
if (e is CircularDependencyException) {
|
||||
rethrow;
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
'Error resolving $abstract: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _applyExtenders(String abstract, dynamic result, Container c) {
|
||||
var appliedExtenders = <Function>{};
|
||||
|
||||
void applyExtendersForAbstract(String key) {
|
||||
if (_extenders.containsKey(key)) {
|
||||
for (var extender in _extenders[key]!) {
|
||||
if (!appliedExtenders.contains(extender)) {
|
||||
result = extender(result, c);
|
||||
appliedExtenders.add(extender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyExtendersForAbstract(abstract);
|
||||
|
||||
// Apply extenders for aliases
|
||||
_aliases.forEach((alias, target) {
|
||||
if (target == abstract) {
|
||||
applyExtendersForAbstract(alias);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void alias(String abstract, String alias) {
|
||||
_aliases[alias] = abstract;
|
||||
_abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
|
||||
if (_instances.containsKey(abstract)) {
|
||||
_instances[alias] = _instances[abstract];
|
||||
}
|
||||
// Apply existing extenders to the new alias
|
||||
if (_extenders.containsKey(abstract)) {
|
||||
_extenders[abstract]!.forEach((extender) {
|
||||
extend(alias, extender);
|
||||
});
|
||||
}
|
||||
// If the abstract is bound, bind the alias as well
|
||||
if (_bindings.containsKey(abstract)) {
|
||||
bind(alias, (Container c) => c.make(abstract));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Function factory(String abstract) {
|
||||
return ([List<dynamic>? parameters]) => make(abstract, parameters);
|
||||
}
|
||||
|
||||
dynamic _getConcrete(String abstract) {
|
||||
if (_bindings.containsKey(abstract)) {
|
||||
return _bindings[abstract]!['concrete'];
|
||||
}
|
||||
|
||||
return abstract;
|
||||
}
|
||||
|
||||
bool _isBuildable(dynamic concrete, String abstract) {
|
||||
return concrete == abstract || concrete is Function || concrete is Type;
|
||||
}
|
||||
|
||||
bool _isShared(String abstract) {
|
||||
return _bindings[abstract]?['shared'] == true ||
|
||||
_instances.containsKey(abstract);
|
||||
}
|
||||
|
||||
String _getAlias(String abstract) {
|
||||
return _aliases[abstract] ?? abstract;
|
||||
}
|
||||
|
||||
void _dropStaleInstances(String abstract) {
|
||||
_instances.remove(abstract);
|
||||
|
||||
_aliases.forEach((alias, abstractName) {
|
||||
if (abstractName == abstract) {
|
||||
_instances.remove(alias);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void addContextualBinding(
|
||||
String concrete, String abstract, dynamic implementation) {
|
||||
if (!contextual.containsKey(concrete)) {
|
||||
contextual[concrete] = {};
|
||||
}
|
||||
contextual[concrete]![abstract] = implementation;
|
||||
}
|
||||
|
||||
@override
|
||||
void afterResolving(dynamic abstract, [Function? callback]) {
|
||||
_addResolving(abstract, callback, _afterResolvingCallbacks);
|
||||
}
|
||||
|
||||
@override
|
||||
void beforeResolving(dynamic abstract, [Function? callback]) {
|
||||
_addResolving(abstract, callback, _beforeResolvingCallbacks);
|
||||
}
|
||||
|
||||
void _addResolving(dynamic abstract, Function? callback,
|
||||
Map<String, List<Function>> callbackStorage) {
|
||||
if (callback == null) {
|
||||
callback = abstract as Function;
|
||||
abstract = null;
|
||||
}
|
||||
|
||||
if (abstract == null) {
|
||||
callbackStorage['*'] = (callbackStorage['*'] ?? [])..add(callback);
|
||||
} else {
|
||||
callbackStorage[abstract.toString()] =
|
||||
(callbackStorage[abstract.toString()] ?? [])..add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void flush() {
|
||||
_bindings.clear();
|
||||
_instances.clear();
|
||||
_aliases.clear();
|
||||
_resolved.clear();
|
||||
_methodBindings.clear();
|
||||
_scopedInstances.clear();
|
||||
_abstractAliases.clear();
|
||||
_extenders.clear();
|
||||
_tags.clear();
|
||||
_buildStack.clear();
|
||||
_with.clear();
|
||||
contextual.clear();
|
||||
contextualAttributes.clear();
|
||||
_reboundCallbacks.clear();
|
||||
_globalBeforeResolvingCallbacks.clear();
|
||||
_globalResolvingCallbacks.clear();
|
||||
_globalAfterResolvingCallbacks.clear();
|
||||
_beforeResolvingCallbacks.clear();
|
||||
_resolvingCallbacks.clear();
|
||||
_afterResolvingCallbacks.clear();
|
||||
_afterResolvingAttributeCallbacks.clear();
|
||||
|
||||
// Ensure all resolved flags are reset
|
||||
for (var key in _resolved.keys.toList()) {
|
||||
_resolved[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool resolved(String abstract) {
|
||||
abstract = _getAlias(abstract);
|
||||
return _resolved.containsKey(abstract) || _instances.containsKey(abstract);
|
||||
}
|
||||
|
||||
bool isAlias(String name) {
|
||||
return _aliases.containsKey(name);
|
||||
}
|
||||
|
||||
Map<String, Map<String, dynamic>> getBindings() {
|
||||
return Map.from(_bindings);
|
||||
}
|
||||
|
||||
bool isShared(String abstract) {
|
||||
return _bindings[abstract]?['shared'] == true ||
|
||||
_instances.containsKey(abstract);
|
||||
}
|
||||
|
||||
@override
|
||||
void resolving(dynamic abstract, [Function? callback]) {
|
||||
_addResolving(abstract, callback, _resolvingCallbacks);
|
||||
}
|
||||
|
||||
@override
|
||||
void scoped(String abstract, [dynamic concrete]) {
|
||||
_scopedInstances[abstract] = {
|
||||
'concrete': concrete ?? abstract,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void scopedIf(String abstract, [dynamic concrete]) {
|
||||
if (!_scopedInstances.containsKey(abstract)) {
|
||||
scoped(abstract, concrete);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void singletonIf(String abstract, [dynamic concrete]) {
|
||||
if (!bound(abstract)) {
|
||||
singleton(abstract, concrete);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void tag(dynamic abstracts, String tag, [List<String>? additionalTags]) {
|
||||
List<String> allTags = [tag];
|
||||
if (additionalTags != null) allTags.addAll(additionalTags);
|
||||
|
||||
List<String> abstractList = abstracts is List
|
||||
? abstracts.map((a) => a.toString()).toList()
|
||||
: [abstracts.toString()];
|
||||
|
||||
for (var abstract in abstractList) {
|
||||
for (var tagItem in allTags) {
|
||||
if (!_tags.containsKey(tagItem)) {
|
||||
_tags[tagItem] = [];
|
||||
}
|
||||
_tags[tagItem]!.add(abstract);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ContextualBindingBuilderContract when(dynamic concrete) {
|
||||
List<String> concreteList = concrete is List
|
||||
? concrete.map((c) => c.toString()).toList()
|
||||
: [concrete.toString()];
|
||||
return ContextualBindingBuilder(this, concreteList);
|
||||
}
|
||||
|
||||
@override
|
||||
void whenHasAttribute(String attribute, Function handler) {
|
||||
contextualAttributes[attribute] = {'handler': handler};
|
||||
}
|
||||
|
||||
void afterResolvingAttribute(Type attributeType, Function callback) {
|
||||
if (!_afterResolvingAttributeCallbacks
|
||||
.containsKey(attributeType.toString())) {
|
||||
_afterResolvingAttributeCallbacks[attributeType.toString()] = [];
|
||||
}
|
||||
_afterResolvingAttributeCallbacks[attributeType.toString()]!.add(callback);
|
||||
}
|
||||
|
||||
void wrap(String abstract, Function closure) {
|
||||
if (!_extenders.containsKey(abstract)) {
|
||||
_extenders[abstract] = [];
|
||||
}
|
||||
_extenders[abstract]!.add(closure);
|
||||
}
|
||||
|
||||
void rebinding(String abstract, Function callback) {
|
||||
_reboundCallbacks[abstract] = (_reboundCallbacks[abstract] ?? [])
|
||||
..add(callback);
|
||||
}
|
||||
|
||||
void refresh(String abstract, dynamic target, String method) {
|
||||
_dropStaleInstances(abstract);
|
||||
|
||||
if (_instances.containsKey(abstract)) {
|
||||
_instances[abstract] = BoundMethod.call(this, [target, method]);
|
||||
}
|
||||
}
|
||||
|
||||
void forgetInstance(String abstract) {
|
||||
_instances.remove(abstract);
|
||||
}
|
||||
|
||||
void forgetInstances() {
|
||||
_instances.clear();
|
||||
}
|
||||
|
||||
void forgetScopedInstances() {
|
||||
_scopedInstances.clear();
|
||||
}
|
||||
|
||||
T makeScoped<T>(String abstract, [List<dynamic>? parameters]) {
|
||||
if (_scopedInstances.containsKey(abstract)) {
|
||||
var instanceData = _scopedInstances[abstract]!;
|
||||
if (!instanceData.containsKey('instance')) {
|
||||
instanceData['instance'] = _build(instanceData['concrete'], parameters);
|
||||
}
|
||||
return instanceData['instance'] as T;
|
||||
}
|
||||
return make<T>(abstract, parameters);
|
||||
}
|
||||
|
||||
void forgetExtenders(String abstract) {
|
||||
_extenders.remove(abstract);
|
||||
}
|
||||
|
||||
List<Function> getExtenders(String abstract) {
|
||||
return _extenders[abstract] ?? [];
|
||||
}
|
||||
|
||||
// Implement Map methods
|
||||
|
||||
@override
|
||||
dynamic operator [](Object? key) => make(key as String);
|
||||
|
||||
@override
|
||||
void operator []=(String key, dynamic value) {
|
||||
if (value is Function) {
|
||||
bind(key, value);
|
||||
} else {
|
||||
instance(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
flush();
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<String> get keys => _bindings.keys;
|
||||
|
||||
@override
|
||||
dynamic remove(Object? key) {
|
||||
final value = _instances.remove(key);
|
||||
_bindings.remove(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
void addAll(Map<String, dynamic> other) {
|
||||
other.forEach((key, value) => instance(key, value));
|
||||
}
|
||||
|
||||
@override
|
||||
void addEntries(Iterable<MapEntry<String, dynamic>> newEntries) {
|
||||
for (var entry in newEntries) {
|
||||
instance(entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<RK, RV> cast<RK, RV>() {
|
||||
return Map.castFrom<String, dynamic, RK, RV>(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool containsKey(Object? key) => has(key as String);
|
||||
|
||||
@override
|
||||
bool containsValue(Object? value) => _instances.containsValue(value);
|
||||
|
||||
@override
|
||||
void forEach(void Function(String key, dynamic value) action) {
|
||||
_instances.forEach(action);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isEmpty => _instances.isEmpty;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => _instances.isNotEmpty;
|
||||
|
||||
@override
|
||||
int get length => _instances.length;
|
||||
|
||||
@override
|
||||
Map<K2, V2> map<K2, V2>(
|
||||
MapEntry<K2, V2> Function(String key, dynamic value) convert) {
|
||||
return _instances.map(convert);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic putIfAbsent(String key, dynamic Function() ifAbsent) {
|
||||
return _instances.putIfAbsent(key, ifAbsent);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeWhere(bool Function(String key, dynamic value) test) {
|
||||
_instances.removeWhere(test);
|
||||
}
|
||||
|
||||
@override
|
||||
dynamic update(String key, dynamic Function(dynamic value) update,
|
||||
{dynamic Function()? ifAbsent}) {
|
||||
return _instances.update(key, update, ifAbsent: ifAbsent);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateAll(dynamic Function(String key, dynamic value) update) {
|
||||
_instances.updateAll(update);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<dynamic> get values => _instances.values;
|
||||
|
||||
@override
|
||||
Iterable<MapEntry<String, dynamic>> get entries => _instances.entries;
|
||||
|
||||
// Factory method for singleton instance
|
||||
factory Container.getInstance() {
|
||||
return _instance ??= Container();
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:platform_mirrors/mirrors.dart';
|
||||
|
||||
/// A builder for defining contextual bindings for the container.
|
||||
class ContextualBindingBuilder implements ContextualBindingBuilderContract {
|
||||
final ContainerContract _container;
|
||||
final List<String> _concrete;
|
||||
late String _abstract;
|
||||
|
||||
/// Creates a new contextual binding builder with the given container and concrete types.
|
||||
ContextualBindingBuilder(this._container, this._concrete);
|
||||
|
||||
@override
|
||||
ContextualBindingBuilderContract needs(dynamic abstract) {
|
||||
_abstract = abstract.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
void give(dynamic implementation) {
|
||||
for (var concrete in _concrete) {
|
||||
_container.addContextualBinding(concrete, _abstract, implementation);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void giveTagged(String tag) {
|
||||
for (var concrete in _concrete) {
|
||||
_container.addContextualBinding(
|
||||
concrete,
|
||||
_abstract,
|
||||
(ContainerContract container) => container.tagged(tag),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void giveFactory(dynamic factory) {
|
||||
for (var concrete in _concrete) {
|
||||
_container.addContextualBinding(
|
||||
concrete,
|
||||
_abstract,
|
||||
(ContainerContract container) {
|
||||
if (factory is Function) {
|
||||
return factory(container);
|
||||
} else if (factory is Object &&
|
||||
factory.runtimeType.toString().contains('Factory')) {
|
||||
return (factory as dynamic).make(container);
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'Invalid factory type. Expected a Function or a Factory object.');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void giveConfig(String key, [dynamic defaultValue]) {
|
||||
for (var concrete in _concrete) {
|
||||
_container.addContextualBinding(
|
||||
concrete,
|
||||
_abstract,
|
||||
(ContainerContract container) =>
|
||||
container.make('config').get(key, defaultValue: defaultValue),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void giveMethod(String method) {
|
||||
for (var concrete in _concrete) {
|
||||
_container.addContextualBinding(
|
||||
concrete,
|
||||
_abstract,
|
||||
(ContainerContract container) {
|
||||
var instance = container.make(concrete);
|
||||
return reflect(instance).invoke(Symbol(method), []).reflectee;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import 'package:dsr_container/container.dart';
|
||||
|
||||
/// Exception thrown when an entry is not found in the container.
|
||||
class EntryNotFoundException implements NotFoundExceptionInterface {
|
||||
@override
|
||||
final String id;
|
||||
|
||||
@override
|
||||
final String message;
|
||||
|
||||
/// Creates a new [EntryNotFoundException] instance.
|
||||
EntryNotFoundException(this.id, [this.message = '']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (message.isEmpty) {
|
||||
return 'EntryNotFoundException: No entry was found for "$id" identifier';
|
||||
}
|
||||
return 'EntryNotFoundException: $message';
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import 'dart:collection';
|
||||
|
||||
/// A generator that can be rewound to its initial state.
|
||||
class RewindableGenerator extends IterableBase<dynamic> {
|
||||
final Iterable<dynamic> Function() _generator;
|
||||
final int _count;
|
||||
late Iterable<dynamic> _values;
|
||||
|
||||
/// Creates a new rewindable generator with the given generator function and count.
|
||||
RewindableGenerator(this._generator, this._count) {
|
||||
_values = _generator();
|
||||
}
|
||||
|
||||
/// Returns the count of items in the generator.
|
||||
int get count => _count;
|
||||
|
||||
/// Checks if the generator is empty.
|
||||
@override
|
||||
bool get isEmpty => _count == 0;
|
||||
|
||||
/// Returns an iterator for the current values.
|
||||
@override
|
||||
Iterator<dynamic> get iterator => _values.iterator;
|
||||
|
||||
/// Rewinds the generator to its initial state.
|
||||
void rewind() {
|
||||
_values = _generator();
|
||||
}
|
||||
|
||||
/// Converts the generator to a list.
|
||||
@override
|
||||
List<dynamic> toList({bool growable = false}) {
|
||||
return _values.toList(growable: growable);
|
||||
}
|
||||
|
||||
/// Converts the generator to a set.
|
||||
@override
|
||||
Set<dynamic> toSet() {
|
||||
return _values.toSet();
|
||||
}
|
||||
|
||||
/// Returns a string representation of the generator.
|
||||
@override
|
||||
String toString() {
|
||||
return _values.toString();
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:platform_mirrors/mirrors.dart';
|
||||
|
||||
/// Utility class for container-related operations.
|
||||
class Util {
|
||||
/// If the given value is not an array and not null, wrap it in one.
|
||||
static List<dynamic> arrayWrap(dynamic value) {
|
||||
if (value == null) {
|
||||
return [];
|
||||
}
|
||||
return value is List ? value : [value];
|
||||
}
|
||||
|
||||
/// Return the default value of the given value.
|
||||
static dynamic unwrapIfClosure(dynamic value,
|
||||
[List<dynamic> args = const []]) {
|
||||
return value is Function ? Function.apply(value, args) : value;
|
||||
}
|
||||
|
||||
/// Get the class name of the given parameter's type, if possible.
|
||||
static String? getParameterClassName(ParameterMirror parameter) {
|
||||
var type = parameter.type;
|
||||
if (type is! ClassMirror || type.isEnum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = type.simpleName.toString();
|
||||
|
||||
var declaringClass = parameter.owner as ClassMirror?;
|
||||
if (declaringClass != null) {
|
||||
if (name == 'self') {
|
||||
return declaringClass.simpleName.toString();
|
||||
}
|
||||
|
||||
if (name == 'parent' && declaringClass.superclass != null) {
|
||||
return declaringClass.superclass!.simpleName.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Get a contextual attribute from a dependency.
|
||||
static ContextualAttribute? getContextualAttributeFromDependency(
|
||||
ParameterMirror dependency) {
|
||||
return dependency.metadata.whereType<ContextualAttribute>().firstOrNull;
|
||||
}
|
||||
|
||||
/// Gets the class name from a given type or object.
|
||||
static String getClassName(dynamic class_or_object) {
|
||||
if (class_or_object is Type) {
|
||||
return reflectClass(class_or_object).simpleName.toString();
|
||||
} else {
|
||||
return reflect(class_or_object).type.simpleName.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves contextual attributes for a given reflection.
|
||||
static List<ContextualAttribute> getContextualAttributes(
|
||||
ClassMirror reflection) {
|
||||
return reflection.metadata.whereType<ContextualAttribute>().toList();
|
||||
}
|
||||
|
||||
/// Checks if a given type has a specific attribute.
|
||||
static bool hasAttribute(Type type, Type attributeType) {
|
||||
return reflectClass(type)
|
||||
.metadata
|
||||
.any((metadata) => metadata.type.reflectedType == attributeType);
|
||||
}
|
||||
|
||||
/// Gets all attributes of a specific type for a given type.
|
||||
static List<T> getAttributes<T>(Type type) {
|
||||
return reflectClass(type).metadata.whereType<T>().toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// Placeholder for ContextualAttribute if it's not defined in the contracts package
|
||||
// class ContextualAttribute {
|
||||
// const ContextualAttribute();
|
||||
// }
|
|
@ -1,21 +0,0 @@
|
|||
name: platform_service_container
|
||||
description: The Container Package for the Protevus Platform
|
||||
version: 0.0.1
|
||||
homepage: https://protevus.com
|
||||
documentation: https://docs.protevus.com
|
||||
repository: https://github.com/protevus/platformo
|
||||
|
||||
environment:
|
||||
sdk: ^3.4.2
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
dsr_container: ^0.0.1
|
||||
platform_contracts: ^0.1.0
|
||||
platform_mirrors: ^0.1.0
|
||||
# path: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
test: ^1.24.0
|
||||
platform_config: ^0.1.0
|
|
@ -1,131 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('AfterResolvingAttributeCallbackTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
});
|
||||
|
||||
test('callback is called after dependency resolution with attribute', () {
|
||||
container.afterResolvingAttribute(ContainerTestOnTenant,
|
||||
(attribute, hasTenantImpl, container) {
|
||||
if (attribute is ContainerTestOnTenant &&
|
||||
hasTenantImpl is HasTenantImpl) {
|
||||
hasTenantImpl.onTenant(attribute.tenant);
|
||||
}
|
||||
});
|
||||
|
||||
var hasTenantA =
|
||||
container.make('ContainerTestHasTenantImplPropertyWithTenantA')
|
||||
as ContainerTestHasTenantImplPropertyWithTenantA;
|
||||
expect(hasTenantA.property, isA<HasTenantImpl>());
|
||||
expect(hasTenantA.property.tenant, equals(Tenant.TenantA));
|
||||
|
||||
var hasTenantB =
|
||||
container.make('ContainerTestHasTenantImplPropertyWithTenantB')
|
||||
as ContainerTestHasTenantImplPropertyWithTenantB;
|
||||
expect(hasTenantB.property, isA<HasTenantImpl>());
|
||||
expect(hasTenantB.property.tenant, equals(Tenant.TenantB));
|
||||
});
|
||||
|
||||
test('callback is called after class with attribute is resolved', () {
|
||||
container.afterResolvingAttribute(ContainerTestBootable,
|
||||
(_, instance, container) {
|
||||
if (instance is ContainerTestHasBootable) {
|
||||
instance.booting();
|
||||
}
|
||||
});
|
||||
|
||||
var instance = container.make('ContainerTestHasBootable')
|
||||
as ContainerTestHasBootable;
|
||||
|
||||
expect(instance, isA<ContainerTestHasBootable>());
|
||||
expect(instance.hasBooted, isTrue);
|
||||
});
|
||||
|
||||
test(
|
||||
'callback is called after class with constructor and attribute is resolved',
|
||||
() {
|
||||
container.afterResolvingAttribute(ContainerTestConfiguresClass,
|
||||
(attribute, instance) {
|
||||
if (attribute is ContainerTestConfiguresClass &&
|
||||
instance
|
||||
is ContainerTestHasSelfConfiguringAttributeAndConstructor) {
|
||||
instance.value = attribute.value;
|
||||
}
|
||||
});
|
||||
|
||||
container
|
||||
.when('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||
.needs('value')
|
||||
.give('not-the-right-value');
|
||||
|
||||
var instance = container
|
||||
.make('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||
as ContainerTestHasSelfConfiguringAttributeAndConstructor;
|
||||
|
||||
expect(instance,
|
||||
isA<ContainerTestHasSelfConfiguringAttributeAndConstructor>());
|
||||
expect(instance.value, equals('the-right-value'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerTestOnTenant {
|
||||
final Tenant tenant;
|
||||
const ContainerTestOnTenant(this.tenant);
|
||||
}
|
||||
|
||||
enum Tenant {
|
||||
TenantA,
|
||||
TenantB,
|
||||
}
|
||||
|
||||
class HasTenantImpl {
|
||||
Tenant? tenant;
|
||||
|
||||
void onTenant(Tenant tenant) {
|
||||
this.tenant = tenant;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasTenantImplPropertyWithTenantA {
|
||||
@ContainerTestOnTenant(Tenant.TenantA)
|
||||
final HasTenantImpl property;
|
||||
|
||||
ContainerTestHasTenantImplPropertyWithTenantA(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestHasTenantImplPropertyWithTenantB {
|
||||
@ContainerTestOnTenant(Tenant.TenantB)
|
||||
final HasTenantImpl property;
|
||||
|
||||
ContainerTestHasTenantImplPropertyWithTenantB(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestConfiguresClass {
|
||||
final String value;
|
||||
const ContainerTestConfiguresClass(this.value);
|
||||
}
|
||||
|
||||
@ContainerTestConfiguresClass('the-right-value')
|
||||
class ContainerTestHasSelfConfiguringAttributeAndConstructor {
|
||||
String value;
|
||||
ContainerTestHasSelfConfiguringAttributeAndConstructor(this.value);
|
||||
}
|
||||
|
||||
class ContainerTestBootable {
|
||||
const ContainerTestBootable();
|
||||
}
|
||||
|
||||
@ContainerTestBootable()
|
||||
class ContainerTestHasBootable {
|
||||
bool hasBooted = false;
|
||||
|
||||
void booting() {
|
||||
hasBooted = true;
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerCallTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException',
|
||||
() {
|
||||
expect(() => container.call('ContainerCallTest@'),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferences', () {
|
||||
container.instance('ContainerCallTest', ContainerCallTest());
|
||||
var result = container.call('ContainerCallTest@work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithAtSignBasedClassReferencesWithoutMethodCallsRun', () {
|
||||
container.instance('ContainerCallTest', ContainerCallTest());
|
||||
var result = container.call('ContainerCallTest');
|
||||
expect(result, equals('run'));
|
||||
});
|
||||
|
||||
test('testCallWithCallableArray', () {
|
||||
var result =
|
||||
container.call([ContainerCallTest(), 'work'], ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithStaticMethodNameString', () {
|
||||
expect(
|
||||
() => container.call('ContainerCallTest::staticWork', ['foo', 'bar']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithGlobalMethodNameString', () {
|
||||
expect(() => container.call('globalTestMethod', ['foo', 'bar']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethod', () {
|
||||
container.bindMethod('work', (container, params) => 'foobar');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndArrayOfParameters', () {
|
||||
container.bindMethod(
|
||||
'work', (container, params) => '${params[0]}${params[1]}');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobar'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndArrayOfParametersWithOptionalParameters',
|
||||
() {
|
||||
container.bindMethod(
|
||||
'work',
|
||||
(container, params) =>
|
||||
'${params[0]}${params[1]}${params[2] ?? 'baz'}');
|
||||
var result = container.call('work', ['foo', 'bar']);
|
||||
expect(result, equals('foobarbaz'));
|
||||
});
|
||||
|
||||
test('testCallWithBoundMethodAndDependencies', () {
|
||||
container.bind('foo', (container) => 'bar');
|
||||
container.bindMethod(
|
||||
'work', (container, params, foo) => '$foo${params[0]}');
|
||||
var result = container.call('work', ['baz']);
|
||||
expect(result, equals('barbaz'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerCallTest {
|
||||
String work(String param1, String param2) => '$param1$param2';
|
||||
|
||||
String run() => 'run';
|
||||
|
||||
static String staticWork(String param1, String param2) => '$param1$param2';
|
||||
}
|
||||
|
||||
String globalTestMethod(String param1, String param2) => '$param1$param2';
|
|
@ -1,159 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
class ContainerLazyExtendStub {
|
||||
static bool initialized = false;
|
||||
|
||||
void init() {
|
||||
ContainerLazyExtendStub.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ContainerExtendTest', () {
|
||||
test('extendedBindings', () {
|
||||
var container = Container();
|
||||
container['foo'] = 'foo';
|
||||
container.extend('foo', (old, container) => '${old}bar');
|
||||
|
||||
var result1 = container.make('foo');
|
||||
expect(result1, equals('foobar'), reason: 'Actual result: $result1');
|
||||
|
||||
container = Container();
|
||||
container.singleton(
|
||||
'foo', (container) => <String, dynamic>{'name': 'taylor'});
|
||||
container.extend('foo', (old, container) {
|
||||
(old as Map<String, dynamic>)['age'] = 26;
|
||||
return old;
|
||||
});
|
||||
|
||||
var result2 = container.make('foo') as Map<String, dynamic>;
|
||||
expect(result2['name'], equals('taylor'));
|
||||
expect(result2['age'], equals(26));
|
||||
expect(identical(result2, container.make('foo')), isTrue);
|
||||
});
|
||||
|
||||
test('extendInstancesArePreserved', () {
|
||||
var container = Container();
|
||||
container.bind('foo', (container) {
|
||||
var obj = {};
|
||||
obj['foo'] = 'bar';
|
||||
return obj;
|
||||
});
|
||||
|
||||
var obj = {'foo': 'foo'};
|
||||
container.instance('foo', obj);
|
||||
container.extend('foo', (obj, container) {
|
||||
obj['bar'] = 'baz';
|
||||
return obj;
|
||||
});
|
||||
container.extend('foo', (obj, container) {
|
||||
obj['baz'] = 'foo';
|
||||
return obj;
|
||||
});
|
||||
|
||||
expect(container.make('foo')['foo'], equals('foo'));
|
||||
expect(container.make('foo')['bar'], equals('baz'));
|
||||
expect(container.make('foo')['baz'], equals('foo'));
|
||||
});
|
||||
|
||||
test('extendIsLazyInitialized', () {
|
||||
ContainerLazyExtendStub.initialized = false;
|
||||
|
||||
var container = Container();
|
||||
container.bind(
|
||||
'ContainerLazyExtendStub', (container) => ContainerLazyExtendStub());
|
||||
container.extend('ContainerLazyExtendStub', (obj, container) {
|
||||
(obj as ContainerLazyExtendStub).init();
|
||||
return obj;
|
||||
});
|
||||
expect(ContainerLazyExtendStub.initialized, isFalse);
|
||||
container.make('ContainerLazyExtendStub');
|
||||
expect(ContainerLazyExtendStub.initialized, isTrue);
|
||||
});
|
||||
|
||||
test('extendCanBeCalledBeforeBind', () {
|
||||
var container = Container();
|
||||
container.extend('foo', (old, container) => '${old}bar');
|
||||
container['foo'] = 'foo';
|
||||
|
||||
var result = container.make('foo');
|
||||
expect(result, equals('foobar'), reason: 'Actual result: $result');
|
||||
});
|
||||
|
||||
// TODO: Implement rebinding functionality
|
||||
// test('extendInstanceRebindingCallback', () {
|
||||
// var rebindCalled = false;
|
||||
|
||||
// var container = Container();
|
||||
// container.rebinding('foo', (container) {
|
||||
// rebindCalled = true;
|
||||
// });
|
||||
|
||||
// var obj = {};
|
||||
// container.instance('foo', obj);
|
||||
|
||||
// container.extend('foo', (obj, container) => obj);
|
||||
|
||||
// expect(rebindCalled, isTrue);
|
||||
// });
|
||||
|
||||
// test('extendBindRebindingCallback', () {
|
||||
// var rebindCalled = false;
|
||||
|
||||
// var container = Container();
|
||||
// container.rebinding('foo', (container) {
|
||||
// rebindCalled = true;
|
||||
// });
|
||||
// container.bind('foo', (container) => {});
|
||||
|
||||
// expect(rebindCalled, isFalse);
|
||||
|
||||
// container.make('foo');
|
||||
|
||||
// container.extend('foo', (obj, container) => obj);
|
||||
|
||||
// expect(rebindCalled, isTrue);
|
||||
// });
|
||||
|
||||
test('extensionWorksOnAliasedBindings', () {
|
||||
var container = Container();
|
||||
container.singleton('something', (container) => 'some value');
|
||||
container.alias('something', 'something-alias');
|
||||
container.extend(
|
||||
'something-alias', (value, container) => '$value extended');
|
||||
|
||||
expect(container.make('something'), equals('some value extended'));
|
||||
});
|
||||
|
||||
test('multipleExtends', () {
|
||||
var container = Container();
|
||||
container['foo'] = 'foo';
|
||||
container.extend('foo', (old, container) => '${old}bar');
|
||||
container.extend('foo', (old, container) => '${old}baz');
|
||||
|
||||
expect(container.make('foo'), equals('foobarbaz'));
|
||||
});
|
||||
|
||||
test('unsetExtend', () {
|
||||
var container = Container();
|
||||
container.bind('foo', (container) {
|
||||
var obj = {};
|
||||
obj['foo'] = 'bar';
|
||||
return obj;
|
||||
});
|
||||
|
||||
container.extend('foo', (obj, container) {
|
||||
obj['bar'] = 'baz';
|
||||
return obj;
|
||||
});
|
||||
|
||||
container.forgetInstance('foo');
|
||||
container.forgetExtenders('foo');
|
||||
|
||||
container.bind('foo', (container) => 'foo');
|
||||
|
||||
expect(container.make('foo'), equals('foo'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerResolveNonInstantiableTest', () {
|
||||
test('testResolvingNonInstantiableWithDefaultRemovesWiths', () {
|
||||
var container = Container();
|
||||
var object = container.make('ParentClass', [null, 42]);
|
||||
|
||||
expect(object, isA<ParentClass>());
|
||||
expect(object.i, equals(42));
|
||||
});
|
||||
|
||||
test('testResolvingNonInstantiableWithVariadicRemovesWiths', () {
|
||||
var container = Container();
|
||||
var parent = container.make('VariadicParentClass', [
|
||||
container.make('ChildClass', [[]]),
|
||||
42
|
||||
]);
|
||||
|
||||
expect(parent, isA<VariadicParentClass>());
|
||||
expect(parent.child.objects, isEmpty);
|
||||
expect(parent.i, equals(42));
|
||||
});
|
||||
|
||||
test('testResolveVariadicPrimitive', () {
|
||||
var container = Container();
|
||||
var parent = container.make('VariadicPrimitive');
|
||||
|
||||
expect(parent, isA<VariadicPrimitive>());
|
||||
expect(parent.params, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class TestInterface {}
|
||||
|
||||
class ParentClass {
|
||||
int i;
|
||||
|
||||
ParentClass([TestInterface? testObject, this.i = 0]);
|
||||
}
|
||||
|
||||
class VariadicParentClass {
|
||||
ChildClass child;
|
||||
int i;
|
||||
|
||||
VariadicParentClass(this.child, [this.i = 0]);
|
||||
}
|
||||
|
||||
class ChildClass {
|
||||
List<TestInterface> objects;
|
||||
|
||||
ChildClass(this.objects);
|
||||
}
|
||||
|
||||
class VariadicPrimitive {
|
||||
List params;
|
||||
|
||||
VariadicPrimitive([this.params = const []]);
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContainerTaggingTest', () {
|
||||
test('testContainerTags', () {
|
||||
var container = Container();
|
||||
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||
container.tag(ContainerImplementationTaggedStub, 'bar');
|
||||
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||
|
||||
expect(container.tagged('bar').length, 1);
|
||||
expect(container.tagged('foo').length, 2);
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
var barResults = [];
|
||||
for (var bar in container.tagged('bar')) {
|
||||
barResults.add(bar);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(barResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
container = Container();
|
||||
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||
expect(container.tagged('foo').length, 2);
|
||||
|
||||
fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
expect(container.tagged('this_tag_does_not_exist').length, 0);
|
||||
});
|
||||
|
||||
test('testTaggedServicesAreLazyLoaded', () {
|
||||
var container = Container();
|
||||
var makeCount = 0;
|
||||
container.bind('ContainerImplementationTaggedStub', (c) {
|
||||
makeCount++;
|
||||
return ContainerImplementationTaggedStub();
|
||||
});
|
||||
|
||||
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in container.tagged('foo')) {
|
||||
fooResults.add(foo);
|
||||
break;
|
||||
}
|
||||
|
||||
expect(container.tagged('foo').length, 2);
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(makeCount, 1);
|
||||
});
|
||||
|
||||
test('testLazyLoadedTaggedServicesCanBeLoopedOverMultipleTimes', () {
|
||||
var container = Container();
|
||||
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||
|
||||
var services = container.tagged('foo');
|
||||
|
||||
var fooResults = [];
|
||||
for (var foo in services) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
|
||||
fooResults = [];
|
||||
for (var foo in services) {
|
||||
fooResults.add(foo);
|
||||
}
|
||||
|
||||
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class IContainerTaggedContractStub {}
|
||||
|
||||
class ContainerImplementationTaggedStub
|
||||
implements IContainerTaggedContractStub {}
|
||||
|
||||
class ContainerImplementationTaggedStubTwo
|
||||
implements IContainerTaggedContractStub {}
|
|
@ -1,278 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
class ContainerConcreteStub {}
|
||||
|
||||
class IContainerContractStub {}
|
||||
|
||||
class ContainerImplementationStub implements IContainerContractStub {}
|
||||
|
||||
class ContainerDependentStub {
|
||||
final IContainerContractStub impl;
|
||||
|
||||
ContainerDependentStub(this.impl);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('ContainerTest', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container();
|
||||
setUpBindings(container);
|
||||
});
|
||||
|
||||
test('testContainerSingleton', () {
|
||||
var container1 = Container.getInstance();
|
||||
var container2 = Container.getInstance();
|
||||
expect(container1, same(container2));
|
||||
});
|
||||
|
||||
test('testClosureResolution', () {
|
||||
container.bind('name', (Container c) => 'Taylor');
|
||||
expect(container.make('name'), equals('Taylor'));
|
||||
});
|
||||
|
||||
test('testBindIfDoesntRegisterIfServiceAlreadyRegistered', () {
|
||||
container.bind('name', (Container c) => 'Taylor');
|
||||
container.bindIf('name', (Container c) => 'Dayle');
|
||||
|
||||
expect(container.make('name'), equals('Taylor'));
|
||||
});
|
||||
|
||||
test('testBindIfDoesRegisterIfServiceNotRegisteredYet', () {
|
||||
container.bind('surname', (Container c) => 'Taylor');
|
||||
container.bindIf('name', (Container c) => 'Dayle');
|
||||
|
||||
expect(container.make('name'), equals('Dayle'));
|
||||
});
|
||||
|
||||
test('testSingletonIfDoesntRegisterIfBindingAlreadyRegistered', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('class');
|
||||
container.singletonIf('class', (Container c) => ContainerConcreteStub());
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testSingletonIfDoesRegisterIfBindingNotRegisteredYet', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
container.singletonIf(
|
||||
'otherClass', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('otherClass');
|
||||
var secondInstantiation = container.make('otherClass');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testSharedClosureResolution', () {
|
||||
container.singleton('class', (Container c) => ContainerConcreteStub());
|
||||
var firstInstantiation = container.make('class');
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testAutoConcreteResolution', () {
|
||||
var instance = container.make('ContainerConcreteStub');
|
||||
expect(instance, isA<ContainerConcreteStub>());
|
||||
});
|
||||
|
||||
test('testSharedConcreteResolution', () {
|
||||
container.singleton(
|
||||
'ContainerConcreteStub', (Container c) => ContainerConcreteStub());
|
||||
|
||||
var var1 = container.make('ContainerConcreteStub');
|
||||
var var2 = container.make('ContainerConcreteStub');
|
||||
expect(var1, same(var2));
|
||||
});
|
||||
|
||||
test('testAbstractToConcreteResolution', () {
|
||||
container.bind('IContainerContractStub',
|
||||
(Container c) => ContainerImplementationStub());
|
||||
var instance = container.make('ContainerDependentStub');
|
||||
expect(instance.impl, isA<ContainerImplementationStub>());
|
||||
});
|
||||
|
||||
test('testNestedDependencyResolution', () {
|
||||
container.bind('IContainerContractStub',
|
||||
(Container c) => ContainerImplementationStub());
|
||||
var instance = container.make('ContainerNestedDependentStub');
|
||||
expect(instance.inner, isA<ContainerDependentStub>());
|
||||
expect(instance.inner.impl, isA<ContainerImplementationStub>());
|
||||
});
|
||||
|
||||
test('testContainerIsPassedToResolvers', () {
|
||||
container.bind('something', (Container c) => c);
|
||||
var c = container.make('something');
|
||||
expect(c, same(container));
|
||||
});
|
||||
|
||||
test('testArrayAccess', () {
|
||||
container['something'] = (Container c) => 'foo';
|
||||
expect(container['something'], equals('foo'));
|
||||
});
|
||||
|
||||
test('testAliases', () {
|
||||
container['foo'] = 'bar';
|
||||
container.alias('foo', 'baz');
|
||||
container.alias('baz', 'bat');
|
||||
expect(container.make('foo'), equals('bar'));
|
||||
expect(container.make('baz'), equals('bar'));
|
||||
expect(container.make('bat'), equals('bar'));
|
||||
});
|
||||
|
||||
test('testBindingsCanBeOverridden', () {
|
||||
container['foo'] = 'bar';
|
||||
container['foo'] = 'baz';
|
||||
expect(container['foo'], equals('baz'));
|
||||
});
|
||||
|
||||
test('testResolutionOfDefaultParameters', () {
|
||||
container.bind('foo', (Container c) => 'bar');
|
||||
container.bind(
|
||||
'ContainerDefaultValueStub',
|
||||
(Container c) => ContainerDefaultValueStub(
|
||||
c.make('ContainerConcreteStub'), c.make('foo')));
|
||||
var result = container.make('ContainerDefaultValueStub');
|
||||
expect(result.stub, isA<ContainerConcreteStub>());
|
||||
expect(result.defaultValue, equals('bar'));
|
||||
});
|
||||
|
||||
test('testUnsetRemoveBoundInstances', () {
|
||||
container.instance('obj', Object());
|
||||
expect(container.bound('obj'), isTrue);
|
||||
container.forgetInstance('obj');
|
||||
expect(container.bound('obj'), isFalse);
|
||||
});
|
||||
|
||||
test('testExtendMethod', () {
|
||||
container.singleton('foo', (Container c) => 'foo');
|
||||
container.extend(
|
||||
'foo', (String original, Container c) => '$original bar');
|
||||
expect(container.make('foo'), equals('foo bar'));
|
||||
});
|
||||
|
||||
test('testFactoryMethod', () {
|
||||
container.bind('foo', (Container c) => 'foo');
|
||||
var factory = container.factory('foo');
|
||||
expect(factory(), equals('foo'));
|
||||
});
|
||||
|
||||
test('testTaggedBindings', () {
|
||||
container.tag(['foo', 'bar'], 'foobar');
|
||||
container.bind('foo', (Container c) => 'foo');
|
||||
container.bind('bar', (Container c) => 'bar');
|
||||
var tagged = container.tagged('foobar');
|
||||
expect(tagged, containsAll(['foo', 'bar']));
|
||||
});
|
||||
|
||||
test('testCircularDependencies', () {
|
||||
container.bind('circular1', (Container c) => c.make('circular2'));
|
||||
container.bind('circular2', (Container c) => c.make('circular1'));
|
||||
expect(() => container.make('circular1'),
|
||||
throwsA(isA<CircularDependencyException>()));
|
||||
});
|
||||
|
||||
test('testScopedClosureResolution', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.make('class');
|
||||
var secondInstantiation = container.make('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
|
||||
test('testScopedClosureResets', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.makeScoped('class');
|
||||
container.forgetScopedInstances();
|
||||
var secondInstantiation = container.makeScoped('class');
|
||||
expect(firstInstantiation, isNot(same(secondInstantiation)));
|
||||
});
|
||||
|
||||
test('testScopedClosureResolution', () {
|
||||
container.scoped('class', (Container c) => Object());
|
||||
var firstInstantiation = container.makeScoped('class');
|
||||
var secondInstantiation = container.makeScoped('class');
|
||||
expect(firstInstantiation, same(secondInstantiation));
|
||||
});
|
||||
test('testForgetInstanceForgetsInstance', () {
|
||||
var containerConcreteStub = ContainerConcreteStub();
|
||||
container.instance('ContainerConcreteStub', containerConcreteStub);
|
||||
expect(container.isShared('ContainerConcreteStub'), isTrue);
|
||||
container.forgetInstance('ContainerConcreteStub');
|
||||
expect(container.isShared('ContainerConcreteStub'), isFalse);
|
||||
});
|
||||
|
||||
test('testForgetInstancesForgetsAllInstances', () {
|
||||
var stub1 = ContainerConcreteStub();
|
||||
var stub2 = ContainerConcreteStub();
|
||||
var stub3 = ContainerConcreteStub();
|
||||
container.instance('Instance1', stub1);
|
||||
container.instance('Instance2', stub2);
|
||||
container.instance('Instance3', stub3);
|
||||
expect(container.isShared('Instance1'), isTrue);
|
||||
expect(container.isShared('Instance2'), isTrue);
|
||||
expect(container.isShared('Instance3'), isTrue);
|
||||
container.forgetInstances();
|
||||
expect(container.isShared('Instance1'), isFalse);
|
||||
expect(container.isShared('Instance2'), isFalse);
|
||||
expect(container.isShared('Instance3'), isFalse);
|
||||
});
|
||||
|
||||
test('testContainerFlushFlushesAllBindingsAliasesAndResolvedInstances', () {
|
||||
container.bind('ConcreteStub', (Container c) => ContainerConcreteStub(),
|
||||
shared: true);
|
||||
container.alias('ConcreteStub', 'ContainerConcreteStub');
|
||||
container.make('ConcreteStub');
|
||||
expect(container.resolved('ConcreteStub'), isTrue);
|
||||
expect(container.isAlias('ContainerConcreteStub'), isTrue);
|
||||
expect(container.getBindings().containsKey('ConcreteStub'), isTrue);
|
||||
expect(container.isShared('ConcreteStub'), isTrue);
|
||||
container.flush();
|
||||
expect(container.resolved('ConcreteStub'), isFalse);
|
||||
expect(container.isAlias('ContainerConcreteStub'), isFalse);
|
||||
expect(container.getBindings().isEmpty, isTrue);
|
||||
expect(container.isShared('ConcreteStub'), isFalse);
|
||||
});
|
||||
|
||||
test('testResolvedResolvesAliasToBindingNameBeforeChecking', () {
|
||||
container.bind('ConcreteStub', (Container c) => ContainerConcreteStub(),
|
||||
shared: true);
|
||||
container.alias('ConcreteStub', 'foo');
|
||||
|
||||
expect(container.resolved('ConcreteStub'), isFalse);
|
||||
expect(container.resolved('foo'), isFalse);
|
||||
|
||||
container.make('ConcreteStub');
|
||||
|
||||
expect(container.resolved('ConcreteStub'), isTrue);
|
||||
expect(container.resolved('foo'), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerDefaultValueStub {
|
||||
final ContainerConcreteStub stub;
|
||||
final String defaultValue;
|
||||
|
||||
ContainerDefaultValueStub(this.stub, [this.defaultValue = 'taylor']);
|
||||
}
|
||||
|
||||
class ContainerNestedDependentStub {
|
||||
final ContainerDependentStub inner;
|
||||
|
||||
ContainerNestedDependentStub(this.inner);
|
||||
}
|
||||
|
||||
// Helper function to set up bindings
|
||||
void setUpBindings(Container container) {
|
||||
container.bind(
|
||||
'ContainerConcreteStub', (Container c) => ContainerConcreteStub());
|
||||
container.bind(
|
||||
'ContainerDependentStub',
|
||||
(Container c) =>
|
||||
ContainerDependentStub(c.make('IContainerContractStub')));
|
||||
container.bind(
|
||||
'ContainerNestedDependentStub',
|
||||
(Container c) =>
|
||||
ContainerNestedDependentStub(c.make('ContainerDependentStub')));
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('ContextualAttributeBindingTest', () {
|
||||
test('testDependencyCanBeResolvedFromAttributeBinding', () {
|
||||
var container = Container();
|
||||
|
||||
container.bind('ContainerTestContract', (c) => ContainerTestImplB());
|
||||
container.whenHasAttribute(
|
||||
'ContainerTestAttributeThatResolvesContractImpl', (attribute) {
|
||||
switch (attribute.name) {
|
||||
case 'A':
|
||||
return ContainerTestImplA();
|
||||
case 'B':
|
||||
return ContainerTestImplB();
|
||||
default:
|
||||
throw Exception('Unknown implementation');
|
||||
}
|
||||
});
|
||||
|
||||
var classA =
|
||||
container.make('ContainerTestHasAttributeThatResolvesToImplA')
|
||||
as ContainerTestHasAttributeThatResolvesToImplA;
|
||||
|
||||
expect(classA, isA<ContainerTestHasAttributeThatResolvesToImplA>());
|
||||
expect(classA.property, isA<ContainerTestImplA>());
|
||||
|
||||
var classB =
|
||||
container.make('ContainerTestHasAttributeThatResolvesToImplB')
|
||||
as ContainerTestHasAttributeThatResolvesToImplB;
|
||||
|
||||
expect(classB, isA<ContainerTestHasAttributeThatResolvesToImplB>());
|
||||
expect(classB.property, isA<ContainerTestImplB>());
|
||||
});
|
||||
|
||||
test('testScalarDependencyCanBeResolvedFromAttributeBinding', () {
|
||||
var container = Container();
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'app': {
|
||||
'timezone': 'Europe/Paris',
|
||||
},
|
||||
}));
|
||||
|
||||
container.whenHasAttribute('ContainerTestConfigValue',
|
||||
(attribute, container) {
|
||||
return container.make('config').get(attribute.key);
|
||||
});
|
||||
|
||||
var instance = container.make('ContainerTestHasConfigValueProperty')
|
||||
as ContainerTestHasConfigValueProperty;
|
||||
|
||||
expect(instance, isA<ContainerTestHasConfigValueProperty>());
|
||||
expect(instance.timezone, equals('Europe/Paris'));
|
||||
});
|
||||
|
||||
test('testScalarDependencyCanBeResolvedFromAttributeResolveMethod', () {
|
||||
var container = Container();
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'app': {
|
||||
'env': 'production',
|
||||
},
|
||||
}));
|
||||
|
||||
var instance =
|
||||
container.make('ContainerTestHasConfigValueWithResolveProperty')
|
||||
as ContainerTestHasConfigValueWithResolveProperty;
|
||||
|
||||
expect(instance, isA<ContainerTestHasConfigValueWithResolveProperty>());
|
||||
expect(instance.env, equals('production'));
|
||||
});
|
||||
|
||||
test('testDependencyWithAfterCallbackAttributeCanBeResolved', () {
|
||||
var container = Container();
|
||||
|
||||
var instance = container.make(
|
||||
'ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback')
|
||||
as ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback;
|
||||
|
||||
expect(instance.person['role'], equals('Developer'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ContainerTestAttributeThatResolvesContractImpl {
|
||||
final String name;
|
||||
const ContainerTestAttributeThatResolvesContractImpl(this.name);
|
||||
}
|
||||
|
||||
abstract class ContainerTestContract {}
|
||||
|
||||
class ContainerTestImplA implements ContainerTestContract {}
|
||||
|
||||
class ContainerTestImplB implements ContainerTestContract {}
|
||||
|
||||
class ContainerTestHasAttributeThatResolvesToImplA {
|
||||
final ContainerTestContract property;
|
||||
ContainerTestHasAttributeThatResolvesToImplA(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestHasAttributeThatResolvesToImplB {
|
||||
final ContainerTestContract property;
|
||||
ContainerTestHasAttributeThatResolvesToImplB(this.property);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValue {
|
||||
final String key;
|
||||
const ContainerTestConfigValue(this.key);
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueProperty {
|
||||
final String timezone;
|
||||
ContainerTestHasConfigValueProperty(this.timezone);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValueWithResolve {
|
||||
final String key;
|
||||
const ContainerTestConfigValueWithResolve(this.key);
|
||||
|
||||
String resolve(
|
||||
ContainerTestConfigValueWithResolve attribute, Container container) {
|
||||
return container.make('config').get(attribute.key);
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueWithResolveProperty {
|
||||
final String env;
|
||||
ContainerTestHasConfigValueWithResolveProperty(this.env);
|
||||
}
|
||||
|
||||
class ContainerTestConfigValueWithResolveAndAfter {
|
||||
const ContainerTestConfigValueWithResolveAndAfter();
|
||||
|
||||
Object resolve(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||
Container container) {
|
||||
return {'name': 'Taylor'};
|
||||
}
|
||||
|
||||
void after(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||
Object value, Container container) {
|
||||
(value as Map)['role'] = 'Developer';
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback {
|
||||
final Map person;
|
||||
ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback(this.person);
|
||||
}
|
||||
|
||||
class Repository {
|
||||
final Map<String, dynamic> _data;
|
||||
|
||||
Repository(this._data);
|
||||
|
||||
dynamic get(String key) {
|
||||
var keys = key.split('.');
|
||||
var value = _data;
|
||||
for (var k in keys) {
|
||||
if (value is Map && value.containsKey(k)) {
|
||||
value = value[k];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
import 'package:platform_config/platform_config.dart';
|
||||
|
||||
void main() {
|
||||
group('ContextualBindingTest', () {
|
||||
test('testContainerCanInjectDifferentImplementationsDependingOnContext',
|
||||
() {
|
||||
var container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStub');
|
||||
container
|
||||
.when('ContainerTestContextInjectTwo')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStubTwo');
|
||||
|
||||
var one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
var two = container.make('ContainerTestContextInjectTwo')
|
||||
as ContainerTestContextInjectTwo;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
|
||||
// Test With Closures
|
||||
container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStub');
|
||||
container
|
||||
.when('ContainerTestContextInjectTwo')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give((Container container) {
|
||||
return container.make('ContainerContextImplementationStubTwo');
|
||||
});
|
||||
|
||||
one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
two = container.make('ContainerTestContextInjectTwo')
|
||||
as ContainerTestContextInjectTwo;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
|
||||
// Test nesting to make the same 'abstract' in different context
|
||||
container = Container();
|
||||
|
||||
container.bind('IContainerContextContractStub',
|
||||
(c) => ContainerContextImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give((Container container) {
|
||||
return container.make('IContainerContextContractStub');
|
||||
});
|
||||
|
||||
one = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
|
||||
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||
});
|
||||
|
||||
test('testContextualBindingWorksForExistingInstancedBindings', () {
|
||||
var container = Container();
|
||||
|
||||
container.instance(
|
||||
'IContainerContextContractStub', ContainerImplementationStub());
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectOne')
|
||||
.needs('IContainerContextContractStub')
|
||||
.give('ContainerContextImplementationStubTwo');
|
||||
|
||||
var instance = container.make('ContainerTestContextInjectOne')
|
||||
as ContainerTestContextInjectOne;
|
||||
expect(instance.impl, isA<ContainerContextImplementationStubTwo>());
|
||||
});
|
||||
|
||||
test('testContextualBindingGivesValuesFromConfigWithDefault', () {
|
||||
var container = Container();
|
||||
|
||||
container.singleton(
|
||||
'config',
|
||||
(c) => Repository({
|
||||
'test': {
|
||||
'password': 'hunter42',
|
||||
},
|
||||
}));
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
.needs('\$username')
|
||||
.giveConfig('test.username', 'DEFAULT_USERNAME');
|
||||
|
||||
container
|
||||
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
.needs('\$password')
|
||||
.giveConfig('test.password');
|
||||
|
||||
var resolvedInstance =
|
||||
container.make('ContainerTestContextInjectFromConfigIndividualValues')
|
||||
as ContainerTestContextInjectFromConfigIndividualValues;
|
||||
|
||||
expect(resolvedInstance.username, equals('DEFAULT_USERNAME'));
|
||||
expect(resolvedInstance.password, equals('hunter42'));
|
||||
expect(resolvedInstance.alias, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract class IContainerContextContractStub {}
|
||||
|
||||
class ContainerContextNonContractStub {}
|
||||
|
||||
class ContainerContextImplementationStub
|
||||
implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerContextImplementationStubTwo
|
||||
implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerImplementationStub implements IContainerContextContractStub {}
|
||||
|
||||
class ContainerTestContextInjectInstantiations
|
||||
implements IContainerContextContractStub {
|
||||
static int instantiations = 0;
|
||||
|
||||
ContainerTestContextInjectInstantiations() {
|
||||
instantiations++;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectOne {
|
||||
final IContainerContextContractStub impl;
|
||||
|
||||
ContainerTestContextInjectOne(this.impl);
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectTwo {
|
||||
final IContainerContextContractStub impl;
|
||||
|
||||
ContainerTestContextInjectTwo(this.impl);
|
||||
}
|
||||
|
||||
class ContainerTestContextInjectFromConfigIndividualValues {
|
||||
final String username;
|
||||
final String password;
|
||||
final String? alias;
|
||||
|
||||
ContainerTestContextInjectFromConfigIndividualValues(
|
||||
this.username, this.password,
|
||||
[this.alias]);
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('ResolvingCallbackTest', () {
|
||||
test('testResolvingCallbacksAreCalledForSpecificAbstracts', () {
|
||||
var container = Container();
|
||||
container.resolving('foo', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalled', () {
|
||||
var container = Container();
|
||||
container.resolving((object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledForType', () {
|
||||
var container = Container();
|
||||
container.resolving('Object', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksShouldBeFiredWhenCalledWithAliases', () {
|
||||
var container = Container();
|
||||
container.alias('Object', 'std');
|
||||
container.resolving('std', (object) {
|
||||
(object as dynamic).name = 'taylor';
|
||||
return object;
|
||||
});
|
||||
container.bind('foo', (c) => Object());
|
||||
var instance = container.make('foo');
|
||||
|
||||
expect((instance as dynamic).name, 'taylor');
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 2);
|
||||
});
|
||||
|
||||
test('testGlobalResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving((_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingContractStub');
|
||||
expect(callCounter, 2);
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksAreCalledOnceForSingletonConcretes', () {
|
||||
var container = Container();
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
container.bind(
|
||||
'ResolvingImplementationStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 2);
|
||||
|
||||
container.make('ResolvingContractStub');
|
||||
expect(callCounter, 3);
|
||||
});
|
||||
|
||||
test('testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution', () {
|
||||
var container = Container();
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
|
||||
var callCounter = 0;
|
||||
container.resolving('ResolvingContractStub', (_, __) {
|
||||
callCounter++;
|
||||
});
|
||||
|
||||
container.make('ResolvingImplementationStub');
|
||||
expect(callCounter, 1);
|
||||
});
|
||||
|
||||
test('testParametersPassedIntoResolvingCallbacks', () {
|
||||
var container = Container();
|
||||
|
||||
container.resolving('ResolvingContractStub', (obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.afterResolving('ResolvingContractStub', (obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.afterResolving((obj, app) {
|
||||
expect(obj, isA<ResolvingContractStub>());
|
||||
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||
expect(app, same(container));
|
||||
});
|
||||
|
||||
container.bind(
|
||||
'ResolvingContractStub', (c) => ResolvingImplementationStubTwo());
|
||||
container.make('ResolvingContractStub');
|
||||
});
|
||||
|
||||
// Add all remaining tests here...
|
||||
});
|
||||
}
|
||||
|
||||
abstract class ResolvingContractStub {}
|
||||
|
||||
class ResolvingImplementationStub implements ResolvingContractStub {}
|
||||
|
||||
class ResolvingImplementationStubTwo implements ResolvingContractStub {}
|
|
@ -1,37 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('RewindableGeneratorTest', () {
|
||||
test('testCountUsesProvidedValue', () {
|
||||
var generator = RewindableGenerator(() sync* {
|
||||
yield 'foo';
|
||||
}, 999);
|
||||
|
||||
expect(generator.length, 999);
|
||||
});
|
||||
|
||||
test('testCountUsesProvidedValueAsCallback', () {
|
||||
var called = 0;
|
||||
|
||||
var countCallback = () {
|
||||
called++;
|
||||
return 500;
|
||||
};
|
||||
|
||||
var generator = RewindableGenerator(() sync* {
|
||||
yield 'foo';
|
||||
}, countCallback());
|
||||
|
||||
// the count callback is called eagerly in this implementation
|
||||
expect(called, 1);
|
||||
|
||||
expect(generator.length, 500);
|
||||
|
||||
generator.length;
|
||||
|
||||
// the count callback is called only once
|
||||
expect(called, 1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_service_container/service_container.dart';
|
||||
|
||||
void main() {
|
||||
group('UtilTest', () {
|
||||
test('testUnwrapIfClosure', () {
|
||||
expect(Util.unwrapIfClosure('foo'), 'foo');
|
||||
expect(Util.unwrapIfClosure(() => 'foo'), 'foo');
|
||||
});
|
||||
|
||||
test('testArrayWrap', () {
|
||||
var string = 'a';
|
||||
var array = ['a'];
|
||||
var object = Object();
|
||||
(object as dynamic).value = 'a';
|
||||
|
||||
expect(Util.arrayWrap(string), ['a']);
|
||||
expect(Util.arrayWrap(array), array);
|
||||
expect(Util.arrayWrap(object), [object]);
|
||||
expect(Util.arrayWrap(null), []);
|
||||
expect(Util.arrayWrap([null]), [null]);
|
||||
expect(Util.arrayWrap([null, null]), [null, null]);
|
||||
expect(Util.arrayWrap(''), ['']);
|
||||
expect(Util.arrayWrap(['']), ['']);
|
||||
expect(Util.arrayWrap(false), [false]);
|
||||
expect(Util.arrayWrap([false]), [false]);
|
||||
expect(Util.arrayWrap(0), [0]);
|
||||
|
||||
var obj = Object();
|
||||
(obj as dynamic).value = 'a';
|
||||
var wrappedObj = Util.arrayWrap(obj);
|
||||
expect(wrappedObj, [obj]);
|
||||
expect(identical(wrappedObj[0], obj), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,45 +1,223 @@
|
|||
# Protevus Container
|
||||
# Platform Container
|
||||
|
||||

|
||||
[](https://dart.dev/null-safety)
|
||||
[](https://gitter.im/angel_dart/discussion)
|
||||
[](https://github.com/dart-backend/angel/tree/master/packages/container/angel_container/LICENSE)
|
||||
|
||||
A better IoC container for Protevus, ultimately allowing Protevus to be used with or without `dart:mirrors` package.
|
||||
A powerful IoC (Inversion of Control) container for Dart, providing robust dependency injection with support for multiple reflection strategies. The container can be used with or without `dart:mirrors`, making it suitable for all Dart platforms including web and Flutter.
|
||||
|
||||
## Features
|
||||
|
||||
- **Constructor Injection**: Automatically resolves and injects constructor dependencies
|
||||
- **Contextual Binding**: Define how abstractions should be resolved in different contexts
|
||||
- **Multiple Registration Types**: Support for singletons, factories, and scoped instances
|
||||
- **Attribute/Annotation Support**: Use annotations to configure injection behavior
|
||||
- **Flexible Reflection**: Choose between mirrors-based, static, or custom reflection
|
||||
- **Type-Safe**: Leverages Dart's type system for reliable dependency resolution
|
||||
- **Tagging System**: Group and resolve related dependencies
|
||||
- **Parameter Overrides**: Override specific dependencies during resolution
|
||||
- **Method Binding**: Inject dependencies into method parameters
|
||||
|
||||
## Installation
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
platform_container: ^latest_version
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Constructor Injection
|
||||
|
||||
```dart
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
// Define some services
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
@Expose('/sales', middleware: [process1])
|
||||
class SalesController extends Controller {
|
||||
@Expose('/', middleware: [process2])
|
||||
Future<String> route1(RequestContext req, ResponseContext res) async {
|
||||
return "Sales route";
|
||||
}
|
||||
}
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print(message);
|
||||
}
|
||||
|
||||
bool process1(RequestContext req, ResponseContext res) {
|
||||
res.write('Hello, ');
|
||||
return true;
|
||||
}
|
||||
class UserService {
|
||||
final Logger logger;
|
||||
|
||||
// Constructor injection - container will automatically inject Logger
|
||||
UserService(this.logger);
|
||||
|
||||
void createUser(String name) {
|
||||
logger.log('Creating user: $name');
|
||||
}
|
||||
}
|
||||
|
||||
bool process2(RequestContext req, ResponseContext res) {
|
||||
res.write('From Sales, ');
|
||||
return true;
|
||||
}
|
||||
// Setup the container
|
||||
var container = Container(MirrorsReflector());
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
|
||||
void main() async {
|
||||
// Using Mirror Reflector
|
||||
var app = Protevus(reflector: MirrorsReflector());
|
||||
|
||||
// Sales Controller
|
||||
app.container.registerSingleton<SalesController>(SalesController());
|
||||
await app.mountController<SalesController>();
|
||||
|
||||
var http = PlatformHttp(app);
|
||||
var server = await http.startServer('localhost', 3000);
|
||||
print("Protevus server listening at ${http.uri}");
|
||||
}
|
||||
// Resolve with automatic dependency injection
|
||||
var userService = container.make<UserService>();
|
||||
```
|
||||
|
||||
### Singleton Registration
|
||||
|
||||
```dart
|
||||
// Register a singleton
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
// Or register a lazy singleton
|
||||
container.registerLazySingleton<Logger>((container) => ConsoleLogger());
|
||||
```
|
||||
|
||||
### Factory Registration
|
||||
|
||||
```dart
|
||||
// Register a factory
|
||||
container.registerFactory<Logger>((container) => ConsoleLogger());
|
||||
|
||||
// Or use the shorter syntax
|
||||
container[Logger] = (container) => ConsoleLogger();
|
||||
```
|
||||
|
||||
### Contextual Binding
|
||||
|
||||
```dart
|
||||
// Bind different implementations based on context
|
||||
container.when(UserService).needs(Logger).give(ConsoleLogger);
|
||||
container.when(AdminService).needs(Logger).give(FileLogger);
|
||||
```
|
||||
|
||||
### Attribute/Annotation Based Injection
|
||||
|
||||
```dart
|
||||
@injectable
|
||||
class UserRepository {
|
||||
@inject
|
||||
final Database db;
|
||||
|
||||
UserRepository(this.db);
|
||||
}
|
||||
```
|
||||
|
||||
### Scoped Instances
|
||||
|
||||
```dart
|
||||
// Register a scoped instance
|
||||
container.scoped<RequestContext>((c) => RequestContext());
|
||||
|
||||
// Clear scoped instances
|
||||
container.clearScoped();
|
||||
```
|
||||
|
||||
### Tagged Dependencies
|
||||
|
||||
```dart
|
||||
// Tag related services
|
||||
container.tag([UserService, OrderService], 'business-logic');
|
||||
|
||||
// Resolve all tagged services
|
||||
var services = container.tagged('business-logic');
|
||||
```
|
||||
|
||||
## Reflection Strategies
|
||||
|
||||
### Mirrors Reflection (Full Runtime Reflection)
|
||||
|
||||
```dart
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
|
||||
var container = Container(MirrorsReflector());
|
||||
```
|
||||
|
||||
### Static Reflection (AOT-Friendly)
|
||||
|
||||
```dart
|
||||
import 'package:platform_container/static.dart';
|
||||
|
||||
var container = Container(StaticReflector());
|
||||
```
|
||||
|
||||
### Empty Reflection (Minimal)
|
||||
|
||||
```dart
|
||||
import 'package:platform_container/empty.dart';
|
||||
|
||||
var container = Container(EmptyReflector());
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Parameter Overrides
|
||||
|
||||
```dart
|
||||
// Override specific parameters during resolution
|
||||
var instance = container.makeWith<Service>({
|
||||
'config': CustomConfig(),
|
||||
'timeout': Duration(seconds: 30),
|
||||
});
|
||||
```
|
||||
|
||||
### Method Binding
|
||||
|
||||
```dart
|
||||
// Bind a method with injected parameters
|
||||
container.bindMethod('processUser', (Logger logger, User user) {
|
||||
logger.log('Processing user: ${user.name}');
|
||||
});
|
||||
|
||||
// Call the method
|
||||
container.callMethod('processUser', [user]);
|
||||
```
|
||||
|
||||
### Child Containers
|
||||
|
||||
```dart
|
||||
// Create a child container with its own bindings
|
||||
var child = container.createChild();
|
||||
child.bind(Logger).to(SpecialLogger);
|
||||
```
|
||||
|
||||
## Web Framework Integration
|
||||
|
||||
The container is used as the core DI system in the Protevus web framework:
|
||||
|
||||
```dart
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
|
||||
@Expose('/api')
|
||||
class ApiController extends Controller {
|
||||
final UserService userService;
|
||||
|
||||
// Constructor injection works automatically
|
||||
ApiController(this.userService);
|
||||
|
||||
@Expose('/users')
|
||||
Future<List<User>> getUsers() async {
|
||||
return userService.getAllUsers();
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var app = Protevus(reflector: MirrorsReflector());
|
||||
|
||||
// Register your services
|
||||
app.container.registerSingleton<UserService>(UserService());
|
||||
|
||||
// Mount the controller
|
||||
await app.mountController<ApiController>();
|
||||
|
||||
var http = PlatformHttp(app);
|
||||
var server = await http.startServer('localhost', 3000);
|
||||
print('Server listening at ${http.uri}');
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read our [contributing guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
|
27
packages/container/container/doc/roadmap.md
Normal file
27
packages/container/container/doc/roadmap.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
Let me summarize what we should add to match Laravel's functionality:
|
||||
|
||||
Core Container Enhancements:
|
||||
- Add alias management (alias(), getAlias(), isAlias())
|
||||
- Add service extenders (extend())
|
||||
- Add rebound callbacks (rebinding(), refresh())
|
||||
- Add parameter override stack
|
||||
- Implement ArrayAccess equivalent
|
||||
Method Binding Improvements:
|
||||
- Support Class@method syntax
|
||||
- Add parameter dependency injection
|
||||
- Add variadic parameter support
|
||||
- Add __invoke support
|
||||
Contextual Binding Enhancements:
|
||||
- Support array of concrete types
|
||||
- Add giveTagged() and giveConfig()
|
||||
- Add attribute-based binding
|
||||
Additional Methods:
|
||||
- Add bindIf() and singletonIf()
|
||||
- Add wrap() for closure injection
|
||||
- Add factory() for deferred resolution
|
||||
- Add makeWith() alias
|
||||
- Add flush() for container reset
|
||||
Error Handling:
|
||||
- Better circular dependency detection
|
||||
- More specific exception types
|
||||
- Better error messages with build stack
|
|
@ -7,9 +7,9 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
library platform_container;
|
||||
|
||||
export 'src/attributes.dart';
|
||||
export 'src/container.dart';
|
||||
export 'src/contextual_binding_builder.dart';
|
||||
export 'src/empty/empty.dart';
|
||||
export 'src/static/static.dart';
|
||||
export 'src/exception.dart';
|
||||
|
|
106
packages/container/container/lib/src/attribute_binding.dart
Normal file
106
packages/container/container/lib/src/attribute_binding.dart
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'attributes.dart';
|
||||
import 'container.dart';
|
||||
import 'reflector.dart';
|
||||
|
||||
/// Extension methods for attribute-based binding support
|
||||
extension AttributeBindingExtension on Container {
|
||||
/// Register all attribute-based bindings for a type
|
||||
void registerAttributeBindings(Type type) {
|
||||
var annotations = reflector.getAnnotations(type);
|
||||
for (var annotation in annotations) {
|
||||
var value = annotation.reflectee;
|
||||
if (value is Injectable) {
|
||||
// Register the binding
|
||||
if (value.bindTo != null) {
|
||||
bind(value.bindTo!).to(type);
|
||||
}
|
||||
|
||||
// Apply tags
|
||||
if (value.tags.isNotEmpty) {
|
||||
tag([type], value.tags.join(','));
|
||||
}
|
||||
|
||||
// Make it a singleton if requested
|
||||
if (value.singleton) {
|
||||
singleton(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve constructor parameters using attribute-based injection
|
||||
List<dynamic> resolveConstructorParameters(
|
||||
Type type, String constructorName, List<ReflectedParameter> parameters) {
|
||||
var result = <dynamic>[];
|
||||
|
||||
for (var param in parameters) {
|
||||
var annotations =
|
||||
reflector.getParameterAnnotations(type, constructorName, param.name);
|
||||
|
||||
// Find injection annotation
|
||||
ReflectedInstance? injectAnnotation;
|
||||
try {
|
||||
injectAnnotation = annotations.firstWhere(
|
||||
(a) => a.reflectee is Inject || a.reflectee is InjectTagged);
|
||||
} catch (_) {
|
||||
try {
|
||||
injectAnnotation =
|
||||
annotations.firstWhere((a) => a.reflectee is InjectAll);
|
||||
} catch (_) {
|
||||
// No injection annotation found
|
||||
}
|
||||
}
|
||||
|
||||
if (injectAnnotation != null) {
|
||||
var value = injectAnnotation.reflectee;
|
||||
if (value is Inject) {
|
||||
// Inject specific implementation with config
|
||||
result.add(
|
||||
withParameters(value.config, () => make(value.implementation)));
|
||||
} else if (value is InjectTagged) {
|
||||
// Inject tagged implementation
|
||||
var tagged = this.tagged(value.tag);
|
||||
if (tagged.isEmpty) {
|
||||
throw Exception('No implementations found for tag: ${value.tag}');
|
||||
}
|
||||
result.add(tagged.first);
|
||||
} else if (value is InjectAll) {
|
||||
// Inject all implementations
|
||||
if (value.tag != null) {
|
||||
result.add(tagged(value.tag!).toList());
|
||||
} else {
|
||||
result.add(makeAll(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No injection annotation, use default resolution
|
||||
result.add(make(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Make all instances of a type
|
||||
List<dynamic> makeAll(Type type) {
|
||||
var reflectedType = reflector.reflectType(type);
|
||||
if (reflectedType == null) {
|
||||
throw Exception('Type not found: $type');
|
||||
}
|
||||
|
||||
return reflector
|
||||
.getAnnotations(type)
|
||||
.where((a) => a.reflectee is Injectable)
|
||||
.map((a) => make(type))
|
||||
.toList();
|
||||
}
|
||||
}
|
58
packages/container/container/lib/src/attributes.dart
Normal file
58
packages/container/container/lib/src/attributes.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// Base class for all container binding attributes
|
||||
abstract class BindingAttribute {
|
||||
const BindingAttribute();
|
||||
}
|
||||
|
||||
/// Marks a class as injectable and optionally specifies how it should be bound
|
||||
class Injectable extends BindingAttribute {
|
||||
/// The type to bind this implementation to (usually an interface)
|
||||
final Type? bindTo;
|
||||
|
||||
/// Whether this should be bound as a singleton
|
||||
final bool singleton;
|
||||
|
||||
/// Tags that can be used to identify this implementation
|
||||
final List<String> tags;
|
||||
|
||||
const Injectable({
|
||||
this.bindTo,
|
||||
this.singleton = false,
|
||||
this.tags = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring a specific implementation
|
||||
class Inject extends BindingAttribute {
|
||||
/// The implementation type to inject
|
||||
final Type implementation;
|
||||
|
||||
/// Configuration parameters for the implementation
|
||||
final Map<String, dynamic> config;
|
||||
|
||||
const Inject(this.implementation, {this.config = const {}});
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring a tagged implementation
|
||||
class InjectTagged extends BindingAttribute {
|
||||
/// The tag to use when resolving the implementation
|
||||
final String tag;
|
||||
|
||||
const InjectTagged(this.tag);
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring all implementations of a type
|
||||
class InjectAll extends BindingAttribute {
|
||||
/// Optional tag to filter implementations
|
||||
final String? tag;
|
||||
|
||||
const InjectAll({this.tag});
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'container.dart';
|
||||
import 'exception.dart';
|
||||
import 'reflector.dart';
|
||||
|
||||
/// A builder class for defining contextual bindings in the container.
|
||||
///
|
||||
/// This class provides a fluent interface for defining how abstract types should
|
||||
/// be resolved in specific contexts. It allows for different implementations of
|
||||
/// an interface to be used depending on where they are being injected.
|
||||
class ContextualBindingBuilder {
|
||||
/// The container instance this builder is associated with
|
||||
final Container container;
|
||||
|
||||
/// The concrete type that needs a contextual binding
|
||||
final List<Type> concrete;
|
||||
|
||||
/// Creates a new contextual binding builder
|
||||
ContextualBindingBuilder(this.container, this.concrete);
|
||||
|
||||
/// Define the abstract type that should be bound differently in this context
|
||||
ContextualImplementationBuilder needs<T>() {
|
||||
return ContextualImplementationBuilder(container, concrete, T);
|
||||
}
|
||||
|
||||
/// Bind directly to a concrete implementation
|
||||
void to(Type implementation) {
|
||||
for (var abstractType in concrete) {
|
||||
// Add contextual binding with a factory function
|
||||
container.addContextualBinding(abstractType, abstractType, (c) {
|
||||
// Get parameter overrides
|
||||
var overrides = c.getParameterOverride('name');
|
||||
if (overrides != null) {
|
||||
return c.withParameters(
|
||||
{'name': overrides}, () => c.make(implementation));
|
||||
}
|
||||
return c.make(implementation);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder class for defining the implementation for a contextual binding.
|
||||
///
|
||||
/// This class completes the contextual binding definition by specifying what
|
||||
/// implementation should be used for the abstract type in the given context.
|
||||
class ContextualImplementationBuilder {
|
||||
/// The container instance this builder is associated with
|
||||
final Container container;
|
||||
|
||||
/// The concrete type that needs a contextual binding
|
||||
final List<Type> concrete;
|
||||
|
||||
/// The abstract type that needs to be bound
|
||||
final Type abstract;
|
||||
|
||||
/// Creates a new contextual implementation builder
|
||||
ContextualImplementationBuilder(
|
||||
this.container, this.concrete, this.abstract) {
|
||||
// Register an empty binding by default
|
||||
for (var concreteType in concrete) {
|
||||
container.addContextualBinding(concreteType, abstract, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the implementation that should be used
|
||||
void give<T>() {
|
||||
for (var concreteType in concrete) {
|
||||
container.addContextualBinding(concreteType, abstract, T);
|
||||
}
|
||||
}
|
||||
|
||||
void to(Type implementation) {
|
||||
for (var abstractType in concrete) {
|
||||
// Add contextual binding with a factory function
|
||||
container.addContextualBinding(abstractType, abstractType, (c) {
|
||||
// Get parameter overrides
|
||||
var overrides = c.getParameterOverride('name');
|
||||
if (overrides != null) {
|
||||
// Create instance with named parameters
|
||||
var reflectedType = c.reflector.reflectType(implementation);
|
||||
if (reflectedType is ReflectedClass) {
|
||||
return reflectedType
|
||||
.newInstance('', [], {'name': overrides}, []).reflectee;
|
||||
}
|
||||
}
|
||||
return c.make(implementation);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify a factory function that should be used to create the implementation
|
||||
void giveFactory(dynamic Function(Container container) factory) {
|
||||
for (var concreteType in concrete) {
|
||||
container.addContextualBinding(concreteType, abstract, factory);
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify that the implementation should be resolved from a tagged binding
|
||||
void giveTagged(String tag) {
|
||||
giveFactory((container) {
|
||||
var tagged = container.tagged(tag);
|
||||
if (tagged.isEmpty) {
|
||||
throw BindingResolutionException(
|
||||
'No implementations found for tag: $tag');
|
||||
}
|
||||
return tagged.first;
|
||||
});
|
||||
}
|
||||
|
||||
/// Specify the implementation type and its configuration
|
||||
void giveConfig(Type implementation, Map<String, dynamic> config) {
|
||||
giveFactory((container) {
|
||||
// Get reflected type to validate required parameters
|
||||
var reflectedType = container.reflector.reflectType(implementation);
|
||||
if (reflectedType is ReflectedClass) {
|
||||
var constructor = reflectedType.constructors.firstWhere(
|
||||
(c) => c.name.isEmpty || c.name == reflectedType.name,
|
||||
orElse: () => reflectedType.constructors.first);
|
||||
|
||||
// Check required parameters
|
||||
for (var param in constructor.parameters) {
|
||||
if (param.isRequired &&
|
||||
param.isNamed &&
|
||||
!config.containsKey(param.name)) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing for ${reflectedType.name}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container.withParameters(config, () {
|
||||
return container.make(implementation);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,28 +7,46 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// A custom exception class for reflection-related errors.
|
||||
///
|
||||
/// This class extends the base [Exception] class and provides a way to
|
||||
/// create exceptions specific to reflection operations. It includes a
|
||||
/// message that describes the nature of the exception.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// throw ReflectionException('Failed to reflect on class XYZ');
|
||||
/// ```
|
||||
class ReflectionException implements Exception {
|
||||
/// Creates a new instance of [ReflectionException] with the specified message.
|
||||
///
|
||||
/// The [message] parameter should describe the nature of the reflection error.
|
||||
/// Base exception class for container-related errors.
|
||||
abstract class ContainerException implements Exception {
|
||||
/// The error message
|
||||
final String message;
|
||||
|
||||
/// Creates a new instance of [ReflectionException] with the specified message.
|
||||
///
|
||||
/// The [message] parameter should describe the nature of the reflection error.
|
||||
ReflectionException(this.message);
|
||||
/// Optional cause of the exception
|
||||
final Object? cause;
|
||||
|
||||
/// Creates a new container exception
|
||||
ContainerException(this.message, [this.cause]);
|
||||
|
||||
// Override the toString method to provide a custom string representation of the exception.
|
||||
@override
|
||||
String toString() => message;
|
||||
String toString() => cause == null ? message : '$message (Caused by: $cause)';
|
||||
}
|
||||
|
||||
/// Exception thrown when reflection operations fail
|
||||
class ReflectionException extends ContainerException {
|
||||
ReflectionException(super.message, [super.cause]);
|
||||
}
|
||||
|
||||
/// Exception thrown when a binding resolution fails
|
||||
class BindingResolutionException extends ContainerException {
|
||||
BindingResolutionException(super.message, [super.cause]);
|
||||
}
|
||||
|
||||
/// Exception thrown when a circular dependency is detected
|
||||
class CircularDependencyException extends ContainerException {
|
||||
CircularDependencyException(super.message, [super.cause]);
|
||||
}
|
||||
|
||||
/// Exception thrown when an entry is not found in the container
|
||||
class EntryNotFoundException extends ContainerException {
|
||||
/// The identifier that was not found
|
||||
final String id;
|
||||
|
||||
EntryNotFoundException(this.id, [Object? cause])
|
||||
: super('No entry was found for identifier "$id"', cause);
|
||||
}
|
||||
|
||||
/// Exception thrown when there are contextual binding issues
|
||||
class ContextualBindingException extends ContainerException {
|
||||
ContextualBindingException(super.message, [super.cause]);
|
||||
}
|
||||
|
|
|
@ -564,8 +564,13 @@ class _ReflectedClassMirror extends ReflectedClass {
|
|||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic>? namedArguments, List<Type>? typeArguments]) {
|
||||
return _ReflectedInstanceMirror(
|
||||
mirror.newInstance(Symbol(constructorName), positionalArguments));
|
||||
try {
|
||||
return _ReflectedInstanceMirror(
|
||||
mirror.newInstance(Symbol(constructorName), positionalArguments));
|
||||
} on dart.AbstractClassInstantiationError {
|
||||
throw BindingResolutionException(
|
||||
'Cannot instantiate abstract class ${mirror.simpleName}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if this [_ReflectedClassMirror] is equal to another object.
|
||||
|
|
|
@ -43,6 +43,41 @@ abstract class Reflector {
|
|||
ReflectedType reflectFutureOf(Type type) {
|
||||
throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Find a type by its name.
|
||||
///
|
||||
/// This method is used to support Class@method syntax by finding a type
|
||||
/// from its string name.
|
||||
Type? findTypeByName(String name) {
|
||||
throw UnsupportedError('`findTypeByName` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Find an instance method by its name.
|
||||
///
|
||||
/// This method is used to support Class@method syntax by finding a method
|
||||
/// on an instance from its string name.
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
throw UnsupportedError('`findInstanceMethod` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Get annotations for a type.
|
||||
///
|
||||
/// This method returns a list of reflected instances representing the annotations
|
||||
/// applied to the given type.
|
||||
List<ReflectedInstance> getAnnotations(Type type) {
|
||||
throw UnsupportedError('`getAnnotations` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Get annotations for a parameter.
|
||||
///
|
||||
/// This method returns a list of reflected instances representing the annotations
|
||||
/// applied to the parameter with the given name in the specified constructor of
|
||||
/// the given type.
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) {
|
||||
throw UnsupportedError(
|
||||
'`getParameterAnnotations` requires `dart:mirrors`.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a reflected instance of an object.
|
||||
|
@ -241,6 +276,9 @@ abstract class ReflectedFunction {
|
|||
other.isGetter == isGetter &&
|
||||
other.isSetter == isSetter;
|
||||
|
||||
/// Invoke this function with the given invocation.
|
||||
///
|
||||
/// This method is used to support dynamic method invocation.
|
||||
ReflectedInstance invoke(Invocation invocation);
|
||||
}
|
||||
|
||||
|
@ -265,13 +303,15 @@ class ReflectedParameter {
|
|||
final ReflectedType type;
|
||||
final bool isRequired;
|
||||
final bool isNamed;
|
||||
final bool isVariadic;
|
||||
|
||||
const ReflectedParameter(
|
||||
this.name, this.annotations, this.type, this.isRequired, this.isNamed);
|
||||
this.name, this.annotations, this.type, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
hashObjects([name, annotations, type, isRequired, isNamed]);
|
||||
hashObjects([name, annotations, type, isRequired, isNamed, isVariadic]);
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
|
@ -281,7 +321,8 @@ class ReflectedParameter {
|
|||
.equals(other.annotations, annotations) &&
|
||||
other.type == type &&
|
||||
other.isRequired == isRequired &&
|
||||
other.isNamed == isNamed;
|
||||
other.isNamed == isNamed &&
|
||||
other.isVariadic == isVariadic;
|
||||
}
|
||||
|
||||
class ReflectedTypeParameter {
|
||||
|
|
230
packages/container/container/test/alias_test.dart
Normal file
230
packages/container/container/test/alias_test.dart
Normal file
|
@ -0,0 +1,230 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
// Test interfaces and implementations
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
final String filename;
|
||||
FileLogger(this.filename);
|
||||
@override
|
||||
void log(String message) => print('File($filename): $message');
|
||||
}
|
||||
|
||||
class LoggerClient {
|
||||
final Logger logger;
|
||||
LoggerClient(this.logger);
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == LoggerClient) {
|
||||
return MockReflectedClass(
|
||||
'LoggerClient',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('logger', Logger)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => LoggerClient(positional[0]),
|
||||
);
|
||||
} else if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('filename', String)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => FileLogger(positional[0]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedClass {
|
||||
final Function instanceBuilder;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
List<ReflectedInstance> annotations,
|
||||
List<ReflectedFunction> constructors,
|
||||
List<ReflectedDeclaration> declarations,
|
||||
Type reflectedType,
|
||||
this.instanceBuilder,
|
||||
) : super(name, typeParameters, annotations, constructors, declarations,
|
||||
reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
var instance = instanceBuilder(
|
||||
constructorName, positionalArguments, namedArguments, typeArguments);
|
||||
return MockReflectedInstance(this, instance);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance extends ReflectedInstance {
|
||||
MockReflectedInstance(ReflectedClass clazz, Object? reflectee)
|
||||
: super(clazz, clazz, reflectee);
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor extends ReflectedFunction {
|
||||
final List<ReflectedParameter> params;
|
||||
|
||||
MockConstructor(this.params)
|
||||
: super('', [], [], params, false, false,
|
||||
returnType: MockReflectedType('void', [], dynamic));
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockParameter extends ReflectedParameter {
|
||||
MockParameter(String name, Type type)
|
||||
: super(name, [], MockReflectedType(type.toString(), [], type), true,
|
||||
false);
|
||||
}
|
||||
|
||||
class MockReflectedType extends ReflectedType {
|
||||
MockReflectedType(String name, List<ReflectedTypeParameter> typeParameters,
|
||||
Type reflectedType)
|
||||
: super(name, typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Alias Tests', () {
|
||||
test('alias resolves to concrete type', () {
|
||||
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('isAlias returns true for aliased type', () {
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
expect(container.isAlias(Logger), isTrue);
|
||||
});
|
||||
|
||||
test('isAlias returns false for non-aliased type', () {
|
||||
expect(container.isAlias(ConsoleLogger), isFalse);
|
||||
});
|
||||
|
||||
test('getAlias returns concrete type for aliased type', () {
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
expect(container.getAlias(Logger), equals(ConsoleLogger));
|
||||
});
|
||||
|
||||
test('getAlias returns same type for non-aliased type', () {
|
||||
expect(container.getAlias(ConsoleLogger), equals(ConsoleLogger));
|
||||
});
|
||||
|
||||
test('alias works with contextual bindings', () {
|
||||
// Register both logger implementations
|
||||
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||
container.registerSingleton<FileLogger>(FileLogger('test.log'));
|
||||
|
||||
// Set up the alias
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
|
||||
// Set up contextual binding for the interface
|
||||
container.when(LoggerClient).needs<Logger>().give<FileLogger>();
|
||||
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
|
||||
var client = container.make<LoggerClient>();
|
||||
expect(client.logger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('child container inherits parent aliases', () {
|
||||
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
|
||||
var child = container.createChild();
|
||||
var logger = child.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('child container can override parent aliases', () {
|
||||
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||
container.registerSingleton<FileLogger>(FileLogger('test.log'));
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
|
||||
var child = container.createChild();
|
||||
child.alias<Logger>(FileLogger);
|
||||
|
||||
var parentLogger = container.make<Logger>();
|
||||
expect(parentLogger, isA<ConsoleLogger>());
|
||||
|
||||
var childLogger = child.make<Logger>();
|
||||
expect(childLogger, isA<FileLogger>());
|
||||
});
|
||||
});
|
||||
}
|
77
packages/container/container/test/array_access_test.dart
Normal file
77
packages/container/container/test/array_access_test.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print(message);
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Array Access Tests', () {
|
||||
test('can get instance using array syntax', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
var logger = container[Logger];
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can register singleton using array syntax', () {
|
||||
container[Logger] = ConsoleLogger();
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can register factory using array syntax', () {
|
||||
container[Logger] = (Container c) => ConsoleLogger();
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('array access works with parameter overrides', () {
|
||||
container[Logger] = (Container c) {
|
||||
var level = c.getParameterOverride('level') as String? ?? 'info';
|
||||
return ConsoleLogger();
|
||||
};
|
||||
|
||||
var logger =
|
||||
container.withParameters({'level': 'debug'}, () => container[Logger]);
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('array access works with child containers', () {
|
||||
container[Logger] = ConsoleLogger();
|
||||
var child = container.createChild();
|
||||
var logger = child[Logger];
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
});
|
||||
}
|
317
packages/container/container/test/array_concrete_test.dart
Normal file
317
packages/container/container/test/array_concrete_test.dart
Normal file
|
@ -0,0 +1,317 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('File: $message');
|
||||
}
|
||||
|
||||
class Service {
|
||||
final Logger logger;
|
||||
Service(this.logger);
|
||||
}
|
||||
|
||||
class AnotherService {
|
||||
final Logger logger;
|
||||
AnotherService(this.logger);
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
if (clazz == AnotherService) {
|
||||
return MockReflectedClass(
|
||||
'AnotherService',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
AnotherService);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
if (type == AnotherService) {
|
||||
return MockReflectedClass(
|
||||
'AnotherService',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
AnotherService);
|
||||
}
|
||||
if (type == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger);
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger', [], [], [MockConstructor('', [])], FileLogger);
|
||||
}
|
||||
if (type == Logger) {
|
||||
return MockReflectedClass(
|
||||
'Logger', [], [], [MockConstructor('', [])], Logger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(name, typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
if (reflectedType == Service) {
|
||||
return MockReflectedInstance(Service(positionalArguments[0] as Logger));
|
||||
}
|
||||
if (reflectedType == AnotherService) {
|
||||
return MockReflectedInstance(
|
||||
AnotherService(positionalArguments[0] as Logger));
|
||||
}
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(ConsoleLogger());
|
||||
}
|
||||
if (reflectedType == FileLogger) {
|
||||
return MockReflectedInstance(FileLogger());
|
||||
}
|
||||
if (reflectedType == Logger) {
|
||||
throw BindingResolutionException(
|
||||
'No implementation was provided for Logger');
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == FileLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) => false;
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Array of Concrete Types Tests', () {
|
||||
test('can bind different implementations for different concrete types', () {
|
||||
container.when([Service]).needs<Logger>().give<ConsoleLogger>();
|
||||
container.when([AnotherService]).needs<Logger>().give<FileLogger>();
|
||||
|
||||
var service = container.make<Service>();
|
||||
var anotherService = container.make<AnotherService>();
|
||||
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(anotherService.logger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('can bind same implementation for multiple concrete types', () {
|
||||
container
|
||||
.when([Service, AnotherService])
|
||||
.needs<Logger>()
|
||||
.give<ConsoleLogger>();
|
||||
|
||||
var service = container.make<Service>();
|
||||
var anotherService = container.make<AnotherService>();
|
||||
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(anotherService.logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('later bindings override earlier ones', () {
|
||||
container
|
||||
.when([Service, AnotherService])
|
||||
.needs<Logger>()
|
||||
.give<ConsoleLogger>();
|
||||
container.when([AnotherService]).needs<Logger>().give<FileLogger>();
|
||||
|
||||
var service = container.make<Service>();
|
||||
var anotherService = container.make<AnotherService>();
|
||||
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(anotherService.logger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('throws when no implementation is provided', () {
|
||||
container.when([Service]).needs<Logger>();
|
||||
|
||||
expect(() => container.make<Service>(),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
519
packages/container/container/test/attribute_binding_test.dart
Normal file
519
packages/container/container/test/attribute_binding_test.dart
Normal file
|
@ -0,0 +1,519 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
@Injectable(bindTo: Logger, tags: ['console'])
|
||||
class ConsoleLogger implements Logger {
|
||||
final String level;
|
||||
|
||||
ConsoleLogger({this.level = 'info'});
|
||||
|
||||
@override
|
||||
void log(String message) => print('Console($level): $message');
|
||||
}
|
||||
|
||||
@Injectable(bindTo: Logger, tags: ['file'])
|
||||
class FileLogger implements Logger {
|
||||
final String filename;
|
||||
|
||||
FileLogger({required this.filename});
|
||||
|
||||
@override
|
||||
void log(String message) => print('File($filename): $message');
|
||||
}
|
||||
|
||||
class Service {
|
||||
final Logger consoleLogger;
|
||||
final Logger fileLogger;
|
||||
final List<Logger> allLoggers;
|
||||
|
||||
Service(
|
||||
@InjectTagged('console') this.consoleLogger,
|
||||
@Inject(FileLogger, config: {'filename': 'app.log'}) this.fileLogger,
|
||||
@InjectAll() this.allLoggers,
|
||||
);
|
||||
|
||||
void logMessage(String message) {
|
||||
for (var logger in allLoggers) {
|
||||
logger.log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable(singleton: true)
|
||||
class SingletonService {
|
||||
static int instanceCount = 0;
|
||||
final int instanceNumber;
|
||||
|
||||
SingletonService() : instanceNumber = ++instanceCount;
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('consoleLogger', Logger, true, false),
|
||||
MockParameter('fileLogger', Logger, true, false),
|
||||
MockParameter('allLoggers', List<Logger>, true, false),
|
||||
])
|
||||
],
|
||||
Service,
|
||||
this);
|
||||
}
|
||||
if (clazz == SingletonService) {
|
||||
return MockReflectedClass('SingletonService', [], [],
|
||||
[MockConstructor('', [])], SingletonService, this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == List<Logger>) {
|
||||
return MockReflectedClass('List<Logger>', [], [], [], List<Logger>, this);
|
||||
}
|
||||
if (type == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('consoleLogger', Logger, true, false),
|
||||
MockParameter('fileLogger', Logger, true, false),
|
||||
MockParameter('allLoggers', List<Logger>, true, false),
|
||||
])
|
||||
],
|
||||
Service,
|
||||
this);
|
||||
}
|
||||
if (type == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('level', String, false, true),
|
||||
])
|
||||
],
|
||||
ConsoleLogger,
|
||||
this);
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('filename', String, true, true),
|
||||
])
|
||||
],
|
||||
FileLogger,
|
||||
this);
|
||||
}
|
||||
if (type == Logger) {
|
||||
return MockReflectedClass(
|
||||
'Logger', [], [], [MockConstructor('', [])], Logger, this);
|
||||
}
|
||||
if (type == SingletonService) {
|
||||
return MockReflectedClass('SingletonService', [], [],
|
||||
[MockConstructor('', [])], SingletonService, this);
|
||||
}
|
||||
if (type == String) {
|
||||
return MockReflectedClass(
|
||||
'String', [], [], [MockConstructor('', [])], String, this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) {
|
||||
if (type == ConsoleLogger) {
|
||||
return [
|
||||
MockReflectedInstance(Injectable(bindTo: Logger, tags: ['console']))
|
||||
];
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return [
|
||||
MockReflectedInstance(Injectable(bindTo: Logger, tags: ['file']))
|
||||
];
|
||||
}
|
||||
if (type == SingletonService) {
|
||||
return [MockReflectedInstance(Injectable(singleton: true))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) {
|
||||
if (type == Service) {
|
||||
if (parameterName == 'consoleLogger') {
|
||||
return [MockReflectedInstance(InjectTagged('console'))];
|
||||
}
|
||||
if (parameterName == 'fileLogger') {
|
||||
return [
|
||||
MockReflectedInstance(
|
||||
Inject(FileLogger, config: {'filename': 'app.log'}))
|
||||
];
|
||||
}
|
||||
if (parameterName == 'allLoggers') {
|
||||
return [MockReflectedInstance(InjectAll())];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
final MockReflector? reflector;
|
||||
|
||||
MockReflectedClass(String name, List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations, this.constructors, Type reflectedType,
|
||||
[this.reflector])
|
||||
: declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) {
|
||||
return reflector?.getParameterAnnotations(
|
||||
type, constructorName, parameterName) ??
|
||||
[];
|
||||
}
|
||||
|
||||
void _validateParameters(List<ReflectedParameter> parameters,
|
||||
List positionalArguments, Map<String, dynamic> namedArguments) {
|
||||
var paramIndex = 0;
|
||||
for (var param in parameters) {
|
||||
if (param.isNamed) {
|
||||
if (param.isRequired && !namedArguments.containsKey(param.name)) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
} else {
|
||||
if (param.isRequired && paramIndex >= positionalArguments.length) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
// Handle List<Logger> specially
|
||||
if (reflectedType == List<Logger>) {
|
||||
var loggers = <Logger>[];
|
||||
if (positionalArguments.isNotEmpty) {
|
||||
if (positionalArguments[0] is List) {
|
||||
for (var item in positionalArguments[0] as List) {
|
||||
if (item is Logger) {
|
||||
loggers.add(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var item in positionalArguments) {
|
||||
if (item is Logger) {
|
||||
loggers.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return MockReflectedInstance(loggers);
|
||||
}
|
||||
|
||||
// Find constructor
|
||||
var constructor = constructors.firstWhere((c) => c.name == constructorName,
|
||||
orElse: () => constructors.first);
|
||||
|
||||
// Validate parameters
|
||||
_validateParameters(
|
||||
constructor.parameters, positionalArguments, namedArguments);
|
||||
|
||||
if (reflectedType == Service) {
|
||||
// Get parameter annotations
|
||||
var fileLoggerAnnotations =
|
||||
getParameterAnnotations(Service, '', 'fileLogger');
|
||||
var fileLoggerConfig = fileLoggerAnnotations
|
||||
.firstWhere((a) => a.reflectee is Inject)
|
||||
.reflectee as Inject;
|
||||
|
||||
var allLoggers = <Logger>[];
|
||||
if (positionalArguments[2] is List) {
|
||||
for (var item in positionalArguments[2] as List) {
|
||||
if (item is Logger) {
|
||||
allLoggers.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MockReflectedInstance(Service(
|
||||
positionalArguments[0] as Logger,
|
||||
FileLogger(filename: fileLoggerConfig.config['filename'] as String),
|
||||
allLoggers,
|
||||
));
|
||||
}
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(
|
||||
ConsoleLogger(level: namedArguments['level'] as String? ?? 'info'));
|
||||
}
|
||||
if (reflectedType == FileLogger) {
|
||||
return MockReflectedInstance(
|
||||
FileLogger(filename: namedArguments['filename'] as String));
|
||||
}
|
||||
if (reflectedType == SingletonService) {
|
||||
return MockReflectedInstance(SingletonService());
|
||||
}
|
||||
if (reflectedType == Logger) {
|
||||
throw BindingResolutionException(
|
||||
'No implementation was provided for Logger');
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
// Handle primitive types and exact matches
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
// Handle Logger implementations
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == FileLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
// Handle List<Logger>
|
||||
if (reflectedType.toString() == 'List<Logger>' &&
|
||||
other?.reflectedType.toString() == 'List<Logger>') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
// Handle primitive types
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
// Handle Logger implementations
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == FileLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
// Handle List<Logger>
|
||||
if (reflectedType.toString() == 'List<Logger>' &&
|
||||
other?.reflectedType.toString() == 'List<Logger>') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Attribute Binding Tests', () {
|
||||
setUp(() {
|
||||
// Reset instance count
|
||||
SingletonService.instanceCount = 0;
|
||||
|
||||
// Register implementations with attributes
|
||||
container.registerAttributeBindings(ConsoleLogger);
|
||||
container.registerAttributeBindings(FileLogger);
|
||||
container.registerAttributeBindings(SingletonService);
|
||||
|
||||
// Register FileLogger binding with configuration
|
||||
container
|
||||
.registerFactory<FileLogger>((c) => FileLogger(filename: 'app.log'));
|
||||
|
||||
// Set ConsoleLogger as default implementation for Logger
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
|
||||
// Register implementations for @InjectAll
|
||||
container.registerFactory<List<Logger>>(
|
||||
(c) => [ConsoleLogger(), FileLogger(filename: 'app.log')]);
|
||||
|
||||
// Register contextual binding for Service's fileLogger parameter
|
||||
container
|
||||
.when(Service)
|
||||
.needs<FileLogger>()
|
||||
.giveFactory((c) => FileLogger(filename: 'app.log'));
|
||||
});
|
||||
|
||||
test('can bind implementation using @Injectable', () {
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can bind implementation using @Injectable with tags', () {
|
||||
var consoleLogger = container.tagged('console').first;
|
||||
expect(consoleLogger, isA<ConsoleLogger>());
|
||||
|
||||
var fileLogger = container.tagged('file').first;
|
||||
expect(fileLogger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('can inject tagged implementation using @InjectTagged', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.consoleLogger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can inject configured implementation using @Inject', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.fileLogger, isA<FileLogger>());
|
||||
expect((service.fileLogger as FileLogger).filename, equals('app.log'));
|
||||
});
|
||||
|
||||
test('can inject all implementations using @InjectAll', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.allLoggers, hasLength(2));
|
||||
expect(service.allLoggers[0], isA<ConsoleLogger>());
|
||||
expect(service.allLoggers[1], isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('can bind singleton using @Injectable', () {
|
||||
var first = container.make<SingletonService>();
|
||||
var second = container.make<SingletonService>();
|
||||
expect(first.instanceNumber, equals(1));
|
||||
expect(second.instanceNumber, equals(1));
|
||||
expect(identical(first, second), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
208
packages/container/container/test/class_method_syntax_test.dart
Normal file
208
packages/container/container/test/class_method_syntax_test.dart
Normal file
|
@ -0,0 +1,208 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class Logger {
|
||||
String log(String message) => message;
|
||||
void debug(String message, {int? level}) {}
|
||||
int count(List<String> items) => items.length;
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) {
|
||||
if (name == 'Logger') return Logger;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
if (instance is Logger) {
|
||||
switch (methodName) {
|
||||
case 'log':
|
||||
return MockMethod('log', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method log requires a message parameter');
|
||||
}
|
||||
return MockReflectedInstance(instance.log(args[0] as String));
|
||||
}, [MockParameter('message', String, true, false)]);
|
||||
case 'debug':
|
||||
return MockMethod('debug', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method debug requires a message parameter');
|
||||
}
|
||||
instance.debug(args[0] as String);
|
||||
return MockReflectedInstance(null);
|
||||
}, [
|
||||
MockParameter('message', String, true, false),
|
||||
MockParameter('level', int, false, true)
|
||||
]);
|
||||
case 'count':
|
||||
return MockMethod('count', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method count requires a list parameter');
|
||||
}
|
||||
return MockReflectedInstance(
|
||||
instance.count(args[0] as List<String>));
|
||||
}, [MockParameter('items', List<String>, true, false)]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMethod implements ReflectedFunction {
|
||||
final String methodName;
|
||||
final ReflectedInstance Function(Invocation) handler;
|
||||
final List<ReflectedParameter> _parameters;
|
||||
|
||||
MockMethod(this.methodName, this.handler,
|
||||
[List<ReflectedParameter>? parameters])
|
||||
: _parameters = parameters ?? [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => methodName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => _parameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final bool isVariadic;
|
||||
final Type paramType;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) => false;
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
container.registerSingleton(Logger());
|
||||
});
|
||||
|
||||
group('Class@method syntax', () {
|
||||
test('can call method with return value', () {
|
||||
var result = container.call('Logger@log', ['Hello world']);
|
||||
expect(result, equals('Hello world'));
|
||||
});
|
||||
|
||||
test('can call void method', () {
|
||||
expect(() => container.call('Logger@debug', ['Debug message']),
|
||||
returnsNormally);
|
||||
});
|
||||
|
||||
test('can call method with list parameter', () {
|
||||
var result = container.call('Logger@count', [
|
||||
['one', 'two', 'three']
|
||||
]);
|
||||
expect(result, equals(3));
|
||||
});
|
||||
|
||||
test('throws on invalid syntax', () {
|
||||
expect(() => container.call('Logger'), throwsArgumentError);
|
||||
expect(() => container.call('Logger@'), throwsArgumentError);
|
||||
expect(() => container.call('@log'), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws on unknown class', () {
|
||||
expect(() => container.call('Unknown@method'), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws on unknown method', () {
|
||||
expect(() => container.call('Logger@unknown'), throwsArgumentError);
|
||||
});
|
||||
});
|
||||
}
|
212
packages/container/container/test/conditional_binding_test.dart
Normal file
212
packages/container/container/test/conditional_binding_test.dart
Normal file
|
@ -0,0 +1,212 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('File: $message');
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Conditional Binding Tests', () {
|
||||
test('bindIf registers binding when not bound', () {
|
||||
container.bindIf<Logger>(ConsoleLogger());
|
||||
expect(container.make<Logger>(), isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('bindIf skips registration when already bound', () {
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
container.bindIf<Logger>(FileLogger());
|
||||
expect(container.make<Logger>(), isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('bindIf registers singleton when specified', () {
|
||||
container.bindIf<Logger>(ConsoleLogger(), singleton: true);
|
||||
var first = container.make<Logger>();
|
||||
var second = container.make<Logger>();
|
||||
expect(identical(first, second), isTrue);
|
||||
});
|
||||
|
||||
test('singletonIf registers singleton when not bound', () {
|
||||
container.singletonIf<Logger>(ConsoleLogger());
|
||||
var first = container.make<Logger>();
|
||||
var second = container.make<Logger>();
|
||||
expect(identical(first, second), isTrue);
|
||||
});
|
||||
|
||||
test('singletonIf skips registration when already bound', () {
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
container.singletonIf<Logger>(FileLogger());
|
||||
expect(container.make<Logger>(), isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('bindIf works with factory functions', () {
|
||||
container.bindIf<Logger>((c) => ConsoleLogger());
|
||||
expect(container.make<Logger>(), isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('singletonIf works with factory functions', () {
|
||||
container.singletonIf<Logger>((c) => ConsoleLogger());
|
||||
var first = container.make<Logger>();
|
||||
var second = container.make<Logger>();
|
||||
expect(identical(first, second), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == Logger) {
|
||||
return MockReflectedClass('Logger', [], [], [], Logger);
|
||||
}
|
||||
if (clazz == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger);
|
||||
}
|
||||
if (clazz == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger', [], [], [MockConstructor('', [])], FileLogger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
return reflectClass(type);
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) => [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) =>
|
||||
[];
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(ConsoleLogger());
|
||||
}
|
||||
if (reflectedType == FileLogger) {
|
||||
return MockReflectedInstance(FileLogger());
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
if ((reflectedType == ConsoleLogger || reflectedType == FileLogger) &&
|
||||
other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_container/container.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
|
||||
// Test interfaces
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
abstract class Config {
|
||||
String get value;
|
||||
}
|
||||
|
||||
abstract class Database {
|
||||
void connect();
|
||||
}
|
||||
|
||||
// Test implementations
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) {}
|
||||
}
|
||||
|
||||
class FileConfig implements Config {
|
||||
@override
|
||||
String get value => 'test';
|
||||
}
|
||||
|
||||
class SqlDatabase implements Database {
|
||||
final Logger logger;
|
||||
final Config config;
|
||||
|
||||
SqlDatabase(this.logger, this.config);
|
||||
|
||||
@override
|
||||
void connect() {}
|
||||
}
|
||||
|
||||
// Tracking implementations for dependency order test
|
||||
class TrackingLogger implements Logger {
|
||||
final List<String> order;
|
||||
static int instanceCount = 0;
|
||||
|
||||
TrackingLogger(this.order) {
|
||||
instanceCount++;
|
||||
order.add('logger');
|
||||
}
|
||||
|
||||
@override
|
||||
void log(String message) {}
|
||||
}
|
||||
|
||||
class TrackingConfig implements Config {
|
||||
final List<String> order;
|
||||
static int instanceCount = 0;
|
||||
|
||||
TrackingConfig(this.order) {
|
||||
instanceCount++;
|
||||
order.add('config');
|
||||
}
|
||||
|
||||
@override
|
||||
String get value => 'test';
|
||||
}
|
||||
|
||||
class TrackingDatabase implements Database {
|
||||
final Logger logger;
|
||||
final Config config;
|
||||
final List<String> order;
|
||||
static int instanceCount = 0;
|
||||
|
||||
TrackingDatabase(this.logger, this.config, this.order) {
|
||||
instanceCount++;
|
||||
order.add('database');
|
||||
}
|
||||
|
||||
@override
|
||||
void connect() {}
|
||||
}
|
||||
|
||||
// Custom config for multiple instances test
|
||||
class CustomConfig implements Config {
|
||||
final String _value;
|
||||
|
||||
CustomConfig(this._value);
|
||||
|
||||
@override
|
||||
String get value => _value;
|
||||
}
|
||||
|
||||
// Test service with multiple dependencies
|
||||
class UserService {
|
||||
final Logger logger;
|
||||
final Database db;
|
||||
final Config config;
|
||||
|
||||
UserService(this.logger, this.db, this.config);
|
||||
}
|
||||
|
||||
// Test service with optional dependencies
|
||||
class OptionalDepsService {
|
||||
final Logger logger;
|
||||
final Config? config;
|
||||
|
||||
OptionalDepsService(this.logger, [this.config]);
|
||||
}
|
||||
|
||||
// Test service with named parameters
|
||||
class NamedParamsService {
|
||||
final Logger logger;
|
||||
final Config? config;
|
||||
|
||||
NamedParamsService(this.logger, {this.config});
|
||||
}
|
||||
|
||||
// Test service with mixed parameters
|
||||
class MixedParamsService {
|
||||
final Logger logger;
|
||||
final Database db;
|
||||
final Config? config;
|
||||
final String? name;
|
||||
|
||||
MixedParamsService(this.logger, this.db, {this.config, this.name});
|
||||
}
|
||||
|
||||
// Test service with nested dependencies
|
||||
class NestedService {
|
||||
final UserService userService;
|
||||
final Logger logger;
|
||||
|
||||
NestedService(this.userService, this.logger);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Constructor Injection Tests', () {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MirrorsReflector());
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
container.bind(Config).to(FileConfig);
|
||||
container.bind(Database).to(SqlDatabase);
|
||||
|
||||
// Reset tracking counters
|
||||
TrackingLogger.instanceCount = 0;
|
||||
TrackingConfig.instanceCount = 0;
|
||||
TrackingDatabase.instanceCount = 0;
|
||||
});
|
||||
|
||||
test('injects basic dependencies', () {
|
||||
var service = container.make<UserService>();
|
||||
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.config, isA<FileConfig>());
|
||||
expect(service.db, isA<SqlDatabase>());
|
||||
});
|
||||
|
||||
test('injects nested dependencies', () {
|
||||
var service = container.make<NestedService>();
|
||||
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.userService, isA<UserService>());
|
||||
expect(service.userService.logger, isA<ConsoleLogger>());
|
||||
expect(service.userService.config, isA<FileConfig>());
|
||||
expect(service.userService.db, isA<SqlDatabase>());
|
||||
});
|
||||
|
||||
test('handles optional dependencies', () {
|
||||
container = Container(MirrorsReflector());
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
|
||||
var service = container.make<OptionalDepsService>();
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.config, isNull);
|
||||
|
||||
// Now bind config and verify it gets injected
|
||||
container.bind(Config).to(FileConfig);
|
||||
service = container.make<OptionalDepsService>();
|
||||
expect(service.config, isA<FileConfig>());
|
||||
});
|
||||
|
||||
test('handles named parameters', () {
|
||||
container = Container(MirrorsReflector());
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
|
||||
var service = container.make<NamedParamsService>();
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.config, isNull);
|
||||
|
||||
// Now bind config and verify it gets injected
|
||||
container.bind(Config).to(FileConfig);
|
||||
service = container.make<NamedParamsService>();
|
||||
expect(service.config, isA<FileConfig>());
|
||||
});
|
||||
|
||||
test('handles mixed parameters', () {
|
||||
var service = container.makeWith<MixedParamsService>({'name': 'test'});
|
||||
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.db, isA<SqlDatabase>());
|
||||
expect(service.config, isNull);
|
||||
expect(service.name, equals('test'));
|
||||
|
||||
// Now bind config and verify it gets injected
|
||||
container.bind(Config).to(FileConfig);
|
||||
service = container.make<MixedParamsService>();
|
||||
expect(service.config, isA<FileConfig>());
|
||||
});
|
||||
|
||||
test('handles parameter overrides', () {
|
||||
var customConfig = FileConfig();
|
||||
var service = container.makeWith<UserService>({'config': customConfig});
|
||||
|
||||
expect(service, isNotNull);
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect(service.config, same(customConfig));
|
||||
});
|
||||
|
||||
test('resolves dependencies in correct order', () {
|
||||
var order = <String>[];
|
||||
|
||||
container = Container(MirrorsReflector());
|
||||
container.registerSingleton<Logger>(TrackingLogger(order));
|
||||
container.registerSingleton<Config>(TrackingConfig(order));
|
||||
container.registerSingleton<Database>(TrackingDatabase(
|
||||
container.make<Logger>(), container.make<Config>(), order));
|
||||
|
||||
// Create service and verify order
|
||||
container.make<UserService>();
|
||||
|
||||
expect(order, equals(['logger', 'config', 'database']));
|
||||
expect(TrackingLogger.instanceCount, equals(1),
|
||||
reason: 'Logger should be instantiated once');
|
||||
expect(TrackingConfig.instanceCount, equals(1),
|
||||
reason: 'Config should be instantiated once');
|
||||
expect(TrackingDatabase.instanceCount, equals(1),
|
||||
reason: 'Database should be instantiated once');
|
||||
});
|
||||
|
||||
test('throws on missing required dependency', () {
|
||||
container =
|
||||
Container(MirrorsReflector()); // Fresh container with no bindings
|
||||
|
||||
expect(() => container.make<UserService>(),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('handles multiple instances with different configurations', () {
|
||||
var container1 = Container(MirrorsReflector());
|
||||
var container2 = Container(MirrorsReflector());
|
||||
|
||||
container1.bind(Logger).to(ConsoleLogger);
|
||||
container2.bind(Logger).to(ConsoleLogger);
|
||||
container1.bind(Database).to(SqlDatabase);
|
||||
container2.bind(Database).to(SqlDatabase);
|
||||
|
||||
container1.registerSingleton<Config>(CustomConfig('config1'));
|
||||
container2.registerSingleton<Config>(CustomConfig('config2'));
|
||||
|
||||
var service1 = container1.make<UserService>();
|
||||
var service2 = container2.make<UserService>();
|
||||
|
||||
expect(service1.config.value, equals('config1'));
|
||||
expect(service2.config.value, equals('config2'));
|
||||
});
|
||||
|
||||
test('throws when trying to instantiate abstract class', () {
|
||||
container = Container(MirrorsReflector());
|
||||
|
||||
expect(() => container.make<Logger>(),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('throws when dependency is abstract', () {
|
||||
container = Container(MirrorsReflector());
|
||||
container.bind(Database).to(SqlDatabase);
|
||||
|
||||
expect(() => container.make<Database>(),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
275
packages/container/container/test/contextual_binding_test.dart
Normal file
275
packages/container/container/test/contextual_binding_test.dart
Normal file
|
@ -0,0 +1,275 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
// Test interfaces and implementations
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
final String filename;
|
||||
FileLogger(this.filename);
|
||||
@override
|
||||
void log(String message) => print('File($filename): $message');
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
class LoggerClient {
|
||||
final Logger logger;
|
||||
LoggerClient(this.logger);
|
||||
}
|
||||
|
||||
class SpecialLoggerClient {
|
||||
final Logger logger;
|
||||
SpecialLoggerClient(this.logger);
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Contextual Binding Tests', () {
|
||||
test('basic contextual binding resolves correctly', () {
|
||||
// Register default binding
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
// Register contextual binding
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => FileLogger('test.log'));
|
||||
|
||||
// The default binding should be used here
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
|
||||
// The contextual binding should be used here
|
||||
var client = container.make<LoggerClient>();
|
||||
expect(client.logger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('multiple contextual bindings work independently', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => FileLogger('test.log'));
|
||||
container
|
||||
.when(SpecialLoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => ConsoleLogger());
|
||||
|
||||
var client1 = container.make<LoggerClient>();
|
||||
var client2 = container.make<SpecialLoggerClient>();
|
||||
|
||||
expect(client1.logger, isA<FileLogger>());
|
||||
expect(client2.logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('contextual binding with factory function works', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((container) => FileLogger('test.log'));
|
||||
|
||||
var client = container.make<LoggerClient>();
|
||||
expect(client.logger, isA<FileLogger>());
|
||||
expect((client.logger as FileLogger).filename, equals('test.log'));
|
||||
});
|
||||
|
||||
test('contextual binding throws when implementation not found', () {
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => FileLogger('test.log'));
|
||||
|
||||
expect(
|
||||
() => container.make<LoggerClient>(),
|
||||
throwsA(isA<BindingResolutionException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('contextual bindings are inherited by child containers', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => FileLogger('test.log'));
|
||||
|
||||
var childContainer = container.createChild();
|
||||
var client = childContainer.make<LoggerClient>();
|
||||
|
||||
expect(client.logger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('child container can override parent contextual binding', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
container
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((c) => FileLogger('test.log'));
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer
|
||||
.when(LoggerClient)
|
||||
.needs<Logger>()
|
||||
.giveFactory((container) => FileLogger('child.log'));
|
||||
|
||||
var client = childContainer.make<LoggerClient>();
|
||||
expect(client.logger, isA<FileLogger>());
|
||||
expect((client.logger as FileLogger).filename, equals('child.log'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Mock reflector implementation for testing
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == LoggerClient) {
|
||||
return MockReflectedClass(
|
||||
'LoggerClient',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('logger', Logger)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => LoggerClient(positional[0]),
|
||||
);
|
||||
} else if (type == SpecialLoggerClient) {
|
||||
return MockReflectedClass(
|
||||
'SpecialLoggerClient',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('logger', Logger)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) =>
|
||||
SpecialLoggerClient(positional[0]),
|
||||
);
|
||||
} else if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('filename', String)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => FileLogger(positional[0]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedClass {
|
||||
final Function instanceBuilder;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
List<ReflectedInstance> annotations,
|
||||
List<ReflectedFunction> constructors,
|
||||
List<ReflectedDeclaration> declarations,
|
||||
Type reflectedType,
|
||||
this.instanceBuilder,
|
||||
) : super(name, typeParameters, annotations, constructors, declarations,
|
||||
reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
var instance = instanceBuilder(
|
||||
constructorName, positionalArguments, namedArguments, typeArguments);
|
||||
return MockReflectedInstance(this, instance);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance extends ReflectedInstance {
|
||||
MockReflectedInstance(ReflectedClass clazz, Object? reflectee)
|
||||
: super(clazz, clazz, reflectee);
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor extends ReflectedFunction {
|
||||
final List<ReflectedParameter> params;
|
||||
|
||||
MockConstructor(this.params)
|
||||
: super('', [], [], params, false, false,
|
||||
returnType: MockReflectedType('void', [], dynamic));
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockParameter extends ReflectedParameter {
|
||||
MockParameter(String name, Type type)
|
||||
: super(name, [], MockReflectedType(type.toString(), [], type), true,
|
||||
false);
|
||||
}
|
||||
|
||||
class MockReflectedType extends ReflectedType {
|
||||
MockReflectedType(String name, List<ReflectedTypeParameter> typeParameters,
|
||||
Type reflectedType)
|
||||
: super(name, typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
113
packages/container/container/test/extend_test.dart
Normal file
113
packages/container/container/test/extend_test.dart
Normal file
|
@ -0,0 +1,113 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
abstract class Logger {
|
||||
LogLevel get level;
|
||||
set level(LogLevel value);
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
enum LogLevel { debug, info, warning, error }
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
LogLevel _level = LogLevel.info;
|
||||
|
||||
@override
|
||||
LogLevel get level => _level;
|
||||
|
||||
@override
|
||||
set level(LogLevel value) => _level = value;
|
||||
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Service Extender Tests', () {
|
||||
test('can extend a service after resolution', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
container.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.debug;
|
||||
return logger;
|
||||
});
|
||||
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger.level, equals(LogLevel.debug));
|
||||
});
|
||||
|
||||
test('can apply multiple extenders in order', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
container.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.debug;
|
||||
return logger;
|
||||
});
|
||||
|
||||
container.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.error;
|
||||
return logger;
|
||||
});
|
||||
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger.level, equals(LogLevel.error));
|
||||
});
|
||||
|
||||
test('child container inherits parent extenders', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
container.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.debug;
|
||||
return logger;
|
||||
});
|
||||
|
||||
var child = container.createChild();
|
||||
var logger = child.make<Logger>();
|
||||
expect(logger.level, equals(LogLevel.debug));
|
||||
});
|
||||
|
||||
test('child container can add its own extenders', () {
|
||||
container.registerSingleton<Logger>(ConsoleLogger());
|
||||
container.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.debug;
|
||||
return logger;
|
||||
});
|
||||
|
||||
var child = container.createChild();
|
||||
child.extend<Logger>((logger, container) {
|
||||
logger.level = LogLevel.error;
|
||||
return logger;
|
||||
});
|
||||
|
||||
var parentLogger = container.make<Logger>();
|
||||
expect(parentLogger.level, equals(LogLevel.debug));
|
||||
|
||||
var childLogger = child.make<Logger>();
|
||||
expect(childLogger.level, equals(LogLevel.error));
|
||||
});
|
||||
});
|
||||
}
|
209
packages/container/container/test/factory_test.dart
Normal file
209
packages/container/container/test/factory_test.dart
Normal file
|
@ -0,0 +1,209 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Factory Tests', () {
|
||||
test('factory creates deferred binding', () {
|
||||
var created = false;
|
||||
container.factory<Logger>(() {
|
||||
created = true;
|
||||
return ConsoleLogger();
|
||||
});
|
||||
|
||||
// Verify binding is not created yet
|
||||
expect(created, isFalse);
|
||||
|
||||
// Resolve the binding
|
||||
var logger = container.make<Logger>();
|
||||
|
||||
// Verify binding was created
|
||||
expect(created, isTrue);
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('factory creates new instance each time', () {
|
||||
container.factory<Logger>(() => ConsoleLogger());
|
||||
|
||||
var logger1 = container.make<Logger>();
|
||||
var logger2 = container.make<Logger>();
|
||||
|
||||
expect(logger1, isNot(same(logger2)));
|
||||
});
|
||||
|
||||
test('factory throws when already bound', () {
|
||||
container.factory<Logger>(() => ConsoleLogger());
|
||||
|
||||
expect(() => container.factory<Logger>(() => ConsoleLogger()),
|
||||
throwsA(isA<StateError>()));
|
||||
});
|
||||
|
||||
test('factory works with interfaces', () {
|
||||
container.factory<Logger>(() => ConsoleLogger());
|
||||
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<Logger>());
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('factory preserves parameter overrides', () {
|
||||
var paramValue = '';
|
||||
container.factory<Logger>(() {
|
||||
paramValue = container.getParameterOverride('level') as String;
|
||||
return ConsoleLogger();
|
||||
});
|
||||
|
||||
container.withParameters({'level': 'debug'}, () {
|
||||
container.make<Logger>();
|
||||
});
|
||||
|
||||
expect(paramValue, equals('debug'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
return reflectClass(type);
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) => [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) =>
|
||||
[];
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(ConsoleLogger());
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
222
packages/container/container/test/flush_test.dart
Normal file
222
packages/container/container/test/flush_test.dart
Normal file
|
@ -0,0 +1,222 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Flush Tests', () {
|
||||
test('flush clears all bindings', () {
|
||||
container.factory<Logger>(() => ConsoleLogger());
|
||||
container.registerSingleton<String>('test');
|
||||
container.registerNamedSingleton('logger', ConsoleLogger());
|
||||
container.alias<Logger>(ConsoleLogger);
|
||||
container.tag([Logger], 'logging');
|
||||
|
||||
container.flush();
|
||||
|
||||
expect(container.has<Logger>(), isFalse);
|
||||
expect(container.has<String>(), isFalse);
|
||||
expect(container.hasNamed('logger'), isFalse);
|
||||
expect(container.isAlias(Logger), isFalse);
|
||||
expect(container.tagged('logging'), isEmpty);
|
||||
});
|
||||
|
||||
test('flush clears callbacks', () {
|
||||
var beforeResolvingCalled = false;
|
||||
var resolvingCalled = false;
|
||||
var afterResolvingCalled = false;
|
||||
var reboundCalled = false;
|
||||
|
||||
container.beforeResolving<Logger>((type, args, container) {
|
||||
beforeResolvingCalled = true;
|
||||
});
|
||||
|
||||
container.resolving<Logger>((instance, container) {
|
||||
resolvingCalled = true;
|
||||
});
|
||||
|
||||
container.afterResolving<Logger>((instance, container) {
|
||||
afterResolvingCalled = true;
|
||||
});
|
||||
|
||||
container.rebinding<Logger>((instance, container) {
|
||||
reboundCalled = true;
|
||||
});
|
||||
|
||||
container.flush();
|
||||
container.factory<Logger>(() => ConsoleLogger());
|
||||
container.make<Logger>();
|
||||
container.refresh<Logger>();
|
||||
|
||||
expect(beforeResolvingCalled, isFalse);
|
||||
expect(resolvingCalled, isFalse);
|
||||
expect(afterResolvingCalled, isFalse);
|
||||
expect(reboundCalled, isFalse);
|
||||
});
|
||||
|
||||
test('flush clears contextual bindings', () {
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
container.flush();
|
||||
|
||||
expect(() => container.make<Logger>(),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('flush preserves parent bindings', () {
|
||||
var parent = Container(MockReflector());
|
||||
parent.registerSingleton<String>('parent');
|
||||
var child = parent.createChild();
|
||||
child.registerSingleton<Logger>(ConsoleLogger());
|
||||
|
||||
child.flush();
|
||||
|
||||
expect(child.has<String>(), isTrue);
|
||||
expect(child.has<Logger>(), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
return reflectClass(type);
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) => [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) =>
|
||||
[];
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(ConsoleLogger());
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
185
packages/container/container/test/invoke_test.dart
Normal file
185
packages/container/container/test/invoke_test.dart
Normal file
|
@ -0,0 +1,185 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class InvokableClass {
|
||||
String call(String message) => 'Invoked with: $message';
|
||||
String invoke(String message) => 'Called invoke with: $message';
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) {
|
||||
if (name == 'InvokableClass') return InvokableClass;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
if (instance is InvokableClass) {
|
||||
switch (methodName) {
|
||||
case '__invoke':
|
||||
case 'call':
|
||||
return MockMethod('call', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method call requires a message parameter');
|
||||
}
|
||||
return MockReflectedInstance(instance.call(args[0] as String));
|
||||
}, [MockParameter('message', String, true, false)]);
|
||||
case 'invoke':
|
||||
return MockMethod('invoke', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method invoke requires a message parameter');
|
||||
}
|
||||
return MockReflectedInstance(instance.invoke(args[0] as String));
|
||||
}, [MockParameter('message', String, true, false)]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMethod implements ReflectedFunction {
|
||||
final String methodName;
|
||||
final ReflectedInstance Function(Invocation) handler;
|
||||
final List<ReflectedParameter> _parameters;
|
||||
|
||||
MockMethod(this.methodName, this.handler,
|
||||
[List<ReflectedParameter>? parameters])
|
||||
: _parameters = parameters ?? [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => methodName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => _parameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) => false;
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
container.registerSingleton(InvokableClass());
|
||||
});
|
||||
|
||||
group('Invoke Tests', () {
|
||||
test('can call __invoke method', () {
|
||||
var result = container.call('InvokableClass@__invoke', ['Hello world']);
|
||||
expect(result, equals('Invoked with: Hello world'));
|
||||
});
|
||||
|
||||
test('__invoke is alias for call method', () {
|
||||
var invokeResult = container.call('InvokableClass@__invoke', ['Test']);
|
||||
var callResult = container.call('InvokableClass@call', ['Test']);
|
||||
expect(invokeResult, equals(callResult));
|
||||
});
|
||||
|
||||
test('can still call other methods', () {
|
||||
var result = container.call('InvokableClass@invoke', ['Hello world']);
|
||||
expect(result, equals('Called invoke with: Hello world'));
|
||||
});
|
||||
|
||||
test('throws when required parameter is missing', () {
|
||||
expect(() => container.call('InvokableClass@__invoke'),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
252
packages/container/container/test/make_with_test.dart
Normal file
252
packages/container/container/test/make_with_test.dart
Normal file
|
@ -0,0 +1,252 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
String get level;
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
final String _level;
|
||||
|
||||
ConsoleLogger(this._level);
|
||||
|
||||
@override
|
||||
void log(String message) => print('Console: $message');
|
||||
|
||||
@override
|
||||
String get level => _level;
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('MakeWith Tests', () {
|
||||
test('makeWith passes parameters to constructor', () {
|
||||
container.factory<Logger>(() =>
|
||||
ConsoleLogger(container.getParameterOverride('level') ?? 'info'));
|
||||
|
||||
var logger = container.makeWith<Logger>({'level': 'debug'});
|
||||
expect(logger.level, equals('debug'));
|
||||
});
|
||||
|
||||
test('makeWith works with type parameter', () {
|
||||
container.factory<Logger>(() =>
|
||||
ConsoleLogger(container.getParameterOverride('level') ?? 'info'));
|
||||
|
||||
var logger = container.makeWith<Logger>({'level': 'debug'}, Logger);
|
||||
expect(logger.level, equals('debug'));
|
||||
});
|
||||
|
||||
test('makeWith preserves parameters for nested dependencies', () {
|
||||
var level = '';
|
||||
container.factory<Logger>(() {
|
||||
level = container.getParameterOverride('level') ?? 'info';
|
||||
return ConsoleLogger(level);
|
||||
});
|
||||
|
||||
container.makeWith<Logger>({'level': 'debug'});
|
||||
expect(level, equals('debug'));
|
||||
});
|
||||
|
||||
test('makeWith throws when binding not found', () {
|
||||
expect(() => container.makeWith<Logger>({'level': 'debug'}),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('level', String, true, false),
|
||||
])
|
||||
],
|
||||
ConsoleLogger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
return reflectClass(type);
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) => [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) =>
|
||||
[];
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
var level = namedArguments['level'] ?? positionalArguments[0];
|
||||
return MockReflectedInstance(ConsoleLogger(level));
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
94
packages/container/container/test/method_binding_test.dart
Normal file
94
packages/container/container/test/method_binding_test.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
class Calculator {
|
||||
int add(int a, int b) => a + b;
|
||||
int multiply(int a, int b) => a * b;
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Method Binding Tests', () {
|
||||
test('can bind and call method', () {
|
||||
var calculator = Calculator();
|
||||
container.bindMethod('add', calculator.add);
|
||||
|
||||
var result = container.callMethod('add', [5, 3]);
|
||||
expect(result, equals(8));
|
||||
});
|
||||
|
||||
test('can bind multiple methods', () {
|
||||
var calculator = Calculator();
|
||||
container.bindMethod('add', calculator.add);
|
||||
container.bindMethod('multiply', calculator.multiply);
|
||||
|
||||
expect(container.callMethod('add', [5, 3]), equals(8));
|
||||
expect(container.callMethod('multiply', [5, 3]), equals(15));
|
||||
});
|
||||
|
||||
test('throws when method not found', () {
|
||||
expect(
|
||||
() => container.callMethod('nonexistent'),
|
||||
throwsA(isA<StateError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws when binding duplicate method', () {
|
||||
var calculator = Calculator();
|
||||
container.bindMethod('add', calculator.add);
|
||||
|
||||
expect(
|
||||
() => container.bindMethod('add', calculator.add),
|
||||
throwsA(isA<StateError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('child container inherits parent methods', () {
|
||||
var calculator = Calculator();
|
||||
container.bindMethod('add', calculator.add);
|
||||
|
||||
var childContainer = container.createChild();
|
||||
expect(childContainer.callMethod('add', [5, 3]), equals(8));
|
||||
});
|
||||
|
||||
test('child container can override parent methods', () {
|
||||
var calculator = Calculator();
|
||||
container.bindMethod('add', calculator.add);
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer.bindMethod('add', (a, b) => a * b); // Override to multiply
|
||||
|
||||
expect(
|
||||
container.callMethod('add', [5, 3]), equals(8)); // Parent unchanged
|
||||
expect(childContainer.callMethod('add', [5, 3]),
|
||||
equals(15)); // Child overridden
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Minimal mock reflector for method binding tests
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
214
packages/container/container/test/parameter_injection_test.dart
Normal file
214
packages/container/container/test/parameter_injection_test.dart
Normal file
|
@ -0,0 +1,214 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class Config {
|
||||
final String environment;
|
||||
Config(this.environment);
|
||||
}
|
||||
|
||||
class Logger {
|
||||
String log(String message) => message;
|
||||
void configure(Config config) {}
|
||||
String format(String message, {int? level}) => '$message (level: $level)';
|
||||
void setup(Config config, String name) {}
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) {
|
||||
if (name == 'Logger') return Logger;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
if (instance is Logger) {
|
||||
switch (methodName) {
|
||||
case 'log':
|
||||
return MockMethod('log', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
return MockReflectedInstance(instance.log(args[0] as String));
|
||||
}, [MockParameter('message', String, true, false)]);
|
||||
case 'configure':
|
||||
return MockMethod('configure', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
instance.configure(args[0] as Config);
|
||||
return MockReflectedInstance(null);
|
||||
}, [MockParameter('config', Config, true, false)]);
|
||||
case 'format':
|
||||
return MockMethod('format', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
var namedArgs = invocation.namedArguments;
|
||||
return MockReflectedInstance(instance.format(args[0] as String,
|
||||
level: namedArgs[#level] as int?));
|
||||
}, [
|
||||
MockParameter('message', String, true, false),
|
||||
MockParameter('level', int, false, true)
|
||||
]);
|
||||
case 'setup':
|
||||
return MockMethod('setup', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
instance.setup(args[0] as Config, args[1] as String);
|
||||
return MockReflectedInstance(null);
|
||||
}, [
|
||||
MockParameter('config', Config, true, false),
|
||||
MockParameter('name', String, true, false)
|
||||
]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMethod implements ReflectedFunction {
|
||||
final String methodName;
|
||||
final ReflectedInstance Function(Invocation) handler;
|
||||
final List<ReflectedParameter> methodParameters;
|
||||
|
||||
MockMethod(this.methodName, this.handler, this.methodParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => methodName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => methodParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final bool isVariadic;
|
||||
final Type paramType;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) => false;
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
container.registerSingleton(Logger());
|
||||
container.registerSingleton(Config('test'));
|
||||
});
|
||||
|
||||
group('Parameter Dependency Injection', () {
|
||||
test('can inject dependencies into method parameters', () {
|
||||
expect(() => container.call('Logger@configure'), returnsNormally);
|
||||
});
|
||||
|
||||
test('uses provided parameters over container bindings', () {
|
||||
var prodConfig = Config('production');
|
||||
container.call('Logger@configure', [prodConfig]);
|
||||
});
|
||||
|
||||
test('throws when required parameter is missing', () {
|
||||
expect(() => container.call('Logger@setup', [Config('test')]),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('handles mix of injected and provided parameters', () {
|
||||
// When null is provided for a parameter that can be resolved from container,
|
||||
// the container should resolve it
|
||||
container.call('Logger@setup', [null, 'test-logger']);
|
||||
});
|
||||
|
||||
test('handles optional parameters', () {
|
||||
var result = container.call('Logger@format', ['test message']);
|
||||
expect(result, equals('test message (level: null)'));
|
||||
});
|
||||
|
||||
test('handles optional parameters with provided values', () {
|
||||
var result =
|
||||
container.call('Logger@format', ['test message'], {#level: 1});
|
||||
expect(result, equals('test message (level: 1)'));
|
||||
});
|
||||
});
|
||||
}
|
109
packages/container/container/test/parameter_override_test.dart
Normal file
109
packages/container/container/test/parameter_override_test.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class FileLogger {
|
||||
final String filename;
|
||||
FileLogger(this.filename);
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Parameter Override Tests', () {
|
||||
test('can override constructor parameters', () {
|
||||
container.registerFactory<FileLogger>((c) {
|
||||
var filename =
|
||||
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||
return FileLogger(filename);
|
||||
});
|
||||
|
||||
var logger = container.withParameters(
|
||||
{'filename': 'custom.log'}, () => container.make<FileLogger>());
|
||||
|
||||
expect(logger.filename, equals('custom.log'));
|
||||
});
|
||||
|
||||
test('parameter overrides are scoped', () {
|
||||
container.registerFactory<FileLogger>((c) {
|
||||
var filename =
|
||||
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||
return FileLogger(filename);
|
||||
});
|
||||
|
||||
var customLogger = container.withParameters(
|
||||
{'filename': 'custom.log'}, () => container.make<FileLogger>());
|
||||
|
||||
var defaultLogger = container.make<FileLogger>();
|
||||
|
||||
expect(customLogger.filename, equals('custom.log'));
|
||||
expect(defaultLogger.filename, equals('default.log'));
|
||||
});
|
||||
|
||||
test('nested parameter overrides', () {
|
||||
container.registerFactory<FileLogger>((c) {
|
||||
var filename =
|
||||
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||
return FileLogger(filename);
|
||||
});
|
||||
|
||||
var logger = container.withParameters(
|
||||
{'filename': 'outer.log'},
|
||||
() => container.withParameters(
|
||||
{'filename': 'inner.log'}, () => container.make<FileLogger>()));
|
||||
|
||||
expect(logger.filename, equals('inner.log'));
|
||||
});
|
||||
|
||||
test('parameter overrides in child container', () {
|
||||
container.registerFactory<FileLogger>((c) {
|
||||
var filename =
|
||||
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||
return FileLogger(filename);
|
||||
});
|
||||
var child = container.createChild();
|
||||
|
||||
var logger = child.withParameters(
|
||||
{'filename': 'custom.log'}, () => child.make<FileLogger>());
|
||||
|
||||
expect(logger.filename, equals('custom.log'));
|
||||
});
|
||||
|
||||
test('parameter overrides with multiple parameters', () {
|
||||
container.registerFactory<FileLogger>((c) {
|
||||
var filename =
|
||||
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||
return FileLogger(filename);
|
||||
});
|
||||
|
||||
var logger = container.withParameters(
|
||||
{'filename': 'custom.log', 'level': 'debug', 'maxSize': 1024},
|
||||
() => container.make<FileLogger>());
|
||||
|
||||
expect(logger.filename, equals('custom.log'));
|
||||
});
|
||||
});
|
||||
}
|
111
packages/container/container/test/rebound_test.dart
Normal file
111
packages/container/container/test/rebound_test.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class Service {
|
||||
String value = 'initial';
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Rebound Tests', () {
|
||||
test('rebinding callback is called when singleton is refreshed', () {
|
||||
var callCount = 0;
|
||||
var lastInstance;
|
||||
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
callCount++;
|
||||
lastInstance = instance;
|
||||
});
|
||||
|
||||
var refreshed = container.refresh<Service>();
|
||||
expect(callCount, equals(1));
|
||||
expect(lastInstance, equals(refreshed));
|
||||
expect(container.make<Service>(), equals(refreshed));
|
||||
});
|
||||
|
||||
test('multiple rebound callbacks are called in order', () {
|
||||
var order = [];
|
||||
container.registerSingleton<Service>(Service());
|
||||
|
||||
container.rebinding<Service>((instance, container) {
|
||||
order.add(1);
|
||||
});
|
||||
|
||||
container.rebinding<Service>((instance, container) {
|
||||
order.add(2);
|
||||
});
|
||||
|
||||
container.refresh<Service>();
|
||||
expect(order, equals([1, 2]));
|
||||
});
|
||||
|
||||
test('child container inherits parent rebound callbacks', () {
|
||||
var parentCallCount = 0;
|
||||
var childCallCount = 0;
|
||||
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
parentCallCount++;
|
||||
});
|
||||
|
||||
var child = container.createChild();
|
||||
child.rebinding<Service>((instance, container) {
|
||||
childCallCount++;
|
||||
});
|
||||
|
||||
child.refresh<Service>();
|
||||
expect(parentCallCount, equals(1));
|
||||
expect(childCallCount, equals(1));
|
||||
});
|
||||
|
||||
test('refresh throws on circular dependency', () {
|
||||
container.registerSingleton<Service>(Service());
|
||||
container.rebinding<Service>((instance, container) {
|
||||
container.refresh<Service>();
|
||||
});
|
||||
|
||||
expect(() => container.refresh<Service>(),
|
||||
throwsA(isA<CircularDependencyException>()));
|
||||
});
|
||||
|
||||
test('refresh creates new instance for factory binding', () {
|
||||
var count = 0;
|
||||
container.registerFactory<Service>((c) {
|
||||
count++;
|
||||
return Service();
|
||||
});
|
||||
|
||||
var first = container.make<Service>();
|
||||
var second = container.refresh<Service>();
|
||||
|
||||
expect(count, equals(2));
|
||||
expect(first, isNot(equals(second)));
|
||||
});
|
||||
});
|
||||
}
|
322
packages/container/container/test/scoped_test.dart
Normal file
322
packages/container/container/test/scoped_test.dart
Normal file
|
@ -0,0 +1,322 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
class RequestScope {
|
||||
final String id;
|
||||
RequestScope(this.id);
|
||||
}
|
||||
|
||||
class UserService {
|
||||
final RequestScope scope;
|
||||
UserService(this.scope);
|
||||
}
|
||||
|
||||
class OrderService {
|
||||
final RequestScope scope;
|
||||
OrderService(this.scope);
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Scoped Instance Tests', () {
|
||||
test('scoped instances are shared within scope', () {
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-1'));
|
||||
container.registerFactory<UserService>(
|
||||
(c) => UserService(c.make<RequestScope>()));
|
||||
container.registerFactory<OrderService>(
|
||||
(c) => OrderService(c.make<RequestScope>()));
|
||||
|
||||
var userService = container.make<UserService>();
|
||||
var orderService = container.make<OrderService>();
|
||||
|
||||
expect(userService.scope, same(orderService.scope));
|
||||
expect(userService.scope.id, equals('request-1'));
|
||||
});
|
||||
|
||||
test('scoped instances are cleared after clearScoped', () {
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-1'));
|
||||
var scope1 = container.make<RequestScope>();
|
||||
|
||||
container.clearScoped();
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-2'));
|
||||
var scope2 = container.make<RequestScope>();
|
||||
|
||||
expect(scope1.id, equals('request-1'));
|
||||
expect(scope2.id, equals('request-2'));
|
||||
expect(scope1, isNot(same(scope2)));
|
||||
});
|
||||
|
||||
test('child container inherits parent scoped instances', () {
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-1'));
|
||||
var childContainer = container.createChild();
|
||||
|
||||
var parentScope = container.make<RequestScope>();
|
||||
var childScope = childContainer.make<RequestScope>();
|
||||
|
||||
expect(parentScope, same(childScope));
|
||||
});
|
||||
|
||||
test('child container can override parent scoped instances', () {
|
||||
container.scoped<RequestScope>((c) => RequestScope('parent-request'));
|
||||
var childContainer = container.createChild();
|
||||
childContainer.scoped<RequestScope>((c) => RequestScope('child-request'));
|
||||
|
||||
var parentScope = container.make<RequestScope>();
|
||||
var childScope = childContainer.make<RequestScope>();
|
||||
|
||||
expect(parentScope.id, equals('parent-request'));
|
||||
expect(childScope.id, equals('child-request'));
|
||||
expect(parentScope, isNot(same(childScope)));
|
||||
});
|
||||
|
||||
test('clearing parent scoped instances affects child containers', () {
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-1'));
|
||||
var childContainer = container.createChild();
|
||||
|
||||
var beforeClear = childContainer.make<RequestScope>();
|
||||
container.clearScoped();
|
||||
container.scoped<RequestScope>((c) => RequestScope('request-2'));
|
||||
var afterClear = childContainer.make<RequestScope>();
|
||||
|
||||
expect(beforeClear.id, equals('request-1'));
|
||||
expect(afterClear.id, equals('request-2'));
|
||||
expect(beforeClear, isNot(same(afterClear)));
|
||||
});
|
||||
});
|
||||
|
||||
group('Resolution Lifecycle Tests', () {
|
||||
test('before resolving callbacks are called', () {
|
||||
var callLog = <String>[];
|
||||
container.beforeResolving<RequestScope>((type, args, container) {
|
||||
callLog.add('before:${type.toString()}');
|
||||
});
|
||||
|
||||
container.registerSingleton(RequestScope('test'));
|
||||
container.make<RequestScope>();
|
||||
|
||||
expect(callLog, contains('before:RequestScope'));
|
||||
});
|
||||
|
||||
test('resolving callbacks are called', () {
|
||||
var callLog = <String>[];
|
||||
container.resolving<RequestScope>((instance, container) {
|
||||
callLog.add('resolving:${(instance as RequestScope).id}');
|
||||
});
|
||||
|
||||
container.registerSingleton(RequestScope('test'));
|
||||
container.make<RequestScope>();
|
||||
|
||||
expect(callLog, contains('resolving:test'));
|
||||
});
|
||||
|
||||
test('after resolving callbacks are called', () {
|
||||
var callLog = <String>[];
|
||||
container.afterResolving<RequestScope>((instance, container) {
|
||||
callLog.add('after:${(instance as RequestScope).id}');
|
||||
});
|
||||
|
||||
container.registerSingleton(RequestScope('test'));
|
||||
container.make<RequestScope>();
|
||||
|
||||
expect(callLog, contains('after:test'));
|
||||
});
|
||||
|
||||
test('callbacks are called in correct order', () {
|
||||
var callOrder = <String>[];
|
||||
|
||||
container.beforeResolving<RequestScope>((type, args, container) {
|
||||
callOrder.add('before');
|
||||
});
|
||||
|
||||
container.resolving<RequestScope>((instance, container) {
|
||||
callOrder.add('resolving');
|
||||
});
|
||||
|
||||
container.afterResolving<RequestScope>((instance, container) {
|
||||
callOrder.add('after');
|
||||
});
|
||||
|
||||
container.registerSingleton(RequestScope('test'));
|
||||
container.make<RequestScope>();
|
||||
|
||||
expect(callOrder, orderedEquals(['before', 'resolving', 'after']));
|
||||
});
|
||||
|
||||
test('child container inherits parent callbacks', () {
|
||||
var callLog = <String>[];
|
||||
container.beforeResolving<RequestScope>((type, args, container) {
|
||||
callLog.add('parent-before');
|
||||
});
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer.registerSingleton(RequestScope('test'));
|
||||
childContainer.make<RequestScope>();
|
||||
|
||||
expect(callLog, contains('parent-before'));
|
||||
});
|
||||
|
||||
test('child container can add its own callbacks', () {
|
||||
var callLog = <String>[];
|
||||
|
||||
container.beforeResolving<RequestScope>((type, args, container) {
|
||||
callLog.add('parent-before');
|
||||
});
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer.beforeResolving<RequestScope>((type, args, container) {
|
||||
callLog.add('child-before');
|
||||
});
|
||||
|
||||
childContainer.registerSingleton(RequestScope('test'));
|
||||
childContainer.make<RequestScope>();
|
||||
|
||||
expect(callLog, containsAll(['parent-before', 'child-before']));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Minimal mock reflector for scoped and lifecycle tests
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == RequestScope) {
|
||||
return MockReflectedClass(
|
||||
'RequestScope',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('id', String)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => RequestScope(positional[0]),
|
||||
);
|
||||
} else if (type == UserService) {
|
||||
return MockReflectedClass(
|
||||
'UserService',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('scope', RequestScope)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => UserService(positional[0]),
|
||||
);
|
||||
} else if (type == OrderService) {
|
||||
return MockReflectedClass(
|
||||
'OrderService',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor([MockParameter('scope', RequestScope)])
|
||||
],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => OrderService(positional[0]),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedClass {
|
||||
final Function instanceBuilder;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
List<ReflectedInstance> annotations,
|
||||
List<ReflectedFunction> constructors,
|
||||
List<ReflectedDeclaration> declarations,
|
||||
Type reflectedType,
|
||||
this.instanceBuilder,
|
||||
) : super(name, typeParameters, annotations, constructors, declarations,
|
||||
reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
var instance = instanceBuilder(
|
||||
constructorName, positionalArguments, namedArguments, typeArguments);
|
||||
return MockReflectedInstance(this, instance);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance extends ReflectedInstance {
|
||||
MockReflectedInstance(ReflectedClass clazz, Object? reflectee)
|
||||
: super(clazz, clazz, reflectee);
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor extends ReflectedFunction {
|
||||
final List<ReflectedParameter> params;
|
||||
|
||||
MockConstructor(this.params)
|
||||
: super('', [], [], params, false, false,
|
||||
returnType: MockReflectedType('void', [], dynamic));
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockParameter extends ReflectedParameter {
|
||||
MockParameter(String name, Type type)
|
||||
: super(name, [], MockReflectedType(type.toString(), [], type), true,
|
||||
false);
|
||||
}
|
||||
|
||||
class MockReflectedType extends ReflectedType {
|
||||
MockReflectedType(String name, List<ReflectedTypeParameter> typeParameters,
|
||||
Type reflectedType)
|
||||
: super(name, typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
241
packages/container/container/test/tag_test.dart
Normal file
241
packages/container/container/test/tag_test.dart
Normal file
|
@ -0,0 +1,241 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
// Test interfaces and implementations
|
||||
abstract class Repository {
|
||||
String getName();
|
||||
}
|
||||
|
||||
class UserRepository implements Repository {
|
||||
@override
|
||||
String getName() => 'users';
|
||||
}
|
||||
|
||||
class ProductRepository implements Repository {
|
||||
@override
|
||||
String getName() => 'products';
|
||||
}
|
||||
|
||||
class OrderRepository implements Repository {
|
||||
@override
|
||||
String getName() => 'orders';
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Tag Tests', () {
|
||||
test('can tag and resolve multiple bindings', () {
|
||||
container.registerSingleton<Repository>(UserRepository(),
|
||||
as: UserRepository);
|
||||
container.registerSingleton<Repository>(ProductRepository(),
|
||||
as: ProductRepository);
|
||||
container.registerSingleton<Repository>(OrderRepository(),
|
||||
as: OrderRepository);
|
||||
|
||||
container.tag([UserRepository, ProductRepository], 'basic');
|
||||
container.tag([OrderRepository], 'advanced');
|
||||
container.tag([UserRepository, OrderRepository], 'critical');
|
||||
|
||||
var basicRepos = container.tagged('basic');
|
||||
expect(basicRepos, hasLength(2));
|
||||
expect(basicRepos.map((r) => r.getName()),
|
||||
containsAll(['users', 'products']));
|
||||
|
||||
var advancedRepos = container.tagged('advanced');
|
||||
expect(advancedRepos, hasLength(1));
|
||||
expect(advancedRepos.first.getName(), equals('orders'));
|
||||
|
||||
var criticalRepos = container.tagged('critical');
|
||||
expect(criticalRepos, hasLength(2));
|
||||
expect(criticalRepos.map((r) => r.getName()),
|
||||
containsAll(['users', 'orders']));
|
||||
});
|
||||
|
||||
test('can tag same binding with multiple tags', () {
|
||||
container.registerSingleton<Repository>(UserRepository(),
|
||||
as: UserRepository);
|
||||
|
||||
container.tag([UserRepository], 'tag1');
|
||||
container.tag([UserRepository], 'tag2');
|
||||
|
||||
expect(container.tagged('tag1'), hasLength(1));
|
||||
expect(container.tagged('tag2'), hasLength(1));
|
||||
expect(container.tagged('tag1').first, isA<UserRepository>());
|
||||
expect(container.tagged('tag2').first, isA<UserRepository>());
|
||||
});
|
||||
|
||||
test('returns empty list for unknown tag', () {
|
||||
var repos = container.tagged('nonexistent');
|
||||
expect(repos, isEmpty);
|
||||
});
|
||||
|
||||
test('child container inherits parent tags', () {
|
||||
container.registerSingleton<Repository>(UserRepository(),
|
||||
as: UserRepository);
|
||||
container.registerSingleton<Repository>(ProductRepository(),
|
||||
as: ProductRepository);
|
||||
container.tag([UserRepository, ProductRepository], 'basic');
|
||||
|
||||
var childContainer = container.createChild();
|
||||
var basicRepos = childContainer.tagged('basic');
|
||||
expect(basicRepos, hasLength(2));
|
||||
expect(basicRepos.map((r) => r.getName()),
|
||||
containsAll(['users', 'products']));
|
||||
});
|
||||
|
||||
test('child container can add new tags', () {
|
||||
container.registerSingleton<Repository>(UserRepository(),
|
||||
as: UserRepository);
|
||||
container.tag([UserRepository], 'parent-tag');
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer.registerSingleton<Repository>(ProductRepository(),
|
||||
as: ProductRepository);
|
||||
childContainer.tag([ProductRepository], 'child-tag');
|
||||
|
||||
expect(childContainer.tagged('parent-tag'), hasLength(1));
|
||||
expect(childContainer.tagged('child-tag'), hasLength(1));
|
||||
expect(childContainer.tagged('parent-tag').first, isA<UserRepository>());
|
||||
expect(
|
||||
childContainer.tagged('child-tag').first, isA<ProductRepository>());
|
||||
});
|
||||
|
||||
test('child container can extend parent tags', () {
|
||||
container.registerSingleton<Repository>(UserRepository(),
|
||||
as: UserRepository);
|
||||
container.tag([UserRepository], 'repositories');
|
||||
|
||||
var childContainer = container.createChild();
|
||||
childContainer.registerSingleton<Repository>(ProductRepository(),
|
||||
as: ProductRepository);
|
||||
childContainer.tag([ProductRepository], 'repositories');
|
||||
|
||||
var repos = childContainer.tagged('repositories');
|
||||
expect(repos, hasLength(2));
|
||||
expect(repos.map((r) => r.getName()), containsAll(['users', 'products']));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Minimal mock reflector for tag tests
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == UserRepository ||
|
||||
type == ProductRepository ||
|
||||
type == OrderRepository) {
|
||||
return MockReflectedClass(
|
||||
type.toString(),
|
||||
[],
|
||||
[],
|
||||
[MockConstructor([])],
|
||||
[],
|
||||
type,
|
||||
(name, positional, named, typeArgs) => _createInstance(type),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
dynamic _createInstance(Type type) {
|
||||
if (type == UserRepository) return UserRepository();
|
||||
if (type == ProductRepository) return ProductRepository();
|
||||
if (type == OrderRepository) return OrderRepository();
|
||||
throw StateError('Unknown type: $type');
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedClass {
|
||||
final Function instanceBuilder;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
List<ReflectedInstance> annotations,
|
||||
List<ReflectedFunction> constructors,
|
||||
List<ReflectedDeclaration> declarations,
|
||||
Type reflectedType,
|
||||
this.instanceBuilder,
|
||||
) : super(name, typeParameters, annotations, constructors, declarations,
|
||||
reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
var instance = instanceBuilder(
|
||||
constructorName, positionalArguments, namedArguments, typeArguments);
|
||||
return MockReflectedInstance(this, instance);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedInstance extends ReflectedInstance {
|
||||
MockReflectedInstance(ReflectedClass clazz, Object? reflectee)
|
||||
: super(clazz, clazz, reflectee);
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor extends ReflectedFunction {
|
||||
final List<ReflectedParameter> params;
|
||||
|
||||
MockConstructor(this.params)
|
||||
: super('', [], [], params, false, false,
|
||||
returnType: MockReflectedType('void', [], dynamic));
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedType extends ReflectedType {
|
||||
MockReflectedType(String name, List<ReflectedTypeParameter> typeParameters,
|
||||
Type reflectedType)
|
||||
: super(name, typeParameters, reflectedType);
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (other == null) return false;
|
||||
return reflectedType == other.reflectedType;
|
||||
}
|
||||
}
|
353
packages/container/container/test/tagged_config_test.dart
Normal file
353
packages/container/container/test/tagged_config_test.dart
Normal file
|
@ -0,0 +1,353 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
final String level;
|
||||
ConsoleLogger({this.level = 'info'});
|
||||
@override
|
||||
void log(String message) => print('Console($level): $message');
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
final String filename;
|
||||
FileLogger({required this.filename});
|
||||
@override
|
||||
void log(String message) => print('File($filename): $message');
|
||||
}
|
||||
|
||||
class Service {
|
||||
final Logger logger;
|
||||
Service(this.logger);
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('logger', Logger, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
if (type == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('level', String, false, true),
|
||||
])
|
||||
],
|
||||
ConsoleLogger);
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('filename', String, true, true),
|
||||
])
|
||||
],
|
||||
FileLogger);
|
||||
}
|
||||
if (type == Logger) {
|
||||
return MockReflectedClass(
|
||||
'Logger', [], [], [MockConstructor('', [])], Logger);
|
||||
}
|
||||
if (type == String) {
|
||||
return MockReflectedClass(
|
||||
'String', [], [], [MockConstructor('', [])], String);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
void _validateParameters(List<ReflectedParameter> parameters,
|
||||
List positionalArguments, Map<String, dynamic> namedArguments) {
|
||||
var paramIndex = 0;
|
||||
for (var param in parameters) {
|
||||
if (param.isNamed) {
|
||||
if (param.isRequired && !namedArguments.containsKey(param.name)) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
} else {
|
||||
if (param.isRequired && paramIndex >= positionalArguments.length) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
// Find constructor
|
||||
var constructor = constructors.firstWhere((c) => c.name == constructorName,
|
||||
orElse: () => constructors.first);
|
||||
|
||||
// Validate parameters
|
||||
_validateParameters(
|
||||
constructor.parameters, positionalArguments, namedArguments);
|
||||
|
||||
if (reflectedType == Service) {
|
||||
return MockReflectedInstance(Service(positionalArguments[0] as Logger));
|
||||
}
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(
|
||||
ConsoleLogger(level: namedArguments['level'] as String? ?? 'info'));
|
||||
}
|
||||
if (reflectedType == FileLogger) {
|
||||
return MockReflectedInstance(
|
||||
FileLogger(filename: namedArguments['filename'] as String));
|
||||
}
|
||||
if (reflectedType == Logger) {
|
||||
throw BindingResolutionException(
|
||||
'No implementation was provided for Logger');
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == FileLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
// Handle primitive types
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
});
|
||||
|
||||
group('Tagged and Config Tests', () {
|
||||
test('can bind implementation from tagged type', () {
|
||||
container.tag([ConsoleLogger], 'loggers');
|
||||
container.when([Service]).needs<Logger>().giveTagged('loggers');
|
||||
|
||||
var service = container.make<Service>();
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('throws when tag has no implementations', () {
|
||||
container.when([Service]).needs<Logger>().giveTagged('loggers');
|
||||
|
||||
expect(
|
||||
() => container.make<Service>(),
|
||||
throwsA(predicate((e) =>
|
||||
e is BindingResolutionException &&
|
||||
e.toString().contains('No implementations found for tag'))));
|
||||
});
|
||||
|
||||
test('can bind implementation with config', () {
|
||||
container
|
||||
.when([Service])
|
||||
.needs<Logger>()
|
||||
.giveConfig(ConsoleLogger, {'level': 'debug'});
|
||||
|
||||
var service = container.make<Service>();
|
||||
expect(service.logger, isA<ConsoleLogger>());
|
||||
expect((service.logger as ConsoleLogger).level, equals('debug'));
|
||||
});
|
||||
|
||||
test('throws when required config is missing', () {
|
||||
container.when([Service]).needs<Logger>().giveConfig(FileLogger, {});
|
||||
|
||||
expect(
|
||||
() => container.make<Service>(),
|
||||
throwsA(predicate((e) =>
|
||||
e is BindingResolutionException &&
|
||||
e.toString().contains(
|
||||
'Required parameter filename is missing for FileLogger'))));
|
||||
});
|
||||
|
||||
test('can mix tagged and config bindings', () {
|
||||
container.tag([ConsoleLogger], 'console');
|
||||
container.tag([FileLogger], 'file');
|
||||
|
||||
container.when([Service]).needs<Logger>().giveTagged('console');
|
||||
container
|
||||
.when([Service])
|
||||
.needs<Logger>()
|
||||
.giveConfig(FileLogger, {'filename': 'app.log'});
|
||||
|
||||
var service = container.make<Service>();
|
||||
expect(service.logger, isA<FileLogger>());
|
||||
expect((service.logger as FileLogger).filename, equals('app.log'));
|
||||
});
|
||||
});
|
||||
}
|
223
packages/container/container/test/variadic_parameter_test.dart
Normal file
223
packages/container/container/test/variadic_parameter_test.dart
Normal file
|
@ -0,0 +1,223 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class Logger {
|
||||
String log(String prefix, String message) => '$prefix: $message';
|
||||
String logMany(String prefix, List<String> messages) =>
|
||||
'$prefix: ${messages.join(", ")}';
|
||||
String format(String message, {List<String> tags = const []}) =>
|
||||
'$message [${tags.join(", ")}]';
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) {
|
||||
if (name == 'Logger') return Logger;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
if (instance is Logger) {
|
||||
switch (methodName) {
|
||||
case 'log':
|
||||
return MockMethod('log', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.length < 2) {
|
||||
throw ArgumentError(
|
||||
'Method log requires prefix and message parameters');
|
||||
}
|
||||
return MockReflectedInstance(
|
||||
instance.log(args[0] as String, args[1] as String));
|
||||
}, [
|
||||
MockParameter('prefix', String, true, false),
|
||||
MockParameter('message', String, true, false)
|
||||
]);
|
||||
case 'logMany':
|
||||
return MockMethod('logMany', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method logMany requires a prefix parameter');
|
||||
}
|
||||
var prefix = args[0] as String;
|
||||
var messages = args.length > 1
|
||||
? args.skip(1).map((e) => e.toString()).toList()
|
||||
: <String>[];
|
||||
return MockReflectedInstance(instance.logMany(prefix, messages));
|
||||
}, [
|
||||
MockParameter('prefix', String, true, false),
|
||||
MockParameter('messages', List<String>, true, false,
|
||||
isVariadic: true)
|
||||
]);
|
||||
case 'format':
|
||||
return MockMethod('format', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
var namedArgs = invocation.namedArguments;
|
||||
if (args.isEmpty) {
|
||||
throw ArgumentError('Method format requires a message parameter');
|
||||
}
|
||||
var tags = (namedArgs[#tags] as List?)
|
||||
?.map((e) => e.toString())
|
||||
.toList() ??
|
||||
const <String>[];
|
||||
return MockReflectedInstance(
|
||||
instance.format(args[0] as String, tags: tags));
|
||||
}, [
|
||||
MockParameter('message', String, true, false),
|
||||
MockParameter('tags', List<String>, false, true, isVariadic: true)
|
||||
]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMethod implements ReflectedFunction {
|
||||
final String methodName;
|
||||
final ReflectedInstance Function(Invocation) handler;
|
||||
final List<ReflectedParameter> _parameters;
|
||||
|
||||
MockMethod(this.methodName, this.handler,
|
||||
[List<ReflectedParameter>? parameters])
|
||||
: _parameters = parameters ?? [];
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => methodName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => _parameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) => false;
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
container.registerSingleton(Logger());
|
||||
});
|
||||
|
||||
group('Variadic Parameter Tests', () {
|
||||
test('can call method with variadic positional parameters', () {
|
||||
var result = container.call('Logger@logMany',
|
||||
['INFO', 'first message', 'second message', 'third message']);
|
||||
expect(result,
|
||||
equals('INFO: [first message, second message, third message]'));
|
||||
});
|
||||
|
||||
test('can call method with variadic named parameters', () {
|
||||
var result = container.call('Logger@format', [
|
||||
'Hello world'
|
||||
], {
|
||||
#tags: ['info', 'debug', 'test']
|
||||
});
|
||||
expect(result, equals('Hello world [info, debug, test]'));
|
||||
});
|
||||
|
||||
test('variadic parameters are optional', () {
|
||||
var result = container.call('Logger@format', ['Hello world']);
|
||||
expect(result, equals('Hello world []'));
|
||||
});
|
||||
|
||||
test('can mix regular and variadic parameters', () {
|
||||
var result =
|
||||
container.call('Logger@logMany', ['DEBUG', 'single message']);
|
||||
expect(result, equals('DEBUG: [single message]'));
|
||||
});
|
||||
});
|
||||
}
|
380
packages/pipeline/README.md
Normal file
380
packages/pipeline/README.md
Normal file
|
@ -0,0 +1,380 @@
|
|||
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
||||
|
||||
# Platform Pipeline
|
||||
|
||||
A Laravel-compatible pipeline implementation in Dart, providing a robust way to pass objects through a series of operations.
|
||||
|
||||
[]()
|
||||
[]()
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Class-Based Pipes](#class-based-pipes)
|
||||
- [Invokable Classes](#invokable-classes)
|
||||
- [Using Different Method Names](#using-different-method-names)
|
||||
- [Passing Parameters to Pipes](#passing-parameters-to-pipes)
|
||||
- [Early Pipeline Termination](#early-pipeline-termination)
|
||||
- [Conditional Pipeline Execution](#conditional-pipeline-execution)
|
||||
- [Advanced Usage](#advanced-usage)
|
||||
- [Working with Objects](#working-with-objects)
|
||||
- [Async Operations](#async-operations)
|
||||
- [Laravel API Compatibility](#laravel-api-compatibility)
|
||||
- [Comparison with Laravel](#comparison-with-laravel)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Testing](#testing)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Overview
|
||||
|
||||
Platform Pipeline is a 100% API-compatible port of Laravel's Pipeline to Dart. It allows you to pass an object through a series of operations (pipes) in a fluent, maintainable way. Each pipe can examine, modify, or replace the object before passing it to the next pipe in the sequence.
|
||||
|
||||
## Features
|
||||
|
||||
- 💯 100% Laravel Pipeline API compatibility
|
||||
- 🔄 Support for class-based and callable pipes
|
||||
- 🎯 Dependency injection through container integration
|
||||
- ⚡ Async operation support
|
||||
- 🔀 Conditional pipeline execution
|
||||
- 🎭 Method name customization via `via()`
|
||||
- 🎁 Parameter passing to pipes
|
||||
- 🛑 Early pipeline termination
|
||||
- 🧪 Comprehensive test coverage
|
||||
|
||||
## Requirements
|
||||
|
||||
- Dart SDK: >=2.17.0 <4.0.0
|
||||
- platform_container: ^1.0.0
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your package's `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
platform_pipeline: ^1.0.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```dart
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
import 'package:platform_container/container.dart';
|
||||
|
||||
void main() async {
|
||||
// Create a container instance
|
||||
var container = Container();
|
||||
|
||||
// Create a pipeline
|
||||
var result = await Pipeline(container)
|
||||
.send('Hello')
|
||||
.through([
|
||||
(String value, next) => next(value + ' World'),
|
||||
(String value, next) => next(value + '!'),
|
||||
])
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: Hello World!
|
||||
}
|
||||
```
|
||||
|
||||
### Class-Based Pipes
|
||||
|
||||
```dart
|
||||
class UppercasePipe {
|
||||
Future<String> handle(String value, Function next) async {
|
||||
return next(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
class AddExclamationPipe {
|
||||
Future<String> handle(String value, Function next) async {
|
||||
return next(value + '!');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.through([
|
||||
UppercasePipe(),
|
||||
AddExclamationPipe(),
|
||||
])
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: HELLO!
|
||||
}
|
||||
```
|
||||
|
||||
### Invokable Classes
|
||||
|
||||
```dart
|
||||
class TransformPipe {
|
||||
Future<String> call(String value, Function next) async {
|
||||
return next(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.through([TransformPipe()])
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: HELLO
|
||||
}
|
||||
```
|
||||
|
||||
### Using Different Method Names
|
||||
|
||||
```dart
|
||||
class CustomPipe {
|
||||
Future<String> transform(String value, Function next) async {
|
||||
return next(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.through([CustomPipe()])
|
||||
.via('transform')
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: HELLO
|
||||
}
|
||||
```
|
||||
|
||||
### Passing Parameters to Pipes
|
||||
|
||||
```dart
|
||||
class PrefixPipe {
|
||||
Future<String> handle(
|
||||
String value,
|
||||
Function next, [
|
||||
String prefix = '',
|
||||
]) async {
|
||||
return next('$prefix$value');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
container.registerFactory<PrefixPipe>((c) => PrefixPipe());
|
||||
|
||||
var pipeline = Pipeline(container);
|
||||
pipeline.registerPipeType('PrefixPipe', PrefixPipe);
|
||||
|
||||
var result = await pipeline
|
||||
.send('World')
|
||||
.through('PrefixPipe:Hello ')
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: Hello World
|
||||
}
|
||||
```
|
||||
|
||||
### Early Pipeline Termination
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.through([
|
||||
(value, next) => 'TERMINATED', // Pipeline stops here
|
||||
(value, next) => next('NEVER REACHED'),
|
||||
])
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: TERMINATED
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Pipeline Execution
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
var container = Container();
|
||||
var shouldTransform = true;
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.when(() => shouldTransform, (Pipeline pipeline) {
|
||||
pipeline.pipe([
|
||||
(value, next) => next(value.toUpperCase()),
|
||||
]);
|
||||
})
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs: HELLO
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Working with Objects
|
||||
|
||||
```dart
|
||||
class User {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
User(this.name, this.age);
|
||||
}
|
||||
|
||||
class AgeValidationPipe {
|
||||
Future<User> handle(User user, Function next) async {
|
||||
if (user.age < 18) {
|
||||
throw Exception('User must be 18 or older');
|
||||
}
|
||||
return next(user);
|
||||
}
|
||||
}
|
||||
|
||||
class NameFormattingPipe {
|
||||
Future<User> handle(User user, Function next) async {
|
||||
user.name = user.name.trim().toLowerCase();
|
||||
return next(user);
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var user = User('John Doe ', 20);
|
||||
|
||||
try {
|
||||
user = await Pipeline(container)
|
||||
.send(user)
|
||||
.through([
|
||||
AgeValidationPipe(),
|
||||
NameFormattingPipe(),
|
||||
])
|
||||
.then((value) => value);
|
||||
|
||||
print('${user.name} is ${user.age} years old');
|
||||
// Outputs: john doe is 20 years old
|
||||
} catch (e) {
|
||||
print('Validation failed: $e');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Async Operations
|
||||
|
||||
```dart
|
||||
class AsyncTransformPipe {
|
||||
Future<String> handle(String value, Function next) async {
|
||||
// Simulate async operation
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
return next(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var container = Container();
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('hello')
|
||||
.through([AsyncTransformPipe()])
|
||||
.then((value) => value);
|
||||
|
||||
print(result); // Outputs after 1 second: HELLO
|
||||
}
|
||||
```
|
||||
|
||||
## Laravel API Compatibility
|
||||
|
||||
This package maintains 100% API compatibility with Laravel's Pipeline implementation. All Laravel Pipeline features are supported:
|
||||
|
||||
- `send()` - Set the object being passed through the pipeline
|
||||
- `through()` - Set the array of pipes
|
||||
- `pipe()` - Push additional pipes onto the pipeline
|
||||
- `via()` - Set the method to call on the pipes
|
||||
- `then()` - Run the pipeline with a final destination callback
|
||||
- `thenReturn()` - Run the pipeline and return the result
|
||||
|
||||
## Comparison with Laravel
|
||||
|
||||
| Feature | Laravel | Platform Pipeline |
|
||||
|---------|---------|------------------|
|
||||
| API Methods | ✓ | ✓ |
|
||||
| Container Integration | ✓ | ✓ |
|
||||
| Pipe Types | Class, Callable | Class, Callable |
|
||||
| Async Support | ✗ | ✓ |
|
||||
| Type Safety | ✗ | ✓ |
|
||||
| Parameter Passing | ✓ | ✓ |
|
||||
| Early Termination | ✓ | ✓ |
|
||||
| Method Customization | ✓ | ✓ |
|
||||
| Conditional Execution | ✓ | ✓ |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. Container Not Provided
|
||||
```dart
|
||||
// ❌ Wrong
|
||||
var pipeline = Pipeline(null);
|
||||
|
||||
// ✓ Correct
|
||||
var container = Container();
|
||||
var pipeline = Pipeline(container);
|
||||
```
|
||||
|
||||
2. Missing Type Registration
|
||||
```dart
|
||||
// ❌ Wrong
|
||||
pipeline.through('CustomPipe:param');
|
||||
|
||||
// ✓ Correct
|
||||
pipeline.registerPipeType('CustomPipe', CustomPipe);
|
||||
pipeline.through('CustomPipe:param');
|
||||
```
|
||||
|
||||
3. Incorrect Method Name
|
||||
```dart
|
||||
// ❌ Wrong
|
||||
class CustomPipe {
|
||||
void process(value, next) {} // Wrong method name
|
||||
}
|
||||
|
||||
// ✓ Correct
|
||||
class CustomPipe {
|
||||
void handle(value, next) {} // Default method name
|
||||
}
|
||||
// Or specify the method name:
|
||||
pipeline.via('process').through([CustomPipe()]);
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the tests with:
|
||||
|
||||
```bash
|
||||
dart test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## License
|
||||
|
||||
This package is open-sourced software licensed under the MIT license.
|
38
packages/pipeline/examples/async_pipeline.dart
Normal file
38
packages/pipeline/examples/async_pipeline.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
|
||||
class AsyncGreetingPipe {
|
||||
Future<dynamic> handle(String input, Function next) async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
return next('Hello, $input');
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncExclamationPipe {
|
||||
Future<dynamic> handle(String input, Function next) async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
return next('$input!');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var app = Application(reflector: MirrorsReflector());
|
||||
var http = PlatformHttp(app);
|
||||
|
||||
app.container.registerSingleton((c) => Pipeline(c));
|
||||
|
||||
app.get('/', (req, res) async {
|
||||
var pipeline = app.container.make<Pipeline>();
|
||||
var result = await pipeline
|
||||
.send('World')
|
||||
.through(['AsyncGreetingPipe', 'AsyncExclamationPipe']).then(
|
||||
(result) => result.toUpperCase());
|
||||
|
||||
res.write(result); // Outputs: "HELLO, WORLD!" (after 2 seconds)
|
||||
});
|
||||
|
||||
await http.startServer('localhost', 3000);
|
||||
print('Server started on http://localhost:3000');
|
||||
}
|
36
packages/pipeline/examples/basic_usage.dart
Normal file
36
packages/pipeline/examples/basic_usage.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
|
||||
class GreetingPipe {
|
||||
dynamic handle(String input, Function next) {
|
||||
return next('Hello, $input');
|
||||
}
|
||||
}
|
||||
|
||||
class ExclamationPipe {
|
||||
dynamic handle(String input, Function next) {
|
||||
return next('$input!');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var app = Application(reflector: MirrorsReflector());
|
||||
var http = PlatformHttp(app);
|
||||
|
||||
app.container.registerSingleton((c) => Pipeline(c));
|
||||
|
||||
app.get('/', (req, res) async {
|
||||
var pipeline = app.container.make<Pipeline>();
|
||||
var result = await pipeline
|
||||
.send('World')
|
||||
.through(['GreetingPipe', 'ExclamationPipe']).then(
|
||||
(result) => result.toUpperCase());
|
||||
|
||||
res.write(result); // Outputs: "HELLO, WORLD!"
|
||||
});
|
||||
|
||||
await http.startServer('localhost', 3000);
|
||||
print('Server started on http://localhost:3000');
|
||||
}
|
34
packages/pipeline/examples/error_handling.dart
Normal file
34
packages/pipeline/examples/error_handling.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
|
||||
class ErrorPipe {
|
||||
dynamic handle(String input, Function next) {
|
||||
throw Exception('Simulated error');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var app = Application(reflector: MirrorsReflector());
|
||||
var http = PlatformHttp(app);
|
||||
|
||||
app.container.registerSingleton((c) => Pipeline(c));
|
||||
|
||||
app.get('/', (req, res) async {
|
||||
var pipeline = app.container.make<Pipeline>();
|
||||
try {
|
||||
await pipeline
|
||||
.send('World')
|
||||
.through(['ErrorPipe']).then((result) => result.toUpperCase());
|
||||
} catch (e) {
|
||||
res.write('Error occurred: ${e.toString()}');
|
||||
return;
|
||||
}
|
||||
|
||||
res.write('This should not be reached');
|
||||
});
|
||||
|
||||
await http.startServer('localhost', 3000);
|
||||
print('Server started on http://localhost:3000');
|
||||
}
|
35
packages/pipeline/examples/mixed_pipes.dart
Normal file
35
packages/pipeline/examples/mixed_pipes.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_foundation/http.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
|
||||
class GreetingPipe {
|
||||
dynamic handle(String input, Function next) {
|
||||
return next('Hello, $input');
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
var app = Application(reflector: MirrorsReflector());
|
||||
var http = PlatformHttp(app);
|
||||
|
||||
app.container.registerSingleton((c) => Pipeline(c));
|
||||
|
||||
app.get('/', (req, res) async {
|
||||
var pipeline = app.container.make<Pipeline>();
|
||||
var result = await pipeline.send('World').through([
|
||||
'GreetingPipe',
|
||||
(String input, Function next) => next('$input!'),
|
||||
(String input, Function next) async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
return next(input.toUpperCase());
|
||||
},
|
||||
]).then((result) => 'Final result: $result');
|
||||
|
||||
res.write(
|
||||
result); // Outputs: "Final result: HELLO, WORLD!" (after 1 second)
|
||||
});
|
||||
|
||||
await http.startServer('localhost', 3000);
|
||||
print('Server started on http://localhost:3000');
|
||||
}
|
5
packages/pipeline/lib/pipeline.dart
Normal file
5
packages/pipeline/lib/pipeline.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
library;
|
||||
|
||||
export 'src/pipeline.dart';
|
||||
export 'src/conditionable.dart';
|
||||
export 'src/pipeline_contract.dart';
|
16
packages/pipeline/lib/src/conditionable.dart
Normal file
16
packages/pipeline/lib/src/conditionable.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
/// Provides conditional execution methods for the pipeline.
|
||||
mixin Conditionable<T> {
|
||||
T when(bool Function() callback, void Function(T) callback2) {
|
||||
if (callback()) {
|
||||
callback2(this as T);
|
||||
}
|
||||
return this as T;
|
||||
}
|
||||
|
||||
T unless(bool Function() callback, void Function(T) callback2) {
|
||||
if (!callback()) {
|
||||
callback2(this as T);
|
||||
}
|
||||
return this as T;
|
||||
}
|
||||
}
|
241
packages/pipeline/lib/src/pipeline.dart
Normal file
241
packages/pipeline/lib/src/pipeline.dart
Normal file
|
@ -0,0 +1,241 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:platform_container/container.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'pipeline_contract.dart';
|
||||
import 'conditionable.dart';
|
||||
|
||||
/// Defines the signature for a pipe function.
|
||||
typedef PipeFunction = FutureOr<dynamic> Function(
|
||||
dynamic passable, FutureOr<dynamic> Function(dynamic) next);
|
||||
|
||||
/// The primary class for building and executing pipelines.
|
||||
class Pipeline with Conditionable<Pipeline> implements PipelineContract {
|
||||
/// The container implementation.
|
||||
Container? _container;
|
||||
|
||||
final Map<String, Type> _typeMap = {};
|
||||
|
||||
/// The object being passed through the pipeline.
|
||||
dynamic _passable;
|
||||
|
||||
/// The array of class pipes.
|
||||
final List<dynamic> _pipes = [];
|
||||
|
||||
/// The method to call on each pipe.
|
||||
String _method = 'handle';
|
||||
|
||||
/// Logger for the pipeline.
|
||||
final Logger _logger = Logger('Pipeline');
|
||||
|
||||
/// Create a new class instance.
|
||||
Pipeline(this._container);
|
||||
|
||||
void registerPipeType(String name, Type type) {
|
||||
_typeMap[name] = type;
|
||||
}
|
||||
|
||||
/// Set the object being sent through the pipeline.
|
||||
@override
|
||||
Pipeline send(dynamic passable) {
|
||||
_passable = passable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the array of pipes.
|
||||
@override
|
||||
Pipeline through(dynamic pipes) {
|
||||
if (_container == null) {
|
||||
throw Exception(
|
||||
'A container instance has not been passed to the Pipeline.');
|
||||
}
|
||||
_pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Push additional pipes onto the pipeline.
|
||||
@override
|
||||
Pipeline pipe(dynamic pipes) {
|
||||
if (_container == null) {
|
||||
throw Exception(
|
||||
'A container instance has not been passed to the Pipeline.');
|
||||
}
|
||||
_pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the method to call on the pipes.
|
||||
@override
|
||||
Pipeline via(String method) {
|
||||
_method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Run the pipeline with a final destination callback.
|
||||
@override
|
||||
Future<dynamic> then(FutureOr<dynamic> Function(dynamic) destination) async {
|
||||
if (_container == null) {
|
||||
throw Exception(
|
||||
'A container instance has not been passed to the Pipeline.');
|
||||
}
|
||||
|
||||
var pipeline = (dynamic passable) async => await destination(passable);
|
||||
|
||||
for (var pipe in _pipes.reversed) {
|
||||
var next = pipeline;
|
||||
pipeline = (dynamic passable) async {
|
||||
return await carry(pipe, passable, next);
|
||||
};
|
||||
}
|
||||
|
||||
return await pipeline(_passable);
|
||||
}
|
||||
|
||||
/// Run the pipeline and return the result.
|
||||
@override
|
||||
Future<dynamic> thenReturn() async {
|
||||
return then((passable) => passable);
|
||||
}
|
||||
|
||||
/// Get a Closure that represents a slice of the application onion.
|
||||
Future<dynamic> carry(dynamic pipe, dynamic passable, Function next) async {
|
||||
try {
|
||||
if (pipe is Function) {
|
||||
return await pipe(passable, next);
|
||||
}
|
||||
|
||||
if (pipe is String) {
|
||||
if (_container == null) {
|
||||
throw Exception('Container is null, cannot resolve pipe: $pipe');
|
||||
}
|
||||
|
||||
final parts = parsePipeString(pipe);
|
||||
final pipeClass = parts[0];
|
||||
final parameters = parts.length > 1 ? parts.sublist(1) : [];
|
||||
|
||||
Type? pipeType;
|
||||
if (_typeMap.containsKey(pipeClass)) {
|
||||
pipeType = _typeMap[pipeClass];
|
||||
} else {
|
||||
// Try to resolve from mirrors
|
||||
try {
|
||||
for (var lib in currentMirrorSystem().libraries.values) {
|
||||
for (var decl in lib.declarations.values) {
|
||||
if (decl is ClassMirror &&
|
||||
decl.simpleName == Symbol(pipeClass)) {
|
||||
pipeType = decl.reflectedType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pipeType != null) break;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
if (pipeType == null) {
|
||||
throw Exception('Type not registered for pipe: $pipe');
|
||||
}
|
||||
}
|
||||
|
||||
var instance = _container?.make(pipeType);
|
||||
if (instance == null) {
|
||||
throw Exception('Unable to resolve pipe: $pipe');
|
||||
}
|
||||
|
||||
return await invokeMethod(
|
||||
instance, _method, [passable, next, ...parameters]);
|
||||
}
|
||||
|
||||
if (pipe is Type) {
|
||||
if (_container == null) {
|
||||
throw Exception('Container is null, cannot resolve pipe type');
|
||||
}
|
||||
|
||||
var instance = _container?.make(pipe);
|
||||
if (instance == null) {
|
||||
throw Exception('Unable to resolve pipe type: $pipe');
|
||||
}
|
||||
|
||||
return await invokeMethod(instance, _method, [passable, next]);
|
||||
}
|
||||
|
||||
// Handle instance of a class
|
||||
if (pipe is Object) {
|
||||
return await invokeMethod(pipe, _method, [passable, next]);
|
||||
}
|
||||
|
||||
throw Exception('Unsupported pipe type: ${pipe.runtimeType}');
|
||||
} catch (e) {
|
||||
return handleException(passable, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse full pipe string to get name and parameters.
|
||||
List<String> parsePipeString(String pipe) {
|
||||
var parts = pipe.split(':');
|
||||
return [parts[0], if (parts.length > 1) ...parts[1].split(',')];
|
||||
}
|
||||
|
||||
/// Get the array of configured pipes.
|
||||
List<dynamic> pipes() {
|
||||
return List.unmodifiable(_pipes);
|
||||
}
|
||||
|
||||
/// Get the container instance.
|
||||
Container getContainer() {
|
||||
if (_container == null) {
|
||||
throw Exception(
|
||||
'A container instance has not been passed to the Pipeline.');
|
||||
}
|
||||
return _container!;
|
||||
}
|
||||
|
||||
/// Set the container instance.
|
||||
Pipeline setContainer(Container container) {
|
||||
_container = container;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Handle the value returned from each pipe before passing it to the next.
|
||||
dynamic handleCarry(dynamic carry) {
|
||||
if (carry is Future) {
|
||||
return carry.then((value) => value ?? _passable);
|
||||
}
|
||||
return carry ?? _passable;
|
||||
}
|
||||
|
||||
Future<dynamic> invokeMethod(
|
||||
dynamic instance, String methodName, List<dynamic> arguments) async {
|
||||
// First try call() for invokable objects
|
||||
if (instance is Function) {
|
||||
return await instance(arguments[0], arguments[1]);
|
||||
}
|
||||
|
||||
var instanceMirror = reflect(instance);
|
||||
|
||||
// Check for call method first (invokable objects)
|
||||
var callSymbol = Symbol('call');
|
||||
if (instanceMirror.type.declarations.containsKey(callSymbol)) {
|
||||
var result = instanceMirror.invoke(callSymbol, arguments);
|
||||
return await result.reflectee;
|
||||
}
|
||||
|
||||
// Then try the specified method
|
||||
var methodSymbol = Symbol(methodName);
|
||||
if (!instanceMirror.type.declarations.containsKey(methodSymbol)) {
|
||||
throw Exception('Method $methodName not found on instance: $instance');
|
||||
}
|
||||
|
||||
var result = instanceMirror.invoke(methodSymbol, arguments);
|
||||
return await result.reflectee;
|
||||
}
|
||||
|
||||
/// Handle the given exception.
|
||||
dynamic handleException(dynamic passable, Object e) {
|
||||
if (e is Exception && e.toString().contains('Container is null')) {
|
||||
throw Exception(
|
||||
'A container instance has not been passed to the Pipeline.');
|
||||
}
|
||||
_logger.severe('Exception occurred in pipeline', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
9
packages/pipeline/lib/src/pipeline_contract.dart
Normal file
9
packages/pipeline/lib/src/pipeline_contract.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
/// Represents a series of "pipes" through which an object can be passed.
|
||||
abstract class PipelineContract {
|
||||
PipelineContract send(dynamic passable);
|
||||
PipelineContract through(dynamic pipes);
|
||||
PipelineContract pipe(dynamic pipes);
|
||||
PipelineContract via(String method);
|
||||
Future<dynamic> then(dynamic Function(dynamic) destination);
|
||||
Future<dynamic> thenReturn();
|
||||
}
|
19
packages/pipeline/pubspec.yaml
Normal file
19
packages/pipeline/pubspec.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
name: platform_pipeline
|
||||
description: The Pipeline Package for the Protevus Platform
|
||||
version: 0.0.1
|
||||
homepage: https://protevus.com
|
||||
documentation: https://docs.protevus.com
|
||||
repository: https://github.com/protevus/platform
|
||||
|
||||
environment:
|
||||
sdk: ^3.4.2
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
platform_container: ^9.0.0
|
||||
platform_foundation: ^9.0.0
|
||||
logging: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
test: ^1.24.0
|
258
packages/pipeline/test/laravel_pipeline_test.dart
Normal file
258
packages/pipeline/test/laravel_pipeline_test.dart
Normal file
|
@ -0,0 +1,258 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// Test pipe classes to match Laravel's test classes
|
||||
class PipelineTestPipeOne {
|
||||
static String? testPipeOne;
|
||||
|
||||
Future<dynamic> handle(dynamic piped, Function next) async {
|
||||
testPipeOne = piped.toString();
|
||||
return next(piped);
|
||||
}
|
||||
|
||||
Future<dynamic> differentMethod(dynamic piped, Function next) async {
|
||||
return next(piped);
|
||||
}
|
||||
}
|
||||
|
||||
class PipelineTestPipeTwo {
|
||||
static String? testPipeOne;
|
||||
|
||||
Future<dynamic> call(dynamic piped, Function next) async {
|
||||
testPipeOne = piped.toString();
|
||||
return next(piped);
|
||||
}
|
||||
}
|
||||
|
||||
class PipelineTestParameterPipe {
|
||||
static List<String>? testParameters;
|
||||
|
||||
Future<dynamic> handle(dynamic piped, Function next,
|
||||
[String? parameter1, String? parameter2]) async {
|
||||
testParameters = [
|
||||
if (parameter1 != null) parameter1,
|
||||
if (parameter2 != null) parameter2
|
||||
];
|
||||
return next(piped);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Laravel Pipeline Tests', () {
|
||||
late Container container;
|
||||
late Pipeline pipeline;
|
||||
|
||||
setUp(() {
|
||||
container = Container(const EmptyReflector());
|
||||
pipeline = Pipeline(container);
|
||||
|
||||
// Register test classes with container
|
||||
container
|
||||
.registerFactory<PipelineTestPipeOne>((c) => PipelineTestPipeOne());
|
||||
container
|
||||
.registerFactory<PipelineTestPipeTwo>((c) => PipelineTestPipeTwo());
|
||||
container.registerFactory<PipelineTestParameterPipe>(
|
||||
(c) => PipelineTestParameterPipe());
|
||||
|
||||
// Register types with pipeline
|
||||
pipeline.registerPipeType('PipelineTestPipeOne', PipelineTestPipeOne);
|
||||
pipeline.registerPipeType('PipelineTestPipeTwo', PipelineTestPipeTwo);
|
||||
pipeline.registerPipeType(
|
||||
'PipelineTestParameterPipe', PipelineTestParameterPipe);
|
||||
|
||||
// Reset static test variables
|
||||
PipelineTestPipeOne.testPipeOne = null;
|
||||
PipelineTestPipeTwo.testPipeOne = null;
|
||||
PipelineTestParameterPipe.testParameters = null;
|
||||
});
|
||||
|
||||
test('Pipeline basic usage', () async {
|
||||
String? testPipeTwo;
|
||||
final pipeTwo = (dynamic piped, Function next) {
|
||||
testPipeTwo = piped.toString();
|
||||
return next(piped);
|
||||
};
|
||||
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([PipelineTestPipeOne(), pipeTwo]).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||
expect(testPipeTwo, equals('foo'));
|
||||
});
|
||||
|
||||
test('Pipeline usage with objects', () async {
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([PipelineTestPipeOne()]).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||
});
|
||||
|
||||
test('Pipeline usage with invokable objects', () async {
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([PipelineTestPipeTwo()]).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeTwo.testPipeOne, equals('foo'));
|
||||
});
|
||||
|
||||
test('Pipeline usage with callable', () async {
|
||||
String? testPipeOne;
|
||||
final function = (dynamic piped, Function next) {
|
||||
testPipeOne = 'foo';
|
||||
return next(piped);
|
||||
};
|
||||
|
||||
var result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([function]).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(testPipeOne, equals('foo'));
|
||||
|
||||
testPipeOne = null;
|
||||
|
||||
result =
|
||||
await Pipeline(container).send('bar').through(function).thenReturn();
|
||||
|
||||
expect(result, equals('bar'));
|
||||
expect(testPipeOne, equals('foo'));
|
||||
});
|
||||
|
||||
test('Pipeline usage with pipe', () async {
|
||||
final object = {'value': 0};
|
||||
|
||||
final function = (dynamic obj, Function next) {
|
||||
obj['value']++;
|
||||
return next(obj);
|
||||
};
|
||||
|
||||
final result = await Pipeline(container)
|
||||
.send(object)
|
||||
.through([function]).pipe([function]).then((piped) => piped);
|
||||
|
||||
expect(result, equals(object));
|
||||
expect(object['value'], equals(2));
|
||||
});
|
||||
|
||||
test('Pipeline usage with invokable class', () async {
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([PipelineTestPipeTwo()]).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeTwo.testPipeOne, equals('foo'));
|
||||
});
|
||||
|
||||
test('Then method is not called if the pipe returns', () async {
|
||||
String thenValue = '(*_*)';
|
||||
String secondValue = '(*_*)';
|
||||
|
||||
final result = await Pipeline(container).send('foo').through([
|
||||
(value, next) => 'm(-_-)m',
|
||||
(value, next) {
|
||||
secondValue = 'm(-_-)m';
|
||||
return next(value);
|
||||
},
|
||||
]).then((piped) {
|
||||
thenValue = '(0_0)';
|
||||
return piped;
|
||||
});
|
||||
|
||||
expect(result, equals('m(-_-)m'));
|
||||
// The then callback is not called
|
||||
expect(thenValue, equals('(*_*)'));
|
||||
// The second pipe is not called
|
||||
expect(secondValue, equals('(*_*)'));
|
||||
});
|
||||
|
||||
test('Then method input value', () async {
|
||||
String? pipeReturn;
|
||||
String? thenArg;
|
||||
|
||||
final result = await Pipeline(container).send('foo').through([
|
||||
(value, next) async {
|
||||
final nextValue = await next('::not_foo::');
|
||||
pipeReturn = nextValue;
|
||||
return 'pipe::$nextValue';
|
||||
}
|
||||
]).then((piped) {
|
||||
thenArg = piped;
|
||||
return 'then$piped';
|
||||
});
|
||||
|
||||
expect(result, equals('pipe::then::not_foo::'));
|
||||
expect(thenArg, equals('::not_foo::'));
|
||||
});
|
||||
|
||||
test('Pipeline usage with parameters', () async {
|
||||
final parameters = ['one', 'two'];
|
||||
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through('PipelineTestParameterPipe:${parameters.join(',')}')
|
||||
.then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestParameterPipe.testParameters, equals(parameters));
|
||||
});
|
||||
|
||||
test('Pipeline via changes the method being called on the pipes', () async {
|
||||
final result = await Pipeline(container)
|
||||
.send('data')
|
||||
.through(PipelineTestPipeOne())
|
||||
.via('differentMethod')
|
||||
.then((piped) => piped);
|
||||
|
||||
expect(result, equals('data'));
|
||||
});
|
||||
|
||||
test('Pipeline throws exception on resolve without container', () async {
|
||||
expect(
|
||||
() => Pipeline(null)
|
||||
.send('data')
|
||||
.through(PipelineTestPipeOne())
|
||||
.then((piped) => piped),
|
||||
throwsA(isA<Exception>().having(
|
||||
(e) => e.toString(),
|
||||
'message',
|
||||
contains(
|
||||
'A container instance has not been passed to the Pipeline'))));
|
||||
});
|
||||
|
||||
test('Pipeline thenReturn method runs pipeline then returns passable',
|
||||
() async {
|
||||
final result = await Pipeline(container)
|
||||
.send('foo')
|
||||
.through([PipelineTestPipeOne()]).thenReturn();
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||
});
|
||||
|
||||
test('Pipeline conditionable', () async {
|
||||
var result = await Pipeline(container).send('foo').when(() => true,
|
||||
(Pipeline pipeline) {
|
||||
pipeline.pipe([PipelineTestPipeOne()]);
|
||||
}).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||
|
||||
PipelineTestPipeOne.testPipeOne = null;
|
||||
|
||||
result = await Pipeline(container).send('foo').when(() => false,
|
||||
(Pipeline pipeline) {
|
||||
pipeline.pipe([PipelineTestPipeOne()]);
|
||||
}).then((piped) => piped);
|
||||
|
||||
expect(result, equals('foo'));
|
||||
expect(PipelineTestPipeOne.testPipeOne, isNull);
|
||||
});
|
||||
});
|
||||
}
|
106
packages/pipeline/test/pipeline_test.dart
Normal file
106
packages/pipeline/test/pipeline_test.dart
Normal file
|
@ -0,0 +1,106 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_foundation/core.dart';
|
||||
import 'package:platform_container/container.dart';
|
||||
import 'package:platform_container/mirrors.dart';
|
||||
import 'package:platform_pipeline/pipeline.dart';
|
||||
|
||||
class AddExclamationPipe {
|
||||
Future<String> handle(String input, Function next) async {
|
||||
return await next('$input!');
|
||||
}
|
||||
}
|
||||
|
||||
class UppercasePipe {
|
||||
Future<String> handle(String input, Function next) async {
|
||||
return await next(input.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Application app;
|
||||
late Container container;
|
||||
late Pipeline pipeline;
|
||||
|
||||
setUp(() {
|
||||
app = Application(reflector: MirrorsReflector());
|
||||
container = app.container;
|
||||
container.registerSingleton(AddExclamationPipe());
|
||||
container.registerSingleton(UppercasePipe());
|
||||
pipeline = Pipeline(container);
|
||||
pipeline.registerPipeType('AddExclamationPipe', AddExclamationPipe);
|
||||
pipeline.registerPipeType('UppercasePipe', UppercasePipe);
|
||||
});
|
||||
|
||||
test('Pipeline should process simple string pipes', () async {
|
||||
var result = await pipeline.send('hello').through(
|
||||
['AddExclamationPipe', 'UppercasePipe']).then((res) async => res);
|
||||
expect(result, equals('HELLO!'));
|
||||
});
|
||||
|
||||
test('Pipeline should process function pipes', () async {
|
||||
var result = await pipeline.send('hello').through([
|
||||
(String input, Function next) async {
|
||||
var result = await next('$input, WORLD');
|
||||
return result;
|
||||
},
|
||||
(String input, Function next) async {
|
||||
var result = await next(input.toUpperCase());
|
||||
return result;
|
||||
},
|
||||
]).then((res) async => res as String);
|
||||
|
||||
expect(result, equals('HELLO, WORLD'));
|
||||
});
|
||||
|
||||
test('Pipeline should handle mixed pipe types', () async {
|
||||
var result = await pipeline.send('hello').through([
|
||||
'AddExclamationPipe',
|
||||
(String input, Function next) async {
|
||||
var result = await next(input.toUpperCase());
|
||||
return result;
|
||||
},
|
||||
]).then((res) async => res as String);
|
||||
expect(result, equals('HELLO!'));
|
||||
});
|
||||
|
||||
test('Pipeline should handle async pipes', () async {
|
||||
var result = await pipeline.send('hello').through([
|
||||
'UppercasePipe',
|
||||
(String input, Function next) async {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
return next('$input, WORLD');
|
||||
},
|
||||
]).then((res) async => res as String);
|
||||
expect(result, equals('HELLO, WORLD'));
|
||||
});
|
||||
|
||||
test('Pipeline should throw exception for unresolvable pipe', () {
|
||||
expect(
|
||||
() => pipeline
|
||||
.send('hello')
|
||||
.through(['NonExistentPipe']).then((res) => res),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('Pipeline should allow chaining of pipes', () async {
|
||||
var result = await pipeline
|
||||
.send('hello')
|
||||
.pipe('AddExclamationPipe')
|
||||
.pipe('UppercasePipe')
|
||||
.then((res) async => res as String);
|
||||
expect(result, equals('HELLO!'));
|
||||
});
|
||||
|
||||
test('Pipeline should respect the order of pipes', () async {
|
||||
var result1 = await pipeline
|
||||
.send('hello')
|
||||
.through(['AddExclamationPipe', 'UppercasePipe']).then((res) => res);
|
||||
var result2 = await pipeline
|
||||
.send('hello')
|
||||
.through(['UppercasePipe', 'AddExclamationPipe']).then((res) => res);
|
||||
expect(result1, equals('HELLO!'));
|
||||
expect(result2, equals('HELLO!!'));
|
||||
expect(result1, isNot(equals(result2)));
|
||||
});
|
||||
}
|
|
@ -44,6 +44,7 @@ export 'src/deferred/deferred_callback_collection.dart';
|
|||
export 'src/process/executable_finder.dart';
|
||||
|
||||
// Traits
|
||||
export 'src/traits/capsule_manager.dart';
|
||||
export 'src/traits/dumpable.dart';
|
||||
export 'src/traits/forwards_calls.dart';
|
||||
export 'src/traits/interacts_with_data.dart';
|
||||
|
|
136
packages/support/lib/src/manager.dart
Normal file
136
packages/support/lib/src/manager.dart
Normal file
|
@ -0,0 +1,136 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:platform_support/src/fluent.dart';
|
||||
|
||||
/// Base class for managing drivers with different implementations.
|
||||
///
|
||||
/// This class provides a foundation for managing different implementations of a service,
|
||||
/// similar to Laravel's Manager class. It handles driver creation, caching, and custom
|
||||
/// driver registration.
|
||||
abstract class Manager {
|
||||
/// The container instance.
|
||||
final Container container;
|
||||
|
||||
/// The configuration repository instance.
|
||||
final Fluent config;
|
||||
|
||||
/// The registered custom driver creators.
|
||||
final Map<String, dynamic Function(Container)> _customCreators = {};
|
||||
|
||||
/// The array of created "drivers".
|
||||
final Map<String, dynamic> _drivers = {};
|
||||
|
||||
/// Create a new manager instance.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [container]: The container instance to use for resolving dependencies
|
||||
Manager(this.container) : config = container.make<Fluent>();
|
||||
|
||||
/// Get the default driver name.
|
||||
///
|
||||
/// This method must be implemented by concrete managers to specify which
|
||||
/// driver should be used by default. Returns null if no default driver is set.
|
||||
String? getDefaultDriver();
|
||||
|
||||
/// Get a driver instance.
|
||||
///
|
||||
/// This method returns an instance of the requested driver. If the driver
|
||||
/// has already been created, it returns the cached instance. Otherwise,
|
||||
/// it creates a new instance.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [driver]: The name of the driver to get. If null, uses default driver.
|
||||
///
|
||||
/// Returns:
|
||||
/// The driver instance.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [ArgumentError] if no driver name is provided and no default exists
|
||||
T driver<T>([String? driver]) {
|
||||
driver ??= getDefaultDriver();
|
||||
|
||||
if (driver == null) {
|
||||
throw ArgumentError(
|
||||
'Unable to resolve NULL driver for [${runtimeType}].');
|
||||
}
|
||||
|
||||
return _drivers.putIfAbsent(driver, () => createDriver(driver!)) as T;
|
||||
}
|
||||
|
||||
/// Create a new driver instance.
|
||||
///
|
||||
/// This method creates a new instance of the requested driver. It first checks
|
||||
/// for custom creators, then looks for a create{Driver}Driver method.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [driver]: The name of the driver to create
|
||||
///
|
||||
/// Returns:
|
||||
/// The new driver instance
|
||||
///
|
||||
/// Throws:
|
||||
/// - [ArgumentError] if the driver is not supported
|
||||
dynamic createDriver(String driver) {
|
||||
// Check for custom creator
|
||||
if (_customCreators.containsKey(driver)) {
|
||||
return _customCreators[driver]!(container);
|
||||
}
|
||||
|
||||
// Look for create{Driver}Driver method
|
||||
var methodName =
|
||||
'create${driver[0].toUpperCase()}${driver.substring(1)}Driver';
|
||||
var result = callDriverCreator(methodName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw ArgumentError('Driver [$driver] not supported.');
|
||||
}
|
||||
|
||||
/// Call a driver creator method.
|
||||
///
|
||||
/// This method must be implemented by concrete managers to handle calling
|
||||
/// the appropriate creator method based on the method name.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [method]: The name of the creator method to call
|
||||
///
|
||||
/// Returns:
|
||||
/// The created driver instance, or null if the method doesn't exist
|
||||
dynamic callDriverCreator(String method);
|
||||
|
||||
/// Register a custom driver creator.
|
||||
///
|
||||
/// This method allows registering custom driver creators that will be used
|
||||
/// instead of the default creation logic.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [driver]: The name of the driver
|
||||
/// - [creator]: The function that creates the driver
|
||||
///
|
||||
/// Returns:
|
||||
/// This manager instance for method chaining
|
||||
Manager extend(String driver, dynamic Function(Container) creator) {
|
||||
_customCreators[driver] = creator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Get all of the created drivers.
|
||||
///
|
||||
/// Returns a map of all driver instances that have been created.
|
||||
Map<String, dynamic> getDrivers() => Map.unmodifiable(_drivers);
|
||||
|
||||
/// Get the container instance used by the manager.
|
||||
Container getContainer() => container;
|
||||
|
||||
/// Forget all of the resolved driver instances.
|
||||
///
|
||||
/// This method clears the driver cache, forcing new instances to be created
|
||||
/// on next access.
|
||||
///
|
||||
/// Returns:
|
||||
/// This manager instance for method chaining
|
||||
Manager forgetDrivers() {
|
||||
_drivers.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
51
packages/support/lib/src/traits/capsule_manager.dart
Normal file
51
packages/support/lib/src/traits/capsule_manager.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import 'package:platform_container/container.dart';
|
||||
import 'package:platform_support/src/fluent.dart';
|
||||
|
||||
/// A mixin that provides capsule management functionality.
|
||||
///
|
||||
/// This mixin allows classes to manage a global instance and a container instance,
|
||||
/// similar to Laravel's CapsuleManagerTrait.
|
||||
mixin CapsuleManager {
|
||||
/// The current globally used instance.
|
||||
static dynamic _instance;
|
||||
|
||||
/// The container instance.
|
||||
Container? _container;
|
||||
|
||||
/// Setup the IoC container instance.
|
||||
///
|
||||
/// This method initializes the container and ensures it has a config binding.
|
||||
/// If no config binding exists, it creates one with an empty [Fluent] instance.
|
||||
void setupContainer(Container container) {
|
||||
_container = container;
|
||||
|
||||
if (!_container!.has<Fluent>()) {
|
||||
_container!.registerSingleton(Fluent(), as: Fluent);
|
||||
}
|
||||
}
|
||||
|
||||
/// Make this capsule instance available globally.
|
||||
///
|
||||
/// This method sets the current instance as the global instance that can be
|
||||
/// accessed throughout the application.
|
||||
void setAsGlobal() {
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
/// Get the IoC container instance.
|
||||
///
|
||||
/// Returns the current container instance used by this capsule.
|
||||
Container? getContainer() => _container;
|
||||
|
||||
/// Set the IoC container instance.
|
||||
///
|
||||
/// This method allows changing the container instance used by this capsule.
|
||||
void setContainer(Container container) {
|
||||
_container = container;
|
||||
}
|
||||
|
||||
/// Get the current globally used instance.
|
||||
///
|
||||
/// Returns the current global instance of this capsule.
|
||||
static dynamic getInstance() => _instance;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue