kit_rs/container/
mod.rs

1//! Application Container for Dependency Injection
2//!
3//! This module provides Laravel-like service container capabilities:
4//! - Singletons: shared instances across the application
5//! - Factories: new instance per resolution
6//! - Trait bindings: bind interfaces to implementations
7//! - Test faking: swap implementations in tests
8//! - Service Providers: bootstrap services with register/boot lifecycle
9//!
10//! # Example
11//!
12//! ```rust,ignore
13//! use kit::{App, service};
14//!
15//! // Define a service trait with auto-registration
16//! #[service(RealHttpClient)]
17//! pub trait HttpClient {
18//!     async fn get(&self, url: &str) -> Result<String, Error>;
19//! }
20//!
21//! // Or register manually
22//! App::bind::<dyn HttpClient>(Arc::new(RealHttpClient::new()));
23//!
24//! // Resolve anywhere in your app
25//! let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
26//! ```
27
28pub mod provider;
29pub mod testing;
30
31use std::any::{Any, TypeId};
32use std::cell::RefCell;
33use std::collections::HashMap;
34use std::sync::{Arc, OnceLock, RwLock};
35
36/// Global application container
37static APP_CONTAINER: OnceLock<RwLock<Container>> = OnceLock::new();
38
39// Thread-local test overrides for isolated testing
40thread_local! {
41    pub(crate) static TEST_CONTAINER: RefCell<Option<Container>> = const { RefCell::new(None) };
42}
43
44/// Binding types: either a singleton instance or a factory closure
45#[derive(Clone)]
46enum Binding {
47    /// Shared singleton instance - same instance returned every time
48    Singleton(Arc<dyn Any + Send + Sync>),
49
50    /// Factory closure - creates new instance each time
51    Factory(Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync>),
52}
53
54/// The main service container
55///
56/// Stores type-erased bindings keyed by TypeId. Supports both concrete types
57/// and trait objects (via Arc<dyn Trait>).
58pub struct Container {
59    /// Type bindings: TypeId -> Binding
60    bindings: HashMap<TypeId, Binding>,
61}
62
63impl Container {
64    /// Create a new empty container
65    pub fn new() -> Self {
66        Self {
67            bindings: HashMap::new(),
68        }
69    }
70
71    /// Register a singleton instance (shared across all resolutions)
72    ///
73    /// # Example
74    /// ```rust,ignore
75    /// container.singleton(DatabaseConnection::new(&url));
76    /// ```
77    pub fn singleton<T: Any + Send + Sync + 'static>(&mut self, instance: T) {
78        let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
79        self.bindings.insert(TypeId::of::<T>(), Binding::Singleton(arc));
80    }
81
82    /// Register a factory closure (new instance per resolution)
83    ///
84    /// # Example
85    /// ```rust,ignore
86    /// container.factory(|| RequestLogger::new());
87    /// ```
88    pub fn factory<T, F>(&mut self, factory: F)
89    where
90        T: Any + Send + Sync + 'static,
91        F: Fn() -> T + Send + Sync + 'static,
92    {
93        let wrapped: Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync> =
94            Arc::new(move || Arc::new(factory()) as Arc<dyn Any + Send + Sync>);
95        self.bindings
96            .insert(TypeId::of::<T>(), Binding::Factory(wrapped));
97    }
98
99    /// Bind a trait object to a concrete implementation (as singleton)
100    ///
101    /// This stores the value under `TypeId::of::<Arc<dyn Trait>>()` which allows
102    /// trait objects to be resolved via `make::<dyn Trait>()`.
103    ///
104    /// # Example
105    /// ```rust,ignore
106    /// container.bind::<dyn HttpClient>(RealHttpClient::new());
107    /// ```
108    pub fn bind<T: ?Sized + Send + Sync + 'static>(&mut self, instance: Arc<T>) {
109        // Store under TypeId of Arc<T> (works for both concrete and trait objects)
110        let type_id = TypeId::of::<Arc<T>>();
111        let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
112        self.bindings.insert(type_id, Binding::Singleton(arc));
113    }
114
115    /// Bind a trait object to a factory
116    ///
117    /// # Example
118    /// ```rust,ignore
119    /// container.bind_factory::<dyn HttpClient>(|| Arc::new(RealHttpClient::new()));
120    /// ```
121    pub fn bind_factory<T: ?Sized + Send + Sync + 'static, F>(&mut self, factory: F)
122    where
123        F: Fn() -> Arc<T> + Send + Sync + 'static,
124    {
125        let type_id = TypeId::of::<Arc<T>>();
126        let wrapped: Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync> =
127            Arc::new(move || Arc::new(factory()) as Arc<dyn Any + Send + Sync>);
128        self.bindings.insert(type_id, Binding::Factory(wrapped));
129    }
130
131    /// Resolve a concrete type (requires Clone)
132    ///
133    /// # Example
134    /// ```rust,ignore
135    /// let db: DatabaseConnection = container.get().unwrap();
136    /// ```
137    pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<T> {
138        match self.bindings.get(&TypeId::of::<T>())? {
139            Binding::Singleton(arc) => arc.downcast_ref::<T>().cloned(),
140            Binding::Factory(factory) => {
141                let arc = factory();
142                arc.downcast_ref::<T>().cloned()
143            }
144        }
145    }
146
147    /// Resolve a trait binding - returns Arc<T>
148    ///
149    /// # Example
150    /// ```rust,ignore
151    /// let client: Arc<dyn HttpClient> = container.make::<dyn HttpClient>().unwrap();
152    /// ```
153    pub fn make<T: ?Sized + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
154        let type_id = TypeId::of::<Arc<T>>();
155        match self.bindings.get(&type_id)? {
156            Binding::Singleton(arc) => {
157                // The stored value is Arc<Arc<T>>, so we downcast and clone the inner Arc
158                arc.downcast_ref::<Arc<T>>().cloned()
159            }
160            Binding::Factory(factory) => {
161                let arc = factory();
162                arc.downcast_ref::<Arc<T>>().cloned()
163            }
164        }
165    }
166
167    /// Check if a concrete type is registered
168    pub fn has<T: Any + 'static>(&self) -> bool {
169        self.bindings.contains_key(&TypeId::of::<T>())
170    }
171
172    /// Check if a trait binding is registered
173    pub fn has_binding<T: ?Sized + 'static>(&self) -> bool {
174        self.bindings.contains_key(&TypeId::of::<Arc<T>>())
175    }
176}
177
178impl Default for Container {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184/// Application container facade
185///
186/// Provides static methods for service registration and resolution.
187/// Uses a global container with thread-local test overrides.
188///
189/// # Example
190///
191/// ```rust,ignore
192/// use kit::App;
193///
194/// // Register services at startup
195/// App::singleton(DatabaseConnection::new(&url));
196/// App::bind::<dyn HttpClient>(Arc::new(RealHttpClient::new()));
197///
198/// // Resolve anywhere
199/// let db: DatabaseConnection = App::get().unwrap();
200/// let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
201/// ```
202pub struct App;
203
204impl App {
205    /// Initialize the application container
206    ///
207    /// Should be called once at application startup. This is automatically
208    /// called by `Server::from_config()`.
209    pub fn init() {
210        APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
211    }
212
213    /// Register a singleton instance (shared across all resolutions)
214    ///
215    /// # Example
216    /// ```rust,ignore
217    /// App::singleton(DatabaseConnection::new(&url));
218    /// ```
219    pub fn singleton<T: Any + Send + Sync + 'static>(instance: T) {
220        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
221        if let Ok(mut c) = container.write() {
222            c.singleton(instance);
223        }
224    }
225
226    /// Register a factory binding (new instance per resolution)
227    ///
228    /// # Example
229    /// ```rust,ignore
230    /// App::factory(|| RequestLogger::new());
231    /// ```
232    pub fn factory<T, F>(factory: F)
233    where
234        T: Any + Send + Sync + 'static,
235        F: Fn() -> T + Send + Sync + 'static,
236    {
237        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
238        if let Ok(mut c) = container.write() {
239            c.factory(factory);
240        }
241    }
242
243    /// Bind a trait object to a concrete implementation (as singleton)
244    ///
245    /// # Example
246    /// ```rust,ignore
247    /// App::bind::<dyn HttpClient>(Arc::new(RealHttpClient::new()));
248    /// ```
249    pub fn bind<T: ?Sized + Send + Sync + 'static>(instance: Arc<T>) {
250        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
251        if let Ok(mut c) = container.write() {
252            c.bind(instance);
253        }
254    }
255
256    /// Bind a trait object to a factory
257    ///
258    /// # Example
259    /// ```rust,ignore
260    /// App::bind_factory::<dyn HttpClient>(|| Arc::new(RealHttpClient::new()));
261    /// ```
262    pub fn bind_factory<T: ?Sized + Send + Sync + 'static, F>(factory: F)
263    where
264        F: Fn() -> Arc<T> + Send + Sync + 'static,
265    {
266        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
267        if let Ok(mut c) = container.write() {
268            c.bind_factory(factory);
269        }
270    }
271
272    /// Resolve a concrete type
273    ///
274    /// Checks test overrides first, then falls back to global container.
275    ///
276    /// # Example
277    /// ```rust,ignore
278    /// let db: DatabaseConnection = App::get().unwrap();
279    /// ```
280    pub fn get<T: Any + Send + Sync + Clone + 'static>() -> Option<T> {
281        // Check test overrides first (thread-local)
282        let test_result = TEST_CONTAINER.with(|c| {
283            c.borrow()
284                .as_ref()
285                .and_then(|container| container.get::<T>())
286        });
287
288        if test_result.is_some() {
289            return test_result;
290        }
291
292        // Fall back to global container
293        let container = APP_CONTAINER.get()?;
294        container.read().ok()?.get::<T>()
295    }
296
297    /// Resolve a trait binding - returns Arc<T>
298    ///
299    /// Checks test overrides first, then falls back to global container.
300    ///
301    /// # Example
302    /// ```rust,ignore
303    /// let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
304    /// ```
305    pub fn make<T: ?Sized + Send + Sync + 'static>() -> Option<Arc<T>> {
306        // Check test overrides first (thread-local)
307        let test_result = TEST_CONTAINER.with(|c| {
308            c.borrow()
309                .as_ref()
310                .and_then(|container| container.make::<T>())
311        });
312
313        if test_result.is_some() {
314            return test_result;
315        }
316
317        // Fall back to global container
318        let container = APP_CONTAINER.get()?;
319        container.read().ok()?.make::<T>()
320    }
321
322    /// Check if a concrete type is registered
323    pub fn has<T: Any + 'static>() -> bool {
324        // Check test container first
325        let in_test = TEST_CONTAINER.with(|c| {
326            c.borrow()
327                .as_ref()
328                .map(|container| container.has::<T>())
329                .unwrap_or(false)
330        });
331
332        if in_test {
333            return true;
334        }
335
336        APP_CONTAINER
337            .get()
338            .and_then(|c| c.read().ok())
339            .map(|c| c.has::<T>())
340            .unwrap_or(false)
341    }
342
343    /// Check if a trait binding is registered
344    pub fn has_binding<T: ?Sized + 'static>() -> bool {
345        // Check test container first
346        let in_test = TEST_CONTAINER.with(|c| {
347            c.borrow()
348                .as_ref()
349                .map(|container| container.has_binding::<T>())
350                .unwrap_or(false)
351        });
352
353        if in_test {
354            return true;
355        }
356
357        APP_CONTAINER
358            .get()
359            .and_then(|c| c.read().ok())
360            .map(|c| c.has_binding::<T>())
361            .unwrap_or(false)
362    }
363
364    /// Boot all auto-registered services
365    ///
366    /// This registers all services marked with `#[service(ConcreteType)]`.
367    /// Called automatically by `Server::from_config()`.
368    pub fn boot_services() {
369        provider::bootstrap();
370    }
371}