1 /** 2 * This module contains facilities to support value injection. Actual injection is done by the 3 * autowiring mechanism. 4 * 5 * Authors: 6 * Mike Bierlee, m.bierlee@lostmoment.com 7 * Copyright: 2014-2025 Mike Bierlee 8 * License: 9 * This software is licensed under the terms of the MIT license. 10 * The full terms of the license can be found in the LICENSE file. 11 */ 12 module poodinis.valueinjection; 13 14 import std.string : format; 15 import std.exception : basicExceptionCtors; 16 17 /** 18 * Thrown when something goes wrong during value injection. 19 */ 20 class ValueInjectionException : Exception { 21 mixin basicExceptionCtors; 22 } 23 24 /** 25 * Thrown by injectors when the value with the given key cannot be found. 26 */ 27 class ValueNotAvailableException : Exception { 28 this(string key) { 29 super(format("Value for key %s is not available", key)); 30 } 31 32 this(string key, Throwable cause) { 33 super(format("Value for key %s is not available", key), cause); 34 } 35 } 36 37 /** 38 * UDA used for marking class members which should be value-injected. 39 * 40 * A key must be supplied, which can be in any format depending on how 41 * a value injector reads it. 42 * 43 * When the injector throws a ValueNotAvailableException, the value is 44 * not injected and will keep its original assignment. 45 * 46 * Examples: 47 * --- 48 * class MyClass { 49 * @Value("general.importantNumber") 50 * private int number = 8; 51 * } 52 * --- 53 */ 54 struct Value { 55 /** 56 * The textual key used to find the value by injectors. 57 * 58 * The format is injector-specific. 59 */ 60 string key; 61 } 62 63 /** 64 * UDA used for marking class members which should be value-injected. 65 * 66 * When the injector throws a ValueNotAvailableException, it is re-thrown 67 * instead of being suppressed. 68 * 69 * A key must be supplied, which can be in any format depending on how 70 * a value injector reads it. 71 * 72 * Examples: 73 * --- 74 * class MyClass { 75 * @MandatoryValue("general.valueWhichShouldBeThere") 76 * private int number; 77 * } 78 * --- 79 */ 80 struct MandatoryValue { 81 /** 82 * The textual key used to find the value by injectors. 83 * 84 * The format is injector-specific. 85 */ 86 string key; 87 } 88 89 /** 90 * Interface which should be implemented by value injectors. 91 * 92 * Each value injector injects one specific type. The type can be any primitive 93 * type or that of a struct. While class types are also supported, value injectors 94 * are not intended for them. 95 * 96 * Note that value injectors are also autowired before being used. Values within dependencies of 97 * a value injector are not injected. Neither are values within the value injector itself. 98 * 99 * Value injection is not supported for constructor injection. 100 * 101 * Examples: 102 * --- 103 * class MyIntInjector : ValueInjector!int { 104 * override int get(string key) { ... } 105 * } 106 * 107 * // In order to make the container use your injector, register it by interface: 108 * container.register!(ValueInjector!int, MyIntInjector); 109 * --- 110 */ 111 interface ValueInjector(Type) { 112 /** 113 * Get a value from the injector by key. 114 * 115 * The key can have any format. Generally you are encouraged 116 * to accept a dot separated path, for example: server.http.port 117 * 118 * Throws: ValueNotAvailableException when the value for the given key is not available for any reason 119 */ 120 Type get(string key); 121 } 122 123 version (unittest) : // 124 125 import poodinis; 126 import poodinis.testclasses; 127 import std.exception; 128 129 struct LocalStruct { 130 bool wasInjected = false; 131 } 132 133 class LocalStructInjector : ValueInjector!LocalStruct { 134 override LocalStruct get(string key) { 135 auto data = LocalStruct(true); 136 return data; 137 } 138 } 139 140 class LocalClassWithStruct { 141 @Value("") 142 LocalStruct localStruct; 143 } 144 145 // Test injection of values 146 unittest { 147 auto container = new shared DependencyContainer(); 148 container.register!MyConfig; 149 container.register!(ValueInjector!int, IntInjector); 150 container.register!(ValueInjector!string, StringInjector); 151 container.register!(ValueInjector!Thing, ThingInjector); 152 153 auto instance = container.resolve!MyConfig; 154 assert(instance.stuffs == 364); 155 assert(instance.name == "Le Chef"); 156 assert(instance.thing.x == 8899); 157 } 158 159 // Test injection of values throws exception when injector is not there 160 unittest { 161 auto container = new shared DependencyContainer(); 162 container.register!MyConfig; 163 assertThrown!ResolveException(container.resolve!MyConfig); 164 165 assertThrown!ValueInjectionException(autowire(container, new MyConfig())); 166 } 167 168 // Test injection of values with defaults 169 unittest { 170 auto container = new shared DependencyContainer(); 171 container.register!ConfigWithDefaults; 172 container.register!(ValueInjector!int, DefaultIntInjector); 173 174 auto instance = container.resolve!ConfigWithDefaults; 175 assert(instance.noms == 9); 176 } 177 178 // Test mandatory injection of values which are available 179 unittest { 180 auto container = new shared DependencyContainer(); 181 container.register!ConfigWithMandatory; 182 container.register!(ValueInjector!int, MandatoryAvailableIntInjector); 183 184 auto instance = container.resolve!ConfigWithMandatory; 185 assert(instance.nums == 7466); 186 } 187 188 // Test mandatory injection of values which are not available 189 unittest { 190 auto container = new shared DependencyContainer(); 191 container.register!ConfigWithMandatory; 192 container.register!(ValueInjector!int, MandatoryUnavailableIntInjector); 193 194 assertThrown!ResolveException(container.resolve!ConfigWithMandatory); 195 assertThrown!ValueInjectionException(autowire(container, new ConfigWithMandatory())); 196 } 197 198 // Test injecting dependencies within value injectors 199 unittest { 200 auto container = new shared DependencyContainer(); 201 auto dependency = new Dependency(); 202 container.register!Dependency.existingInstance(dependency); 203 container.register!(ValueInjector!int, DependencyInjectedIntInjector); 204 auto injector = cast(DependencyInjectedIntInjector) container.resolve!(ValueInjector!int); 205 206 assert(injector.dependency is dependency); 207 } 208 209 // Test injecting circular dependencies within value injectors 210 unittest { 211 auto container = new shared DependencyContainer(); 212 container.register!(ValueInjector!int, CircularIntInjector); 213 auto injector = cast(CircularIntInjector) container.resolve!(ValueInjector!int); 214 215 assert(injector.dependency is injector); 216 assert(injector.get("whatever") == 3); 217 } 218 219 // Test value injection within value injectors 220 unittest { 221 auto container = new shared DependencyContainer(); 222 container.register!(ValueInjector!int, ValueInjectedIntInjector); 223 auto injector = cast(ValueInjectedIntInjector) container.resolve!(ValueInjector!int); 224 225 assert(injector.count == 5); 226 } 227 228 // Test value injection within dependencies of value injectors 229 unittest { 230 auto container = new shared DependencyContainer(); 231 container.register!ConfigWithDefaults; 232 233 container.register!(ValueInjector!int, DependencyValueInjectedIntInjector); 234 auto injector = cast(DependencyValueInjectedIntInjector) container.resolve!( 235 ValueInjector!int); 236 237 assert(injector.config.noms == 8899); 238 } 239 240 // Test resolving locally defined struct injector (github issue #20) 241 unittest { 242 auto container = new shared DependencyContainer(); 243 container.register!(ValueInjector!LocalStruct, LocalStructInjector); 244 container.register!LocalClassWithStruct; 245 246 auto injector = container.resolve!(ValueInjector!LocalStruct); 247 assert(injector !is null); 248 249 auto localClass = container.resolve!LocalClassWithStruct; 250 assert(localClass.localStruct.wasInjected); 251 }