nestforge_core/container.rs
1use std::{
2 any::{Any, TypeId},
3 collections::HashMap,
4 sync::{Arc, RwLock},
5};
6
7use anyhow::{anyhow, Result};
8
9/**
10* Container = our tiny dependency injection store (v1).
11*
12* What it does:
13* - stores values by type (TypeId).
14* - lets us register services/config once.
15* - lets us resolve them later.
16*
17* Why Arc?
18* - so multiple parts of the app can share the same service safely.
19*
20* Why RwLock?
21* - allows safe reads/writes across threads.
22* - write when registering.
23* - read when resolving.
24*/
25
26#[derive(Clone, Default)]
27pub struct Container {
28 inner: Arc<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>>,
29}
30
31impl Container {
32 /**
33 * Nice helper constructor.
34 * Same as Default, just cleaner to read in app code.
35 */
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 /**
41 * Register a value/service into the container.
42 *
43 * Example:
44 * container.register(AppConfig { ... })?;
45 *
46 * Rules:
47 * - T must be thread-safe (Send + Sync).
48 * - T must be 'static because we store it for the app lifetime.
49 */
50 pub fn register<T>(&self, value: T) -> Result<()>
51 where
52 T: Send + Sync + 'static,
53 {
54 let mut map = self
55 .inner
56 .write()
57 .map_err(|_| anyhow!("Container write lock poisoned"))?;
58
59 let type_id = TypeId::of::<T>();
60
61 /* Prevent accidental duplicate registration of the same type. */
62 if map.contains_key(&type_id) {
63 return Err(anyhow!(
64 "Type already registered: {}",
65 std::any::type_name::<T>()
66 ));
67 }
68
69 /* Store as Arc<dyn Any> so we can keep different types in one map. */
70 map.insert(type_id, Arc::new(value));
71 Ok(())
72 }
73
74 /**
75 * Resolve (get back) a registered value/service by type.
76 *
77 * Example:
78 * let config = container.resolve::<AppConfig>()?;
79 *
80 * Returns Arc<T> so the caller can clone/share it cheaply.
81 */
82 pub fn resolve<T>(&self) -> Result<Arc<T>>
83 where
84 T: Send + Sync + 'static,
85 {
86 let map = self
87 .inner
88 .read()
89 .map_err(|_| anyhow!("Container read lock poisoned"))?;
90
91 let value = map
92 .get(&TypeId::of::<T>())
93 .ok_or_else(|| anyhow!("Type not registered: {}", std::any::type_name::<T>()))?
94 .clone();
95
96 /*
97 * We stored the value as dyn Any, so now we downcast it back to the real type T.
98 * If downcast fails, the type in the map doesn’t match what we asked for.
99 */
100 value.downcast::<T>()
101 .map_err(|_| anyhow!("Failed to downcast: {}", std::any::type_name::<T>()))
102 }
103}