dependency_injector/typed.rs
1//! Compile-Time Type-Safe Container Builder
2//!
3//! This module provides a type-state container builder that ensures
4//! type safety at compile time using Rust's type system.
5//!
6//! # Features
7//!
8//! - **Zero runtime overhead**: Type checking happens at compile time
9//! - **Builder pattern**: Fluent API that tracks registered types
10//! - **Dependency verification**: Ensure deps are registered before dependents
11//!
12//! # Example
13//!
14//! ```rust
15//! use dependency_injector::typed::TypedBuilder;
16//!
17//! #[derive(Clone)]
18//! struct Database { url: String }
19//!
20//! #[derive(Clone)]
21//! struct Cache { size: usize }
22//!
23//! // Build with compile-time type tracking
24//! let container = TypedBuilder::new()
25//! .singleton(Database { url: "postgres://localhost".into() })
26//! .singleton(Cache { size: 1024 })
27//! .build();
28//!
29//! // Type-safe resolution
30//! let db = container.get::<Database>();
31//! let cache = container.get::<Cache>();
32//! ```
33//!
34//! # Compile-Time Dependency Declaration
35//!
36//! ```rust
37//! use dependency_injector::typed::{TypedBuilder, DeclaresDeps};
38//!
39//! #[derive(Clone)]
40//! struct Database;
41//!
42//! #[derive(Clone)]
43//! struct UserService;
44//!
45//! // Declare that UserService depends on Database
46//! impl DeclaresDeps for UserService {
47//! fn dependency_names() -> &'static [&'static str] {
48//! &["Database"]
49//! }
50//! }
51//!
52//! // Register deps first, then dependent
53//! let container = TypedBuilder::new()
54//! .singleton(Database)
55//! .with_deps(UserService)
56//! .build();
57//! ```
58
59use crate::{Container, Injectable};
60use std::marker::PhantomData;
61use std::sync::Arc;
62
63// =============================================================================
64// Registry Marker Types
65// =============================================================================
66
67/// Marker for a registered type in the builder's registry.
68pub struct Reg<T, Rest>(PhantomData<(T, Rest)>);
69
70/// Trait for checking if type T is at the head of a registry.
71pub trait HasType<T: Injectable> {}
72
73impl<T: Injectable, Rest> HasType<T> for Reg<T, Rest> {}
74
75/// Trait for services that declare their dependencies as a type-level list.
76///
77/// The `Dependencies` associated type is a [`Reg`] chain terminated by `()`,
78/// mirroring the registry type built by [`TypedBuilder`]. Implement it by
79/// hand, or derive it with `#[derive(TypedRequire)]` when the `derive`
80/// feature is enabled.
81///
82/// This trait is declaration-only: nothing in the library consumes
83/// `Dependencies` at runtime. It exists so the derived list can be checked
84/// against a [`TypedBuilder`] registry type at compile time (see
85/// `tests/typed_require.rs`). It is distinct from [`DeclaresDeps`], which
86/// carries runtime dependency *names* for the builder's `with_deps`
87/// verification path — a service may implement either or both.
88///
89/// # Example
90///
91/// ```rust
92/// use dependency_injector::typed::{Reg, Require};
93///
94/// #[derive(Clone)]
95/// struct Database;
96///
97/// #[derive(Clone)]
98/// struct Cache;
99///
100/// #[derive(Clone)]
101/// struct UserService;
102///
103/// impl Require for UserService {
104/// type Dependencies = Reg<Database, Reg<Cache, ()>>;
105/// }
106/// ```
107pub trait Require {
108 /// Type-level list of required dependencies: `Reg<T1, Reg<T2, ... ()>>`.
109 type Dependencies;
110}
111
112// =============================================================================
113// Type-State Builder
114// =============================================================================
115
116/// A type-state container builder.
117///
118/// The type parameter `R` tracks all registered types at compile time.
119pub struct TypedBuilder<R = ()> {
120 container: Container,
121 _registry: PhantomData<R>,
122}
123
124impl TypedBuilder<()> {
125 /// Create a new typed builder.
126 #[inline]
127 pub fn new() -> Self {
128 Self {
129 container: Container::new(),
130 _registry: PhantomData,
131 }
132 }
133
134 /// Create with pre-allocated capacity.
135 #[inline]
136 pub fn with_capacity(capacity: usize) -> Self {
137 Self {
138 container: Container::with_capacity(capacity),
139 _registry: PhantomData,
140 }
141 }
142}
143
144impl Default for TypedBuilder<()> {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl<R> TypedBuilder<R> {
151 /// Register a singleton service.
152 #[inline]
153 pub fn singleton<T: Injectable>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
154 self.container.singleton(instance);
155 TypedBuilder {
156 container: self.container,
157 _registry: PhantomData,
158 }
159 }
160
161 /// Register a lazy singleton.
162 #[inline]
163 pub fn lazy<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
164 where
165 F: Fn() -> T + Send + Sync + 'static,
166 {
167 self.container.lazy(factory);
168 TypedBuilder {
169 container: self.container,
170 _registry: PhantomData,
171 }
172 }
173
174 /// Register a transient service.
175 #[inline]
176 pub fn transient<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
177 where
178 F: Fn() -> T + Send + Sync + 'static,
179 {
180 self.container.transient(factory);
181 TypedBuilder {
182 container: self.container,
183 _registry: PhantomData,
184 }
185 }
186
187 /// Build the typed container.
188 #[inline]
189 pub fn build(self) -> TypedContainer<R> {
190 self.container.lock();
191 TypedContainer {
192 container: self.container,
193 _registry: PhantomData,
194 }
195 }
196
197 /// Build and return the underlying container.
198 #[inline]
199 pub fn build_dynamic(self) -> Container {
200 self.container.lock();
201 self.container
202 }
203
204 /// Access the underlying container.
205 #[inline]
206 pub fn inner(&self) -> &Container {
207 &self.container
208 }
209}
210
211// =============================================================================
212// Dependency Declaration
213// =============================================================================
214
215// =============================================================================
216// Dependency Declaration (Runtime-Verified)
217// =============================================================================
218
219/// Trait for services that declare their dependencies.
220///
221/// Use with `with_deps` to get documentation-level dependency declaration.
222/// Runtime verification ensures all dependencies are present.
223///
224/// Note: Full compile-time dependency verification requires proc macros
225/// or unstable Rust features. This provides a documentation/runtime hybrid.
226pub trait DeclaresDeps: Injectable {
227 /// List of dependency type names (for documentation and debugging).
228 fn dependency_names() -> &'static [&'static str] {
229 &[]
230 }
231}
232
233impl<R> TypedBuilder<R> {
234 /// Register a service (alias for singleton with deps intent).
235 ///
236 /// Note: This method is the same as `singleton` but signals that
237 /// the service has dependencies that should already be registered.
238 #[inline]
239 pub fn with_deps<T: DeclaresDeps>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
240 self.singleton(instance)
241 }
242
243 /// Register a lazy service with deps intent.
244 #[inline]
245 pub fn lazy_with_deps<T: DeclaresDeps, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
246 where
247 F: Fn() -> T + Send + Sync + 'static,
248 {
249 self.lazy(factory)
250 }
251}
252
253// Dummy traits for backwards compatibility
254pub trait VerifyDeps<D> {}
255impl<R, D> VerifyDeps<D> for R {}
256
257// =============================================================================
258// Typed Container
259// =============================================================================
260
261/// A container with compile-time type tracking.
262///
263/// The type parameter tracks what was registered, enabling
264/// compile-time verification of service access.
265pub struct TypedContainer<R> {
266 container: Container,
267 _registry: PhantomData<R>,
268}
269
270impl<R> TypedContainer<R> {
271 /// Resolve a service by type.
272 ///
273 /// Uses the dynamic container internally but provides type-safe API.
274 #[inline]
275 pub fn get<T: Injectable>(&self) -> Arc<T> {
276 self.container
277 .get::<T>()
278 .expect("TypedContainer: service not found (registration mismatch)")
279 }
280
281 /// Try to resolve a service.
282 #[inline]
283 pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
284 self.container.try_get::<T>()
285 }
286
287 /// Check if service exists.
288 #[inline]
289 pub fn contains<T: Injectable>(&self) -> bool {
290 self.container.contains::<T>()
291 }
292
293 /// Create a dynamic child scope.
294 #[inline]
295 pub fn scope(&self) -> Container {
296 self.container.scope()
297 }
298
299 /// Access the underlying container.
300 #[inline]
301 pub fn inner(&self) -> &Container {
302 &self.container
303 }
304
305 /// Convert to the underlying container.
306 #[inline]
307 pub fn into_inner(self) -> Container {
308 self.container
309 }
310}
311
312impl<R> Clone for TypedContainer<R> {
313 fn clone(&self) -> Self {
314 Self {
315 container: self.container.clone(),
316 _registry: PhantomData,
317 }
318 }
319}
320
321impl<R> std::fmt::Debug for TypedContainer<R> {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 f.debug_struct("TypedContainer")
324 .field("inner", &self.container)
325 .finish()
326 }
327}
328
329// =============================================================================
330// Backward Compatibility Aliases
331// =============================================================================
332
333/// Alias for HasType trait.
334pub trait Has<T: Injectable>: HasType<T> {}
335impl<T: Injectable, R: HasType<T>> Has<T> for R {}
336
337/// Alias for HasType trait.
338pub trait HasService<T: Injectable>: HasType<T> {}
339impl<T: Injectable, R: HasType<T>> HasService<T> for R {}
340
341// Dummy trait for DepsPresent compatibility
342pub trait DepsPresent<D> {}
343impl<R, D> DepsPresent<D> for R {}
344
345// =============================================================================
346// Tests
347// =============================================================================
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[derive(Clone)]
354 struct Database {
355 url: String,
356 }
357
358 #[derive(Clone)]
359 struct Cache {
360 size: usize,
361 }
362
363 #[derive(Clone)]
364 struct UserService;
365
366 impl DeclaresDeps for UserService {
367 fn dependency_names() -> &'static [&'static str] {
368 &["Database", "Cache"]
369 }
370 }
371
372 #[test]
373 fn test_typed_builder_basic() {
374 let container = TypedBuilder::new()
375 .singleton(Database {
376 url: "postgres://localhost".into(),
377 })
378 .singleton(Cache { size: 1024 })
379 .build();
380
381 let db = container.get::<Database>();
382 let cache = container.get::<Cache>();
383
384 assert_eq!(db.url, "postgres://localhost");
385 assert_eq!(cache.size, 1024);
386 }
387
388 #[test]
389 fn test_typed_builder_lazy() {
390 let container = TypedBuilder::new()
391 .lazy(|| Database {
392 url: "lazy://created".into(),
393 })
394 .build();
395
396 let db = container.get::<Database>();
397 assert_eq!(db.url, "lazy://created");
398 }
399
400 #[test]
401 fn test_typed_builder_transient() {
402 use std::sync::atomic::{AtomicU32, Ordering};
403
404 static COUNTER: AtomicU32 = AtomicU32::new(0);
405
406 #[derive(Clone)]
407 struct Counter(u32);
408
409 let container = TypedBuilder::new()
410 .transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)))
411 .build();
412
413 let c1 = container.get::<Counter>();
414 let c2 = container.get::<Counter>();
415
416 assert_ne!(c1.0, c2.0);
417 }
418
419 #[test]
420 fn test_typed_container_clone() {
421 let container = TypedBuilder::new()
422 .singleton(Database { url: "test".into() })
423 .build();
424
425 let container2 = container.clone();
426
427 let db1 = container.get::<Database>();
428 let db2 = container2.get::<Database>();
429
430 assert!(Arc::ptr_eq(&db1, &db2));
431 }
432
433 #[test]
434 fn test_with_dependencies() {
435 // Register deps first, then dependent service
436 let container = TypedBuilder::new()
437 .singleton(Database { url: "pg".into() })
438 .singleton(Cache { size: 100 })
439 .with_deps(UserService)
440 .build();
441
442 let _ = container.get::<UserService>();
443 }
444
445 #[test]
446 fn test_many_services() {
447 #[derive(Clone)]
448 struct S1;
449 #[derive(Clone)]
450 struct S2;
451 #[derive(Clone)]
452 struct S3;
453 #[derive(Clone)]
454 struct S4;
455 #[derive(Clone)]
456 struct S5;
457
458 let container = TypedBuilder::new()
459 .singleton(S1)
460 .singleton(S2)
461 .singleton(S3)
462 .singleton(S4)
463 .singleton(S5)
464 .build();
465
466 let _ = container.get::<S1>();
467 let _ = container.get::<S2>();
468 let _ = container.get::<S3>();
469 let _ = container.get::<S4>();
470 let _ = container.get::<S5>();
471 }
472
473 #[test]
474 fn test_scope_from_typed() {
475 let container = TypedBuilder::new()
476 .singleton(Database { url: "root".into() })
477 .build();
478
479 let child = container.scope();
480 child.singleton(Cache { size: 256 });
481
482 assert!(child.contains::<Database>());
483 assert!(child.contains::<Cache>());
484 }
485}