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