add: another container test 28 pass

This commit is contained in:
Patrick Stewart 2024-12-23 20:36:32 -07:00
parent c64b707ea5
commit 0be803288b
24 changed files with 2260 additions and 0 deletions

7
packages/ioc_container/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# 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

View file

@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View file

@ -0,0 +1,10 @@
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.

View file

@ -0,0 +1,39 @@
<!--
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.

View file

@ -0,0 +1,30 @@
# 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

View file

View file

@ -0,0 +1,20 @@
// 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/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

View file

@ -0,0 +1,143 @@
import 'dart:mirrors';
import 'package:ioc_container/src/container.dart';
import 'package:ioc_container/src/util.dart';
class BoundMethod {
static dynamic call(Container container, dynamic callback,
[List<dynamic> parameters = const [], String? defaultMethod]) {
if (callback is String &&
defaultMethod == null &&
_hasInvokeMethod(callback)) {
defaultMethod = '__invoke';
}
if (_isCallableWithAtSign(callback) || defaultMethod != null) {
return _callClass(container, callback, parameters, defaultMethod);
}
return _callBoundMethod(container, callback, () {
var dependencies =
_getMethodDependencies(container, callback, parameters);
return Function.apply(callback, dependencies);
});
}
static bool _hasInvokeMethod(String className) {
ClassMirror? classMirror = _getClassMirror(className);
return classMirror?.declarations[Symbol('__invoke')] != null;
}
static dynamic _callClass(Container container, String target,
List<dynamic> parameters, String? defaultMethod) {
var segments = target.split('@');
var method = segments.length == 2 ? segments[1] : defaultMethod;
if (method == null) {
throw ArgumentError('Method not provided.');
}
var instance = container.make(segments[0]);
return call(container, [instance, method], parameters);
}
static dynamic _callBoundMethod(
Container container, dynamic callback, Function defaultCallback) {
if (callback is! List) {
return Util.unwrapIfClosure(defaultCallback);
}
var method = _normalizeMethod(callback);
// Note: We need to add these methods to the Container class
if (container.hasMethodBinding(method)) {
return container.callMethodBinding(method, callback[0]);
}
return Util.unwrapIfClosure(defaultCallback);
}
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 List _getMethodDependencies(
Container container, dynamic callback, List<dynamic> parameters) {
var dependencies = <dynamic>[];
var reflector = _getCallReflector(callback);
for (var parameter in reflector.parameters) {
_addDependencyForCallParameter(
container, parameter, parameters, dependencies);
}
return [...dependencies, ...parameters];
}
static MethodMirror _getCallReflector(dynamic callback) {
if (callback is String && callback.contains('::')) {
callback = callback.split('::');
} else if (callback is! Function && callback is! List) {
callback = [callback, '__invoke'];
}
if (callback is List) {
return (reflectClass(callback[0].runtimeType)
.declarations[Symbol(callback[1])] as MethodMirror);
} else {
return (reflect(callback) as ClosureMirror).function;
}
}
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;
}
}
}

View file

@ -0,0 +1,830 @@
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>());
}
}
@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(String attribute, Function callback) {
_afterResolvingAttributeCallbacks[attribute] ??= [];
_afterResolvingAttributeCallbacks[attribute]!.add(callback);
}
@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) {
if (annotation.reflectee is ContextualAttribute) {
var instance = annotation.reflectee as ContextualAttribute;
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);
}
}
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;
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
_fireAfterResolvingCallbacks(concrete, instance);
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]!);
}
bool isShared(String abstract) {
return _instances.containsKey(abstract) ||
(_bindings.containsKey(abstract) &&
_bindings[abstract]!['shared'] == true);
}
bool isAlias(String name) {
return _aliases.containsKey(name);
}
// Implement ArrayAccess-like functionality
dynamic operator [](String key) => make(key);
void operator []=(String key, dynamic value) => bind(key, value);
}

View file

@ -0,0 +1,47 @@
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));
}
}

View file

@ -0,0 +1,14 @@
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';
}

View file

@ -0,0 +1,24 @@
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;
}
}

View file

@ -0,0 +1,56 @@
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;
}
}

View file

@ -0,0 +1,19 @@
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
test: ^1.24.0

View file

@ -0,0 +1,279 @@
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')));
}

View file

@ -1 +1,6 @@
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';

View file

@ -18,3 +18,4 @@ dependencies:
dev_dependencies:
lints: ^3.0.0
test: ^1.24.0
platform_config: ^0.1.0

View file

@ -0,0 +1,61 @@
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 []]);
}

View file

@ -0,0 +1,99 @@
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 {}

View file

@ -0,0 +1,171 @@
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;
}
}

View file

@ -0,0 +1,164 @@
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]);
}

View file

@ -0,0 +1,165 @@
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 {}

View file

@ -0,0 +1,37 @@
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);
});
});
}

View file

@ -0,0 +1,36 @@
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);
});
});
}