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// =============================================================================
76// Type-State Builder
77// =============================================================================
78
79/// A type-state container builder.
80///
81/// The type parameter `R` tracks all registered types at compile time.
82pub struct TypedBuilder<R = ()> {
83    container: Container,
84    _registry: PhantomData<R>,
85}
86
87impl TypedBuilder<()> {
88    /// Create a new typed builder.
89    #[inline]
90    pub fn new() -> Self {
91        Self {
92            container: Container::new(),
93            _registry: PhantomData,
94        }
95    }
96
97    /// Create with pre-allocated capacity.
98    #[inline]
99    pub fn with_capacity(capacity: usize) -> Self {
100        Self {
101            container: Container::with_capacity(capacity),
102            _registry: PhantomData,
103        }
104    }
105}
106
107impl Default for TypedBuilder<()> {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl<R> TypedBuilder<R> {
114    /// Register a singleton service.
115    #[inline]
116    pub fn singleton<T: Injectable>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
117        self.container.singleton(instance);
118        TypedBuilder {
119            container: self.container,
120            _registry: PhantomData,
121        }
122    }
123
124    /// Register a lazy singleton.
125    #[inline]
126    pub fn lazy<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
127    where
128        F: Fn() -> T + Send + Sync + 'static,
129    {
130        self.container.lazy(factory);
131        TypedBuilder {
132            container: self.container,
133            _registry: PhantomData,
134        }
135    }
136
137    /// Register a transient service.
138    #[inline]
139    pub fn transient<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
140    where
141        F: Fn() -> T + Send + Sync + 'static,
142    {
143        self.container.transient(factory);
144        TypedBuilder {
145            container: self.container,
146            _registry: PhantomData,
147        }
148    }
149
150    /// Build the typed container.
151    #[inline]
152    pub fn build(self) -> TypedContainer<R> {
153        self.container.lock();
154        TypedContainer {
155            container: self.container,
156            _registry: PhantomData,
157        }
158    }
159
160    /// Build and return the underlying container.
161    #[inline]
162    pub fn build_dynamic(self) -> Container {
163        self.container.lock();
164        self.container
165    }
166
167    /// Access the underlying container.
168    #[inline]
169    pub fn inner(&self) -> &Container {
170        &self.container
171    }
172}
173
174// =============================================================================
175// Dependency Declaration
176// =============================================================================
177
178// =============================================================================
179// Dependency Declaration (Runtime-Verified)
180// =============================================================================
181
182/// Trait for services that declare their dependencies.
183///
184/// Use with `with_deps` to get documentation-level dependency declaration.
185/// Runtime verification ensures all dependencies are present.
186///
187/// Note: Full compile-time dependency verification requires proc macros
188/// or unstable Rust features. This provides a documentation/runtime hybrid.
189pub trait DeclaresDeps: Injectable {
190    /// List of dependency type names (for documentation and debugging).
191    fn dependency_names() -> &'static [&'static str] {
192        &[]
193    }
194}
195
196impl<R> TypedBuilder<R> {
197    /// Register a service (alias for singleton with deps intent).
198    ///
199    /// Note: This method is the same as `singleton` but signals that
200    /// the service has dependencies that should already be registered.
201    #[inline]
202    pub fn with_deps<T: DeclaresDeps>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
203        self.singleton(instance)
204    }
205
206    /// Register a lazy service with deps intent.
207    #[inline]
208    pub fn lazy_with_deps<T: DeclaresDeps, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
209    where
210        F: Fn() -> T + Send + Sync + 'static,
211    {
212        self.lazy(factory)
213    }
214}
215
216// Dummy traits for backwards compatibility
217pub trait VerifyDeps<D> {}
218impl<R, D> VerifyDeps<D> for R {}
219
220// =============================================================================
221// Typed Container
222// =============================================================================
223
224/// A container with compile-time type tracking.
225///
226/// The type parameter tracks what was registered, enabling
227/// compile-time verification of service access.
228pub struct TypedContainer<R> {
229    container: Container,
230    _registry: PhantomData<R>,
231}
232
233impl<R> TypedContainer<R> {
234    /// Resolve a service by type.
235    ///
236    /// Uses the dynamic container internally but provides type-safe API.
237    #[inline]
238    pub fn get<T: Injectable>(&self) -> Arc<T> {
239        self.container
240            .get::<T>()
241            .expect("TypedContainer: service not found (registration mismatch)")
242    }
243
244    /// Try to resolve a service.
245    #[inline]
246    pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
247        self.container.try_get::<T>()
248    }
249
250    /// Check if service exists.
251    #[inline]
252    pub fn contains<T: Injectable>(&self) -> bool {
253        self.container.contains::<T>()
254    }
255
256    /// Create a dynamic child scope.
257    #[inline]
258    pub fn scope(&self) -> Container {
259        self.container.scope()
260    }
261
262    /// Access the underlying container.
263    #[inline]
264    pub fn inner(&self) -> &Container {
265        &self.container
266    }
267
268    /// Convert to the underlying container.
269    #[inline]
270    pub fn into_inner(self) -> Container {
271        self.container
272    }
273}
274
275impl<R> Clone for TypedContainer<R> {
276    fn clone(&self) -> Self {
277        Self {
278            container: self.container.clone(),
279            _registry: PhantomData,
280        }
281    }
282}
283
284impl<R> std::fmt::Debug for TypedContainer<R> {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        f.debug_struct("TypedContainer")
287            .field("inner", &self.container)
288            .finish()
289    }
290}
291
292// =============================================================================
293// Backward Compatibility Aliases
294// =============================================================================
295
296/// Alias for HasType trait.
297pub trait Has<T: Injectable>: HasType<T> {}
298impl<T: Injectable, R: HasType<T>> Has<T> for R {}
299
300/// Alias for HasType trait.
301pub trait HasService<T: Injectable>: HasType<T> {}
302impl<T: Injectable, R: HasType<T>> HasService<T> for R {}
303
304// Dummy trait for DepsPresent compatibility
305pub trait DepsPresent<D> {}
306impl<R, D> DepsPresent<D> for R {}
307
308// =============================================================================
309// Tests
310// =============================================================================
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[derive(Clone)]
317    struct Database {
318        url: String,
319    }
320
321    #[derive(Clone)]
322    struct Cache {
323        size: usize,
324    }
325
326    #[derive(Clone)]
327    struct UserService;
328
329    #[test]
330    fn test_typed_builder_basic() {
331        let container = TypedBuilder::new()
332            .singleton(Database {
333                url: "postgres://localhost".into(),
334            })
335            .singleton(Cache { size: 1024 })
336            .build();
337
338        let db = container.get::<Database>();
339        let cache = container.get::<Cache>();
340
341        assert_eq!(db.url, "postgres://localhost");
342        assert_eq!(cache.size, 1024);
343    }
344
345    #[test]
346    fn test_typed_builder_lazy() {
347        let container = TypedBuilder::new()
348            .lazy(|| Database {
349                url: "lazy://created".into(),
350            })
351            .build();
352
353        let db = container.get::<Database>();
354        assert_eq!(db.url, "lazy://created");
355    }
356
357    #[test]
358    fn test_typed_builder_transient() {
359        use std::sync::atomic::{AtomicU32, Ordering};
360
361        static COUNTER: AtomicU32 = AtomicU32::new(0);
362
363        #[derive(Clone)]
364        struct Counter(u32);
365
366        let container = TypedBuilder::new()
367            .transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)))
368            .build();
369
370        let c1 = container.get::<Counter>();
371        let c2 = container.get::<Counter>();
372
373        assert_ne!(c1.0, c2.0);
374    }
375
376    #[test]
377    fn test_typed_container_clone() {
378        let container = TypedBuilder::new()
379            .singleton(Database { url: "test".into() })
380            .build();
381
382        let container2 = container.clone();
383
384        let db1 = container.get::<Database>();
385        let db2 = container2.get::<Database>();
386
387        assert!(Arc::ptr_eq(&db1, &db2));
388    }
389
390    #[test]
391    fn test_with_dependencies() {
392        impl DeclaresDeps for UserService {
393            fn dependency_names() -> &'static [&'static str] {
394                &["Database", "Cache"]
395            }
396        }
397
398        // Register deps first, then dependent service
399        let container = TypedBuilder::new()
400            .singleton(Database { url: "pg".into() })
401            .singleton(Cache { size: 100 })
402            .with_deps(UserService)
403            .build();
404
405        let _ = container.get::<UserService>();
406    }
407
408    #[test]
409    fn test_many_services() {
410        #[derive(Clone)]
411        struct S1;
412        #[derive(Clone)]
413        struct S2;
414        #[derive(Clone)]
415        struct S3;
416        #[derive(Clone)]
417        struct S4;
418        #[derive(Clone)]
419        struct S5;
420
421        let container = TypedBuilder::new()
422            .singleton(S1)
423            .singleton(S2)
424            .singleton(S3)
425            .singleton(S4)
426            .singleton(S5)
427            .build();
428
429        let _ = container.get::<S1>();
430        let _ = container.get::<S2>();
431        let _ = container.get::<S3>();
432        let _ = container.get::<S4>();
433        let _ = container.get::<S5>();
434    }
435
436    #[test]
437    fn test_scope_from_typed() {
438        let container = TypedBuilder::new()
439            .singleton(Database { url: "root".into() })
440            .build();
441
442        let child = container.scope();
443        child.singleton(Cache { size: 256 });
444
445        assert!(child.contains::<Database>());
446        assert!(child.contains::<Cache>());
447    }
448}