Skip to main content

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}