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}