diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart index c53b7ce..fc845ad 100644 --- a/incubation/container/container/lib/src/container.dart +++ b/incubation/container/container/lib/src/container.dart @@ -268,6 +268,10 @@ class Container { // Check for contextual binding var contextualConcrete = _getContextualConcrete(t2); + if (contextualConcrete == null && _hasContextualBinding(t2)) { + throw BindingResolutionException( + 'No implementation was provided for contextual binding of $t2'); + } if (contextualConcrete != null) { dynamic instance; if (contextualConcrete is Function) { @@ -382,6 +386,31 @@ class Container { // Use reflection to create instance var reflectedType = reflector.reflectType(t2); + if (reflectedType == null) { + throw BindingResolutionException('No binding was found for $t2'); + } + + // Check if we have all required dependencies + if (reflectedType is ReflectedClass) { + bool isDefault(String name) { + return name.isEmpty || name == reflectedType.name; + } + + var constructor = reflectedType.constructors.firstWhere( + (c) => isDefault(c.name), + orElse: (() => throw BindingResolutionException( + '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'))); + + // Check if we can resolve all constructor parameters + for (var param in constructor.parameters) { + var paramType = param.type.reflectedType; + if (!has(paramType) && reflector.reflectType(paramType) == null) { + throw BindingResolutionException( + 'No binding was found for $paramType required by $t2'); + } + } + } + var positional = []; var named = {}; @@ -730,6 +759,24 @@ class Container { return null; } + /// Check if a type has a contextual binding. + bool _hasContextualBinding(Type type) { + if (_buildStack.isEmpty) return false; + + // Check current container's contextual bindings + Container? search = this; + while (search != null) { + var building = _buildStack.last; + var contextMap = search._contextual[building]; + if (contextMap != null && contextMap.containsKey(type)) { + return true; + } + search = search._parent; + } + + return false; + } + /// Check if we're in danger of a circular dependency. void _checkCircularDependency(Type type) { if (_buildStack.contains(type)) { diff --git a/incubation/container/container/lib/src/contextual_binding_builder.dart b/incubation/container/container/lib/src/contextual_binding_builder.dart index 975c7e7..77186f6 100644 --- a/incubation/container/container/lib/src/contextual_binding_builder.dart +++ b/incubation/container/container/lib/src/contextual_binding_builder.dart @@ -45,12 +45,19 @@ class ContextualImplementationBuilder { final Type abstract; /// Creates a new contextual implementation builder - ContextualImplementationBuilder(this.container, this.concrete, this.abstract); + 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() { for (var concreteType in concrete) { - container.addContextualBinding(concreteType, abstract, T); + container.addContextualBinding( + concreteType, abstract, (Container c) => c.make()); } } diff --git a/incubation/container/container/test/contextual_binding_test.dart b/incubation/container/container/test/contextual_binding_test.dart index 3881209..2500044 100644 --- a/incubation/container/container/test/contextual_binding_test.dart +++ b/incubation/container/container/test/contextual_binding_test.dart @@ -42,7 +42,10 @@ void main() { container.registerSingleton(ConsoleLogger()); // Register contextual binding - container.when(LoggerClient).needs().give(); + container + .when(LoggerClient) + .needs() + .giveFactory((c) => FileLogger('test.log')); // The default binding should be used here var logger = container.make(); @@ -56,8 +59,14 @@ void main() { test('multiple contextual bindings work independently', () { container.registerSingleton(ConsoleLogger()); - container.when(LoggerClient).needs().give(); - container.when(SpecialLoggerClient).needs().give(); + container + .when(LoggerClient) + .needs() + .giveFactory((c) => FileLogger('test.log')); + container + .when(SpecialLoggerClient) + .needs() + .giveFactory((c) => ConsoleLogger()); var client1 = container.make(); var client2 = container.make(); @@ -80,7 +89,10 @@ void main() { }); test('contextual binding throws when implementation not found', () { - container.when(LoggerClient).needs().give(); + container + .when(LoggerClient) + .needs() + .giveFactory((c) => FileLogger('test.log')); expect( () => container.make(), @@ -90,7 +102,10 @@ void main() { test('contextual bindings are inherited by child containers', () { container.registerSingleton(ConsoleLogger()); - container.when(LoggerClient).needs().give(); + container + .when(LoggerClient) + .needs() + .giveFactory((c) => FileLogger('test.log')); var childContainer = container.createChild(); var client = childContainer.make(); @@ -100,7 +115,10 @@ void main() { test('child container can override parent contextual binding', () { container.registerSingleton(ConsoleLogger()); - container.when(LoggerClient).needs().give(); + container + .when(LoggerClient) + .needs() + .giveFactory((c) => FileLogger('test.log')); var childContainer = container.createChild(); childContainer