From 52dcecef9a5fdb0403c2322bea2e99eb74f1aeae Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Sun, 11 Dec 2016 01:15:30 +0100 Subject: [PATCH] Add optional value injection when values cannot be injected by injectors --- TUTORIAL.md | 2 +- source/poodinis/autowire.d | 8 ++++++-- source/poodinis/valueinjection.d | 21 ++++++++++++++++++--- test/poodinis/valueinjectiontest.d | 22 ++++++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index ded4a07..936abd1 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -140,7 +140,7 @@ Besides injecting class instances, Poodinis can also inject values: ``` class ExampleClass { @Value("a.key.for.this.value") - private int someNumber; + private int someNumber = 9; // Assignment is kept when the injector cannot find the value associated with key } ``` The value will automatically be injected during the autowiring process. In order for Poodinis to be able to inject values, ValueInjectors must be available and registered with the dependency container: diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index 398c0fa..e498c56 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -134,7 +134,7 @@ private void autowireMember(string member, size_t memberIndex, Type)(shared(Depe injectInstance!(member, memberIndex, UseMemberType)(container, instance); } else static if (is(typeof(attribute) == Value)) { enum key = attribute.key; - injectValue!(member, memberIndex, key)(container, instance); + injectValue!(member, memberIndex, key, false)(container, instance); } } } @@ -204,7 +204,7 @@ private QualifierType createOrResolveInstance(MemberType, QualifierType, bool cr } } -private void injectValue(string member, size_t memberIndex, string key, Type)(shared(DependencyContainer) container, Type instance) { +private void injectValue(string member, size_t memberIndex, string key, bool mandatory, Type)(shared(DependencyContainer) container, Type instance) { alias MemberType = typeof(Type.tupleof[memberIndex]); try { auto injector = container.resolve!(ValueInjector!MemberType); @@ -214,6 +214,10 @@ private void injectValue(string member, size_t memberIndex, string key, Type)(sh } } catch (ResolveException e) { throw new ValueInjectionException(format("Could not inject value of type %s into %s.%s: value injector is missing for this type.", typeid(MemberType), typeid(Type), member)); + } catch (ValueNotAvailableException e) { + static if (mandatory) { + throw new ValueInjectionException(format("Could not inject value of type %s into %s.%s", typeid(MemberType), typeid(Type), member), e); + } } } diff --git a/source/poodinis/valueinjection.d b/source/poodinis/valueinjection.d index 964f9c8..4910bb8 100644 --- a/source/poodinis/valueinjection.d +++ b/source/poodinis/valueinjection.d @@ -12,6 +12,7 @@ module poodinis.valueinjection; import std.exception; +import std.string; /** * Thrown when something goes wrong during value injection. @@ -20,6 +21,19 @@ class ValueInjectionException : Exception { mixin basicExceptionCtors; } +/** + * Thrown by injectors when the value with the given key cannot be found. + */ +class ValueNotAvailableException : Exception { + this(string key) { + super(format("Value for key %s is not available", key)); + } + + this(string key, Throwable cause) { + super(format("Value for key %s is not available", key), cause); + } +} + /** * UDA used for marking class members which should be value-injected. * @@ -53,9 +67,8 @@ struct Value { * type or that of a struct. While class types are also supported, value injectors * are not intended for them. * - * Note that value injectors are also autowired before being used. Value injectors should - * not contain dependencies on classes which require value injection. Neither should a - * value injector have members which are to be value-injected. + * Note that value injectors are also autowired before being used. Values within dependencies of + * a value injector are not injected. Neither are values within the value injector itself. * * Value injection is not supported for constructor injection. * @@ -75,6 +88,8 @@ interface ValueInjector(Type) { * * The key can have any format. Generally you are encouraged * to accept a dot separated path, for example: server.http.port + * + * Throws: ValueNotAvailableException when the value for the given key is not available for any reason */ Type get(string key); } diff --git a/test/poodinis/valueinjectiontest.d b/test/poodinis/valueinjectiontest.d index 78526ad..01e8039 100644 --- a/test/poodinis/valueinjectiontest.d +++ b/test/poodinis/valueinjectiontest.d @@ -25,6 +25,11 @@ version(unittest) { Thing thing; } + class ConfigWithDefaults { + @Value("conf.missing") + int noms = 9; + } + // Test injection of values unittest { auto container = new shared DependencyContainer(); @@ -69,4 +74,21 @@ version(unittest) { assertThrown!ValueInjectionException(autowire(container, new MyConfig())); } + + // Test injection of values with defaults + unittest { + auto container = new shared DependencyContainer(); + container.register!ConfigWithDefaults; + + class IntInjector : ValueInjector!int { + public override int get(string key) { + throw new ValueNotAvailableException(key); + } + } + + container.register!(ValueInjector!int, IntInjector); + + auto instance = container.resolve!ConfigWithDefaults; + assert(instance.noms == 9); + } }