Skip to main content

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