rainier_container/
container.rs1use std::any::{type_name, Any, TypeId};
36use std::cell::RefCell;
37use std::collections::HashMap;
38use std::sync::{Arc, Mutex, RwLock};
39
40use rainier_support::{Error, Result};
41
42type Shared = Arc<dyn Any + Send + Sync>;
44
45type Factory = Arc<dyn Fn(&Container) -> Result<Shared> + Send + Sync>;
47
48#[derive(Clone)]
50enum Binding {
51 Transient(Factory),
53 Shared { factory: Factory, instance: Arc<Mutex<Option<Shared>>>, scoped: bool },
55}
56
57#[derive(Default)]
63pub struct Container {
64 bindings: RwLock<HashMap<TypeId, Binding>>,
65}
66
67thread_local! {
68 static RESOLVING: RefCell<Vec<(TypeId, &'static str)>> = const { RefCell::new(Vec::new()) };
72}
73
74impl Container {
75 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn bind<T, F>(&self, factory: F)
82 where
83 T: Send + Sync + 'static,
84 F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
85 {
86 let factory: Factory = Arc::new(move |c| Ok(Arc::new(factory(c)?) as Shared));
87 self.insert::<T>(Binding::Transient(factory));
88 }
89
90 pub fn singleton<T, F>(&self, factory: F)
93 where
94 T: Send + Sync + 'static,
95 F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
96 {
97 self.shared::<T, F>(factory, false);
98 }
99
100 pub fn scoped<T, F>(&self, factory: F)
109 where
110 T: Send + Sync + 'static,
111 F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
112 {
113 self.shared::<T, F>(factory, true);
114 }
115
116 fn shared<T, F>(&self, factory: F, scoped: bool)
117 where
118 T: Send + Sync + 'static,
119 F: Fn(&Container) -> Result<T> + Send + Sync + 'static,
120 {
121 let factory: Factory = Arc::new(move |c| Ok(Arc::new(factory(c)?) as Shared));
122 self.insert::<T>(Binding::Shared { factory, instance: Arc::new(Mutex::new(None)), scoped });
123 }
124
125 pub fn instance<T: Send + Sync + 'static>(&self, value: T) {
127 self.instance_arc(Arc::new(value));
128 }
129
130 pub fn instance_arc<T: Send + Sync + 'static>(&self, value: Arc<T>) {
133 let instance: Shared = value;
134 self.insert::<T>(Binding::Shared {
135 factory: Arc::new(|_| Err(Error::internal("instance binding has no factory"))),
137 instance: Arc::new(Mutex::new(Some(instance))),
138 scoped: false,
139 });
140 }
141
142 fn insert<T: 'static>(&self, binding: Binding) {
143 let mut bindings = self.bindings.write().expect("container lock poisoned");
144 bindings.insert(TypeId::of::<T>(), binding);
145 }
146
147 pub fn resolve<T: Send + Sync + 'static>(&self) -> Result<Arc<T>> {
152 let binding = {
153 let bindings = self.bindings.read().expect("container lock poisoned");
154 bindings.get(&TypeId::of::<T>()).cloned()
155 };
156
157 let Some(binding) = binding else {
158 return Err(Error::internal(format!(
159 "nothing is bound for `{}` — register it in a service provider before resolving",
160 type_name::<T>()
161 )));
162 };
163
164 let value = self.build::<T>(binding)?;
165 value.downcast::<T>().map_err(|_| {
166 Error::internal(format!("binding for `{}` produced the wrong type", type_name::<T>()))
170 })
171 }
172
173 fn build<T: Send + Sync + 'static>(&self, binding: Binding) -> Result<Shared> {
174 let _guard = ResolutionGuard::enter::<T>()?;
180
181 match binding {
182 Binding::Transient(factory) => factory(self),
183 Binding::Shared { factory, instance, .. } => {
184 let mut slot = instance.lock().expect("container lock poisoned");
185 if let Some(existing) = slot.clone() {
186 return Ok(existing);
187 }
188 let built = factory(self)?;
189 *slot = Some(built.clone());
190 Ok(built)
191 }
192 }
193 }
194
195 pub fn try_resolve<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
197 self.resolve::<T>().ok()
198 }
199
200 pub fn expect_resolve<T: Send + Sync + 'static>(&self) -> Arc<T> {
204 match self.resolve::<T>() {
205 Ok(value) => value,
206 Err(e) => panic!("{e}"),
207 }
208 }
209
210 pub fn bound<T: 'static>(&self) -> bool {
212 self.bindings.read().expect("container lock poisoned").contains_key(&TypeId::of::<T>())
213 }
214
215 pub fn forget<T: 'static>(&self) {
217 self.bindings.write().expect("container lock poisoned").remove(&TypeId::of::<T>());
218 }
219
220 pub fn flush_scoped(&self) {
223 let bindings = self.bindings.read().expect("container lock poisoned");
224 for binding in bindings.values() {
225 if let Binding::Shared { instance, scoped: true, .. } = binding {
226 *instance.lock().expect("container lock poisoned") = None;
227 }
228 }
229 }
230
231 pub fn len(&self) -> usize {
233 self.bindings.read().expect("container lock poisoned").len()
234 }
235
236 pub fn is_empty(&self) -> bool {
238 self.len() == 0
239 }
240}
241
242impl std::fmt::Debug for Container {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 f.debug_struct("Container").field("bindings", &self.len()).finish()
245 }
246}
247
248struct ResolutionGuard;
255
256impl ResolutionGuard {
257 fn enter<T: 'static>() -> Result<Self> {
258 let id = TypeId::of::<T>();
259 let name = type_name::<T>();
260
261 RESOLVING.with(|stack| {
262 let mut stack = stack.borrow_mut();
263 if stack.iter().any(|(seen, _)| *seen == id) {
264 let mut chain: Vec<&str> = stack.iter().map(|(_, n)| *n).collect();
265 chain.push(name);
266 return Err(Error::internal(format!(
267 "circular dependency while resolving: {}",
268 chain.join(" -> ")
269 )));
270 }
271 stack.push((id, name));
272 Ok(())
273 })?;
274
275 Ok(Self)
276 }
277}
278
279impl Drop for ResolutionGuard {
280 fn drop(&mut self) {
281 RESOLVING.with(|stack| {
285 stack.borrow_mut().pop();
286 });
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use std::sync::atomic::{AtomicUsize, Ordering};
294
295 #[derive(Debug)]
296 struct Counter(usize);
297 #[derive(Debug)]
298 struct Dependent(usize);
299
300 #[test]
301 fn instance_bindings_come_back_as_given() {
302 let c = Container::new();
303 c.instance(Counter(42));
304 assert_eq!(c.resolve::<Counter>().unwrap().0, 42);
305 }
306
307 #[test]
308 fn unbound_types_report_their_name() {
309 let c = Container::new();
310 let err = c.resolve::<Counter>().unwrap_err();
311 assert!(err.message().contains("Counter"), "{}", err.message());
312 }
313
314 #[test]
315 fn bind_rebuilds_every_time() {
316 static BUILDS: AtomicUsize = AtomicUsize::new(0);
317 let c = Container::new();
318 c.bind(|_| Ok(Counter(BUILDS.fetch_add(1, Ordering::SeqCst))));
319
320 assert_eq!(c.resolve::<Counter>().unwrap().0, 0);
321 assert_eq!(c.resolve::<Counter>().unwrap().0, 1);
322 assert_eq!(BUILDS.load(Ordering::SeqCst), 2);
323 }
324
325 #[test]
326 fn singleton_builds_once_and_shares() {
327 static BUILDS: AtomicUsize = AtomicUsize::new(0);
328 let c = Container::new();
329 c.singleton(|_| {
330 BUILDS.fetch_add(1, Ordering::SeqCst);
331 Ok(Counter(7))
332 });
333
334 let a = c.resolve::<Counter>().unwrap();
335 let b = c.resolve::<Counter>().unwrap();
336 assert!(Arc::ptr_eq(&a, &b));
337 assert_eq!(BUILDS.load(Ordering::SeqCst), 1);
338 }
339
340 #[test]
341 fn factories_can_resolve_their_dependencies() {
342 let c = Container::new();
343 c.instance(Counter(5));
344 c.singleton(|c: &Container| Ok(Dependent(c.resolve::<Counter>()?.0 * 2)));
345 assert_eq!(c.resolve::<Dependent>().unwrap().0, 10);
346 }
347
348 #[test]
349 fn a_failing_factory_surfaces_its_error() {
350 let c = Container::new();
351 c.singleton(|_| Err::<Counter, _>(Error::internal("no database")));
352 assert_eq!(c.resolve::<Counter>().unwrap_err().message(), "no database");
353 }
354
355 #[test]
356 fn dependency_cycles_error_instead_of_deadlocking() {
357 let c = Container::new();
358 c.singleton(|c: &Container| Ok(Counter(c.resolve::<Dependent>()?.0)));
360 c.singleton(|c: &Container| Ok(Dependent(c.resolve::<Counter>()?.0)));
361
362 let err = c.resolve::<Counter>().unwrap_err();
363 assert!(err.message().contains("circular dependency"), "{}", err.message());
364 }
365
366 #[test]
367 fn a_singleton_that_resolves_itself_errors_rather_than_self_deadlocking() {
368 let c = Container::new();
371 c.singleton(|c: &Container| Ok(Counter(c.resolve::<Counter>()?.0)));
372
373 let err = c.resolve::<Counter>().unwrap_err();
374 assert!(err.message().contains("circular dependency"), "{}", err.message());
375 }
376
377 #[test]
378 fn a_panicking_factory_does_not_poison_later_resolutions() {
379 let c = Arc::new(Container::new());
380 c.bind(|_| -> Result<Counter> { panic!("boom") });
381
382 let panicked = {
383 let c = Arc::clone(&c);
384 std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
385 let _ = c.resolve::<Counter>();
386 }))
387 };
388 assert!(panicked.is_err());
389
390 c.forget::<Counter>();
393 c.instance(Counter(1));
394 assert_eq!(c.resolve::<Counter>().unwrap().0, 1);
395 }
396
397 #[test]
398 fn scoped_bindings_rebuild_after_a_flush() {
399 static BUILDS: AtomicUsize = AtomicUsize::new(0);
400 let c = Container::new();
401 c.scoped(|_| Ok(Counter(BUILDS.fetch_add(1, Ordering::SeqCst))));
402
403 assert_eq!(c.resolve::<Counter>().unwrap().0, 0);
404 assert_eq!(c.resolve::<Counter>().unwrap().0, 0);
405 c.flush_scoped();
406 assert_eq!(c.resolve::<Counter>().unwrap().0, 1);
407 }
408
409 #[test]
410 fn flush_scoped_leaves_singletons_alone() {
411 static BUILDS: AtomicUsize = AtomicUsize::new(0);
412 let c = Container::new();
413 c.singleton(|_| Ok(Counter(BUILDS.fetch_add(1, Ordering::SeqCst))));
414
415 assert_eq!(c.resolve::<Counter>().unwrap().0, 0);
416 c.flush_scoped();
417 assert_eq!(c.resolve::<Counter>().unwrap().0, 0);
418 }
419
420 #[test]
421 fn bound_and_forget_track_the_registry() {
422 let c = Container::new();
423 assert!(!c.bound::<Counter>());
424 c.instance(Counter(1));
425 assert!(c.bound::<Counter>());
426 c.forget::<Counter>();
427 assert!(!c.bound::<Counter>());
428 }
429
430 #[test]
431 fn resolution_is_safe_across_threads() {
432 let c = Arc::new(Container::new());
433 c.singleton(|_| Ok(Counter(99)));
434
435 let handles: Vec<_> = (0..8)
436 .map(|_| {
437 let c = Arc::clone(&c);
438 std::thread::spawn(move || c.resolve::<Counter>().unwrap())
439 })
440 .collect();
441
442 let first = c.resolve::<Counter>().unwrap();
443 for handle in handles {
444 assert!(Arc::ptr_eq(&first, &handle.join().unwrap()));
445 }
446 }
447}