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