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, bind, singleton, 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 using macros
22//! bind!(dyn HttpClient, RealHttpClient::new());
23//! singleton!(CacheService::new());
24//!
25//! // Resolve anywhere in your app
26//! let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
27//! ```
28
29pub mod provider;
30pub mod testing;
31
32use std::any::{Any, TypeId};
33use std::cell::RefCell;
34use std::collections::HashMap;
35use std::sync::{Arc, OnceLock, RwLock};
36
37/// Global application container
38static APP_CONTAINER: OnceLock<RwLock<Container>> = OnceLock::new();
39
40// Thread-local test overrides for isolated testing
41thread_local! {
42    pub(crate) static TEST_CONTAINER: RefCell<Option<Container>> = const { RefCell::new(None) };
43}
44
45/// Binding types: either a singleton instance or a factory closure
46#[derive(Clone)]
47enum Binding {
48    /// Shared singleton instance - same instance returned every time
49    Singleton(Arc<dyn Any + Send + Sync>),
50
51    /// Factory closure - creates new instance each time
52    Factory(Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync>),
53}
54
55/// The main service container
56///
57/// Stores type-erased bindings keyed by TypeId. Supports both concrete types
58/// and trait objects (via Arc<dyn Trait>).
59pub struct Container {
60    /// Type bindings: TypeId -> Binding
61    bindings: HashMap<TypeId, Binding>,
62}
63
64impl Container {
65    /// Create a new empty container
66    pub fn new() -> Self {
67        Self {
68            bindings: HashMap::new(),
69        }
70    }
71
72    /// Register a singleton instance (shared across all resolutions)
73    ///
74    /// # Example
75    /// ```rust,ignore
76    /// container.singleton(DatabaseConnection::new(&url));
77    /// ```
78    pub fn singleton<T: Any + Send + Sync + 'static>(&mut self, instance: T) {
79        let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
80        self.bindings
81            .insert(TypeId::of::<T>(), Binding::Singleton(arc));
82    }
83
84    /// Register a factory closure (new instance per resolution)
85    ///
86    /// # Example
87    /// ```rust,ignore
88    /// container.factory(|| RequestLogger::new());
89    /// ```
90    pub fn factory<T, F>(&mut self, factory: F)
91    where
92        T: Any + Send + Sync + 'static,
93        F: Fn() -> T + Send + Sync + 'static,
94    {
95        let wrapped: Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync> =
96            Arc::new(move || Arc::new(factory()) as Arc<dyn Any + Send + Sync>);
97        self.bindings
98            .insert(TypeId::of::<T>(), Binding::Factory(wrapped));
99    }
100
101    /// Bind a trait object to a concrete implementation (as singleton)
102    ///
103    /// This stores the value under `TypeId::of::<Arc<dyn Trait>>()` which allows
104    /// trait objects to be resolved via `make::<dyn Trait>()`.
105    ///
106    /// # Example
107    /// ```rust,ignore
108    /// container.bind::<dyn HttpClient>(RealHttpClient::new());
109    /// ```
110    pub fn bind<T: ?Sized + Send + Sync + 'static>(&mut self, instance: Arc<T>) {
111        // Store under TypeId of Arc<T> (works for both concrete and trait objects)
112        let type_id = TypeId::of::<Arc<T>>();
113        let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
114        self.bindings.insert(type_id, Binding::Singleton(arc));
115    }
116
117    /// Bind a trait object to a factory
118    ///
119    /// # Example
120    /// ```rust,ignore
121    /// container.bind_factory::<dyn HttpClient>(|| Arc::new(RealHttpClient::new()));
122    /// ```
123    pub fn bind_factory<T: ?Sized + Send + Sync + 'static, F>(&mut self, factory: F)
124    where
125        F: Fn() -> Arc<T> + Send + Sync + 'static,
126    {
127        let type_id = TypeId::of::<Arc<T>>();
128        let wrapped: Arc<dyn Fn() -> Arc<dyn Any + Send + Sync> + Send + Sync> =
129            Arc::new(move || Arc::new(factory()) as Arc<dyn Any + Send + Sync>);
130        self.bindings.insert(type_id, Binding::Factory(wrapped));
131    }
132
133    /// Resolve a concrete type (requires Clone)
134    ///
135    /// # Example
136    /// ```rust,ignore
137    /// let db: DatabaseConnection = container.get().unwrap();
138    /// ```
139    pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<T> {
140        match self.bindings.get(&TypeId::of::<T>())? {
141            Binding::Singleton(arc) => arc.downcast_ref::<T>().cloned(),
142            Binding::Factory(factory) => {
143                let arc = factory();
144                arc.downcast_ref::<T>().cloned()
145            }
146        }
147    }
148
149    /// Resolve a trait binding - returns Arc<T>
150    ///
151    /// # Example
152    /// ```rust,ignore
153    /// let client: Arc<dyn HttpClient> = container.make::<dyn HttpClient>().unwrap();
154    /// ```
155    pub fn make<T: ?Sized + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
156        let type_id = TypeId::of::<Arc<T>>();
157        match self.bindings.get(&type_id)? {
158            Binding::Singleton(arc) => {
159                // The stored value is Arc<Arc<T>>, so we downcast and clone the inner Arc
160                arc.downcast_ref::<Arc<T>>().cloned()
161            }
162            Binding::Factory(factory) => {
163                let arc = factory();
164                arc.downcast_ref::<Arc<T>>().cloned()
165            }
166        }
167    }
168
169    /// Check if a concrete type is registered
170    pub fn has<T: Any + 'static>(&self) -> bool {
171        self.bindings.contains_key(&TypeId::of::<T>())
172    }
173
174    /// Check if a trait binding is registered
175    pub fn has_binding<T: ?Sized + 'static>(&self) -> bool {
176        self.bindings.contains_key(&TypeId::of::<Arc<T>>())
177    }
178}
179
180impl Default for Container {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186/// Application container facade
187///
188/// Provides static methods for service registration and resolution.
189/// Uses a global container with thread-local test overrides.
190///
191/// # Example
192///
193/// ```rust,ignore
194/// use kit::{App, bind, singleton};
195///
196/// // Register services at startup using macros
197/// singleton!(DatabaseConnection::new(&url));
198/// bind!(dyn HttpClient, RealHttpClient::new());
199///
200/// // Resolve anywhere
201/// let db: DatabaseConnection = App::get().unwrap();
202/// let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
203/// ```
204pub struct App;
205
206impl App {
207    /// Initialize the application container
208    ///
209    /// Should be called once at application startup. This is automatically
210    /// called by `Server::from_config()`.
211    pub fn init() {
212        APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
213    }
214
215    /// Register a singleton instance (shared across all resolutions)
216    ///
217    /// # Example
218    /// ```rust,ignore
219    /// App::singleton(DatabaseConnection::new(&url));
220    /// ```
221    pub fn singleton<T: Any + Send + Sync + 'static>(instance: T) {
222        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
223        if let Ok(mut c) = container.write() {
224            c.singleton(instance);
225        }
226    }
227
228    /// Register a factory binding (new instance per resolution)
229    ///
230    /// # Example
231    /// ```rust,ignore
232    /// App::factory(|| RequestLogger::new());
233    /// ```
234    pub fn factory<T, F>(factory: F)
235    where
236        T: Any + Send + Sync + 'static,
237        F: Fn() -> T + Send + Sync + 'static,
238    {
239        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
240        if let Ok(mut c) = container.write() {
241            c.factory(factory);
242        }
243    }
244
245    /// Bind a trait object to a concrete implementation (as singleton)
246    ///
247    /// # Example
248    /// ```rust,ignore
249    /// App::bind::<dyn HttpClient>(Arc::new(RealHttpClient::new()));
250    /// ```
251    pub fn bind<T: ?Sized + Send + Sync + 'static>(instance: Arc<T>) {
252        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
253        if let Ok(mut c) = container.write() {
254            c.bind(instance);
255        }
256    }
257
258    /// Bind a trait object to a factory
259    ///
260    /// # Example
261    /// ```rust,ignore
262    /// App::bind_factory::<dyn HttpClient>(|| Arc::new(RealHttpClient::new()));
263    /// ```
264    pub fn bind_factory<T: ?Sized + Send + Sync + 'static, F>(factory: F)
265    where
266        F: Fn() -> Arc<T> + Send + Sync + 'static,
267    {
268        let container = APP_CONTAINER.get_or_init(|| RwLock::new(Container::new()));
269        if let Ok(mut c) = container.write() {
270            c.bind_factory(factory);
271        }
272    }
273
274    /// Resolve a concrete type
275    ///
276    /// Checks test overrides first, then falls back to global container.
277    ///
278    /// # Example
279    /// ```rust,ignore
280    /// let db: DatabaseConnection = App::get().unwrap();
281    /// ```
282    pub fn get<T: Any + Send + Sync + Clone + 'static>() -> Option<T> {
283        // Check test overrides first (thread-local)
284        let test_result = TEST_CONTAINER.with(|c| {
285            c.borrow()
286                .as_ref()
287                .and_then(|container| container.get::<T>())
288        });
289
290        if test_result.is_some() {
291            return test_result;
292        }
293
294        // Fall back to global container
295        let container = APP_CONTAINER.get()?;
296        container.read().ok()?.get::<T>()
297    }
298
299    /// Resolve a trait binding - returns Arc<T>
300    ///
301    /// Checks test overrides first, then falls back to global container.
302    ///
303    /// # Example
304    /// ```rust,ignore
305    /// let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
306    /// ```
307    pub fn make<T: ?Sized + Send + Sync + 'static>() -> Option<Arc<T>> {
308        // Check test overrides first (thread-local)
309        let test_result = TEST_CONTAINER.with(|c| {
310            c.borrow()
311                .as_ref()
312                .and_then(|container| container.make::<T>())
313        });
314
315        if test_result.is_some() {
316            return test_result;
317        }
318
319        // Fall back to global container
320        let container = APP_CONTAINER.get()?;
321        container.read().ok()?.make::<T>()
322    }
323
324    /// Resolve a concrete type, returning an error if not found
325    ///
326    /// This allows using the `?` operator in controllers and services for
327    /// automatic error propagation with proper HTTP responses.
328    ///
329    /// # Example
330    /// ```rust,ignore
331    /// pub async fn index(_req: Request) -> Response {
332    ///     let service = App::resolve::<MyService>()?;
333    ///     // ...
334    /// }
335    /// ```
336    pub fn resolve<T: Any + Send + Sync + Clone + 'static>(
337    ) -> Result<T, crate::error::FrameworkError> {
338        Self::get::<T>().ok_or_else(crate::error::FrameworkError::service_not_found::<T>)
339    }
340
341    /// Resolve a trait binding, returning an error if not found
342    ///
343    /// This allows using the `?` operator for trait object resolution.
344    ///
345    /// # Example
346    /// ```rust,ignore
347    /// let client: Arc<dyn HttpClient> = App::resolve_make::<dyn HttpClient>()?;
348    /// ```
349    pub fn resolve_make<T: ?Sized + Send + Sync + 'static>(
350    ) -> Result<Arc<T>, crate::error::FrameworkError> {
351        Self::make::<T>().ok_or_else(crate::error::FrameworkError::service_not_found::<T>)
352    }
353
354    /// Check if a concrete type is registered
355    pub fn has<T: Any + 'static>() -> bool {
356        // Check test container first
357        let in_test = TEST_CONTAINER.with(|c| {
358            c.borrow()
359                .as_ref()
360                .map(|container| container.has::<T>())
361                .unwrap_or(false)
362        });
363
364        if in_test {
365            return true;
366        }
367
368        APP_CONTAINER
369            .get()
370            .and_then(|c| c.read().ok())
371            .map(|c| c.has::<T>())
372            .unwrap_or(false)
373    }
374
375    /// Check if a trait binding is registered
376    pub fn has_binding<T: ?Sized + 'static>() -> bool {
377        // Check test container first
378        let in_test = TEST_CONTAINER.with(|c| {
379            c.borrow()
380                .as_ref()
381                .map(|container| container.has_binding::<T>())
382                .unwrap_or(false)
383        });
384
385        if in_test {
386            return true;
387        }
388
389        APP_CONTAINER
390            .get()
391            .and_then(|c| c.read().ok())
392            .map(|c| c.has_binding::<T>())
393            .unwrap_or(false)
394    }
395
396    /// Boot all auto-registered services
397    ///
398    /// This registers all services marked with `#[service(ConcreteType)]`.
399    /// Called automatically by `Server::from_config()`.
400    pub fn boot_services() {
401        provider::bootstrap();
402    }
403}
404
405/// Bind a trait to a singleton implementation (auto-wraps in Arc)
406///
407/// # Example
408/// ```rust,ignore
409/// bind!(dyn Database, PostgresDB::connect(&db_url));
410/// bind!(dyn HttpClient, RealHttpClient::new());
411/// ```
412#[macro_export]
413macro_rules! bind {
414    ($trait:ty, $instance:expr) => {
415        $crate::App::bind::<$trait>(::std::sync::Arc::new($instance) as ::std::sync::Arc<$trait>)
416    };
417}
418
419/// Bind a trait to a factory (auto-wraps in Arc, new instance each resolution)
420///
421/// # Example
422/// ```rust,ignore
423/// bind_factory!(dyn HttpClient, || RealHttpClient::new());
424/// ```
425#[macro_export]
426macro_rules! bind_factory {
427    ($trait:ty, $factory:expr) => {{
428        let f = $factory;
429        $crate::App::bind_factory::<$trait, _>(move || {
430            ::std::sync::Arc::new(f()) as ::std::sync::Arc<$trait>
431        })
432    }};
433}
434
435/// Register a singleton instance (concrete type)
436///
437/// # Example
438/// ```rust,ignore
439/// singleton!(DatabaseConnection::new(&url));
440/// ```
441#[macro_export]
442macro_rules! singleton {
443    ($instance:expr) => {
444        $crate::App::singleton($instance)
445    };
446}
447
448/// Register a factory (concrete type, new instance each resolution)
449///
450/// # Example
451/// ```rust,ignore
452/// factory!(|| RequestLogger::new());
453/// ```
454#[macro_export]
455macro_rules! factory {
456    ($factory:expr) => {
457        $crate::App::factory($factory)
458    };
459}