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}