Skip to main content

dependency_injector/
verified.rs

1//! Verified Service Providers
2//!
3//! This module provides traits for services that declare their dependencies
4//! at compile time, enabling static verification of dependency graphs.
5//!
6//! # Features
7//!
8//! - **`Service` trait**: Declare dependencies and creation logic
9//! - **`ServiceProvider` trait**: Auto-register services with their factories
10//! - **Compile-time cycle detection**: The type system prevents circular deps
11//!
12//! # Example
13//!
14//! ```rust
15//! use dependency_injector::verified::{Service, ServiceProvider};
16//! use dependency_injector::Container;
17//! use std::sync::Arc;
18//!
19//! #[derive(Clone)]
20//! struct Database {
21//!     url: String,
22//! }
23//!
24//! impl Service for Database {
25//!     type Dependencies = ();
26//!
27//!     fn create(_deps: Self::Dependencies) -> Self {
28//!         Database { url: "postgres://localhost".into() }
29//!     }
30//! }
31//!
32//! #[derive(Clone)]
33//! struct UserRepository {
34//!     db: Arc<Database>,
35//! }
36//!
37//! impl Service for UserRepository {
38//!     type Dependencies = Arc<Database>;
39//!
40//!     fn create(db: Self::Dependencies) -> Self {
41//!         UserRepository { db }
42//!     }
43//! }
44//!
45//! // Auto-register with dependencies resolved
46//! let container = Container::new();
47//! container.provide::<Database>();
48//! container.provide::<UserRepository>();
49//!
50//! let repo = container.get::<UserRepository>().unwrap();
51//! ```
52
53use crate::{Container, Injectable};
54use std::sync::Arc;
55
56// =============================================================================
57// Service Trait
58// =============================================================================
59
60/// A service that declares its dependencies at compile time.
61///
62/// The `Dependencies` associated type specifies what the service needs,
63/// and `create` defines how to construct the service given those dependencies.
64///
65/// # Supported Dependency Types
66///
67/// - `()` - No dependencies
68/// - `Arc<T>` - Single required dependency
69/// - `(Arc<A>, Arc<B>)` - Multiple dependencies (tuples up to 12)
70/// - `Option<Arc<T>>` - Optional dependency
71/// - `(Arc<A>, Option<Arc<B>>)` - Tuples may mix required and optional
72///
73/// # Example
74///
75/// ```rust
76/// use dependency_injector::verified::Service;
77/// use std::sync::Arc;
78///
79/// #[derive(Clone)]
80/// struct Config {
81///     debug: bool,
82/// }
83///
84/// impl Service for Config {
85///     type Dependencies = ();
86///
87///     fn create(_: ()) -> Self {
88///         Config { debug: false }
89///     }
90/// }
91///
92/// #[derive(Clone)]
93/// struct Logger {
94///     config: Arc<Config>,
95/// }
96///
97/// impl Service for Logger {
98///     type Dependencies = Arc<Config>;
99///
100///     fn create(config: Arc<Config>) -> Self {
101///         Logger { config }
102///     }
103/// }
104/// ```
105pub trait Service: Injectable + Sized {
106    /// The dependencies required to create this service.
107    ///
108    /// Use `()` for no dependencies, `Arc<T>` for one, or tuples for multiple.
109    type Dependencies: Resolvable;
110
111    /// Create a new instance given the resolved dependencies.
112    fn create(deps: Self::Dependencies) -> Self;
113}
114
115// =============================================================================
116// Resolvable Trait - Dependencies that can be resolved from a container
117// =============================================================================
118
119/// Trait for types that can be resolved from a container.
120///
121/// This is automatically implemented for:
122/// - `()` - No dependencies
123/// - `Arc<T>` - Single service
124/// - `Option<Arc<T>>` - Optional service
125/// - Tuples of resolvable elements - Multiple services, mixing required
126///   and optional dependencies as needed
127pub trait Resolvable: Sized {
128    /// Resolve this dependency from the container.
129    ///
130    /// Returns `None` if any required dependency is missing.
131    fn resolve(container: &Container) -> Option<Self>;
132}
133
134// No dependencies
135impl Resolvable for () {
136    #[inline]
137    fn resolve(_container: &Container) -> Option<Self> {
138        Some(())
139    }
140}
141
142// Single dependency
143impl<T: Injectable> Resolvable for Arc<T> {
144    #[inline]
145    fn resolve(container: &Container) -> Option<Self> {
146        container.try_get::<T>()
147    }
148}
149
150// Optional dependency
151impl<T: Injectable> Resolvable for Option<Arc<T>> {
152    #[inline]
153    fn resolve(container: &Container) -> Option<Self> {
154        Some(container.try_get::<T>())
155    }
156}
157
158// Tuple implementations (2-12 elements)
159//
160// Each element only needs to be `Resolvable` itself, so tuples may freely mix
161// required (`Arc<T>`) and optional (`Option<Arc<T>>`) dependencies. A missing
162// required element fails resolution of the whole tuple; a missing optional
163// element resolves to `None`.
164macro_rules! impl_resolvable_tuple {
165    ($($T:ident),+) => {
166        impl<$($T: Resolvable),+> Resolvable for ($($T,)+) {
167            #[inline]
168            fn resolve(container: &Container) -> Option<Self> {
169                Some(($($T::resolve(container)?,)+))
170            }
171        }
172    };
173}
174
175impl_resolvable_tuple!(A, B);
176impl_resolvable_tuple!(A, B, C);
177impl_resolvable_tuple!(A, B, C, D);
178impl_resolvable_tuple!(A, B, C, D, E);
179impl_resolvable_tuple!(A, B, C, D, E, F);
180impl_resolvable_tuple!(A, B, C, D, E, F, G);
181impl_resolvable_tuple!(A, B, C, D, E, F, G, H);
182impl_resolvable_tuple!(A, B, C, D, E, F, G, H, I);
183impl_resolvable_tuple!(A, B, C, D, E, F, G, H, I, J);
184impl_resolvable_tuple!(A, B, C, D, E, F, G, H, I, J, K);
185impl_resolvable_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
186
187// =============================================================================
188// ServiceProvider Trait - Auto-registration
189// =============================================================================
190
191/// Extension trait for containers to auto-register services.
192pub trait ServiceProvider {
193    /// Register a service using its `Service` implementation.
194    ///
195    /// The service will be created lazily on first access, with dependencies
196    /// resolved from the container.
197    ///
198    /// # Panics
199    ///
200    /// The created factory will panic at runtime if dependencies are missing.
201    /// For compile-time safety, use the typed builder API.
202    ///
203    /// # Example
204    ///
205    /// ```rust
206    /// use dependency_injector::{Container, verified::{Service, ServiceProvider}};
207    ///
208    /// #[derive(Clone)]
209    /// struct MyService;
210    ///
211    /// impl Service for MyService {
212    ///     type Dependencies = ();
213    ///     fn create(_: ()) -> Self { MyService }
214    /// }
215    ///
216    /// let container = Container::new();
217    /// container.provide::<MyService>();
218    ///
219    /// let service = container.get::<MyService>().unwrap();
220    /// ```
221    fn provide<T: Service>(&self);
222
223    /// Register a service as a singleton with pre-resolved dependencies.
224    ///
225    /// Dependencies are resolved immediately, not lazily.
226    ///
227    /// # Returns
228    ///
229    /// `true` if all dependencies were resolved and the service was registered,
230    /// `false` if any dependency was missing.
231    fn provide_singleton<T: Service>(&self) -> bool;
232
233    /// Register a service as transient.
234    ///
235    /// A new instance is created on every resolution.
236    fn provide_transient<T: Service>(&self);
237}
238
239impl ServiceProvider for Container {
240    #[inline]
241    fn provide<T: Service>(&self) {
242        let container = self.clone();
243        self.lazy(move || {
244            let deps = T::Dependencies::resolve(&container)
245                .expect("Failed to resolve dependencies for service");
246            T::create(deps)
247        });
248    }
249
250    #[inline]
251    fn provide_singleton<T: Service>(&self) -> bool {
252        if let Some(deps) = T::Dependencies::resolve(self) {
253            self.singleton(T::create(deps));
254            true
255        } else {
256            false
257        }
258    }
259
260    #[inline]
261    fn provide_transient<T: Service>(&self) {
262        let container = self.clone();
263        self.transient(move || {
264            let deps = T::Dependencies::resolve(&container)
265                .expect("Failed to resolve dependencies for transient service");
266            T::create(deps)
267        });
268    }
269}
270
271// =============================================================================
272// ServiceModule - Group related services
273// =============================================================================
274
275/// A module that groups related service registrations.
276///
277/// # Example
278///
279/// ```rust
280/// use dependency_injector::{Container, verified::{Service, ServiceModule, ServiceProvider}};
281///
282/// #[derive(Clone)]
283/// struct Database;
284///
285/// impl Service for Database {
286///     type Dependencies = ();
287///     fn create(_: ()) -> Self { Database }
288/// }
289///
290/// #[derive(Clone)]
291/// struct Cache;
292///
293/// impl Service for Cache {
294///     type Dependencies = ();
295///     fn create(_: ()) -> Self { Cache }
296/// }
297///
298/// struct DataModule;
299///
300/// impl ServiceModule for DataModule {
301///     fn register(container: &Container) {
302///         container.provide::<Database>();
303///         container.provide::<Cache>();
304///     }
305/// }
306///
307/// let container = Container::new();
308/// DataModule::register(&container);
309///
310/// assert!(container.contains::<Database>());
311/// assert!(container.contains::<Cache>());
312/// ```
313pub trait ServiceModule {
314    /// Register all services in this module.
315    fn register(container: &Container);
316}
317
318// =============================================================================
319// Dependency Graph Helpers
320// =============================================================================
321
322/// Trait for extracting dependency type information.
323///
324/// This is mainly useful for debugging and visualization.
325pub trait DependencyInfo {
326    /// Get the type names of all dependencies.
327    fn dependency_names() -> Vec<&'static str>;
328}
329
330impl DependencyInfo for () {
331    fn dependency_names() -> Vec<&'static str> {
332        vec![]
333    }
334}
335
336impl<T: Injectable> DependencyInfo for Arc<T> {
337    fn dependency_names() -> Vec<&'static str> {
338        vec![std::any::type_name::<T>()]
339    }
340}
341
342impl<T: Injectable> DependencyInfo for Option<Arc<T>> {
343    fn dependency_names() -> Vec<&'static str> {
344        vec![std::any::type_name::<T>()]
345    }
346}
347
348// Tuple implementations for DependencyInfo
349macro_rules! impl_dependency_info_tuple {
350    ($($T:ident),+) => {
351        impl<$($T: Injectable),+> DependencyInfo for ($(Arc<$T>,)+) {
352            fn dependency_names() -> Vec<&'static str> {
353                vec![$(std::any::type_name::<$T>()),+]
354            }
355        }
356    };
357}
358
359impl_dependency_info_tuple!(A, B);
360impl_dependency_info_tuple!(A, B, C);
361impl_dependency_info_tuple!(A, B, C, D);
362impl_dependency_info_tuple!(A, B, C, D, E);
363impl_dependency_info_tuple!(A, B, C, D, E, F);
364
365// =============================================================================
366// Compile-Time Cycle Detection (Documentation Only)
367// =============================================================================
368
369// Note: Full compile-time cycle detection requires either:
370// 1. A procedural macro that analyzes the full dependency graph
371// 2. Unstable Rust features (specialization, const generics)
372//
373// The current approach provides partial protection:
374// - The `Service` trait requires explicit dependency declaration
375// - The `TypedBuilder::with_dependencies` method verifies deps exist
376// - Runtime errors are caught when resolving missing dependencies
377//
378// For complete compile-time cycle detection, consider:
379// - Using the `#[derive(Service)]` macro which can analyze dependencies
380// - Using the typed builder API which tracks registrations
381//
382// Future: When Rust's type system supports it, we can add full cycle detection.
383
384// =============================================================================
385// Tests
386// =============================================================================
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[derive(Clone)]
393    struct Config {
394        debug: bool,
395    }
396
397    impl Service for Config {
398        type Dependencies = ();
399
400        fn create(_: ()) -> Self {
401            Config { debug: true }
402        }
403    }
404
405    #[derive(Clone)]
406    struct Database {
407        url: String,
408    }
409
410    impl Service for Database {
411        type Dependencies = Arc<Config>;
412
413        fn create(config: Arc<Config>) -> Self {
414            Database {
415                url: if config.debug {
416                    "debug://localhost".into()
417                } else {
418                    "prod://server".into()
419                },
420            }
421        }
422    }
423
424    #[derive(Clone)]
425    struct Cache {
426        size: usize,
427    }
428
429    impl Service for Cache {
430        type Dependencies = ();
431
432        fn create(_: ()) -> Self {
433            Cache { size: 1024 }
434        }
435    }
436
437    #[derive(Clone)]
438    struct UserRepository {
439        db: Arc<Database>,
440        cache: Arc<Cache>,
441    }
442
443    impl Service for UserRepository {
444        type Dependencies = (Arc<Database>, Arc<Cache>);
445
446        fn create((db, cache): (Arc<Database>, Arc<Cache>)) -> Self {
447            UserRepository { db, cache }
448        }
449    }
450
451    #[derive(Clone)]
452    struct MixedDeps {
453        db: Arc<Database>,
454        cache: Option<Arc<Cache>>,
455    }
456
457    impl Service for MixedDeps {
458        type Dependencies = (Arc<Database>, Option<Arc<Cache>>);
459
460        fn create((db, cache): (Arc<Database>, Option<Arc<Cache>>)) -> Self {
461            MixedDeps { db, cache }
462        }
463    }
464
465    #[test]
466    fn test_service_no_deps() {
467        let container = Container::new();
468        container.provide::<Config>();
469
470        let config = container.get::<Config>().unwrap();
471        assert!(config.debug);
472    }
473
474    #[test]
475    fn test_service_single_dep() {
476        let container = Container::new();
477        container.provide::<Config>();
478        container.provide::<Database>();
479
480        let db = container.get::<Database>().unwrap();
481        assert_eq!(db.url, "debug://localhost");
482    }
483
484    #[test]
485    fn test_service_multiple_deps() {
486        let container = Container::new();
487        container.provide::<Config>();
488        container.provide::<Database>();
489        container.provide::<Cache>();
490        container.provide::<UserRepository>();
491
492        let repo = container.get::<UserRepository>().unwrap();
493        assert_eq!(repo.db.url, "debug://localhost");
494        assert_eq!(repo.cache.size, 1024);
495    }
496
497    #[test]
498    fn test_provide_singleton() {
499        let container = Container::new();
500        container.provide::<Config>();
501
502        // Should succeed
503        let result = container.provide_singleton::<Database>();
504        assert!(result);
505
506        let db = container.get::<Database>().unwrap();
507        assert_eq!(db.url, "debug://localhost");
508    }
509
510    #[test]
511    fn test_provide_singleton_missing_dep() {
512        let container = Container::new();
513
514        // Should fail - Config not registered
515        let result = container.provide_singleton::<Database>();
516        assert!(!result);
517    }
518
519    #[test]
520    fn test_provide_transient() {
521        use std::sync::atomic::{AtomicU32, Ordering};
522
523        static COUNTER: AtomicU32 = AtomicU32::new(0);
524
525        #[derive(Clone)]
526        struct Counter(u32);
527
528        impl Service for Counter {
529            type Dependencies = ();
530
531            fn create(_: ()) -> Self {
532                Counter(COUNTER.fetch_add(1, Ordering::SeqCst))
533            }
534        }
535
536        let container = Container::new();
537        container.provide_transient::<Counter>();
538
539        let c1 = container.get::<Counter>().unwrap();
540        let c2 = container.get::<Counter>().unwrap();
541
542        assert_ne!(c1.0, c2.0);
543    }
544
545    #[test]
546    fn test_optional_dependency() {
547        #[derive(Clone)]
548        struct OptionalCache;
549
550        #[derive(Clone)]
551        struct ServiceWithOptional {
552            cache: Option<Arc<OptionalCache>>,
553        }
554
555        impl Service for ServiceWithOptional {
556            type Dependencies = Option<Arc<OptionalCache>>;
557
558            fn create(cache: Option<Arc<OptionalCache>>) -> Self {
559                ServiceWithOptional { cache }
560            }
561        }
562
563        let container = Container::new();
564        container.provide::<ServiceWithOptional>();
565
566        let svc = container.get::<ServiceWithOptional>().unwrap();
567        assert!(svc.cache.is_none());
568
569        // Now register the optional dep
570        let container2 = Container::new();
571        container2.singleton(OptionalCache);
572        container2.provide::<ServiceWithOptional>();
573
574        let svc2 = container2.get::<ServiceWithOptional>().unwrap();
575        assert!(svc2.cache.is_some());
576    }
577
578    #[test]
579    fn test_mixed_tuple_both_registered() {
580        let container = Container::new();
581        container.provide::<Config>();
582        container.provide::<Database>();
583        container.provide::<Cache>();
584        container.provide::<MixedDeps>();
585
586        let svc = container.get::<MixedDeps>().unwrap();
587        assert_eq!(svc.db.url, "debug://localhost");
588        assert!(svc.cache.is_some());
589    }
590
591    #[test]
592    fn test_mixed_tuple_optional_missing() {
593        let container = Container::new();
594        container.provide::<Config>();
595        container.provide::<Database>();
596        // Cache not registered - optional dep resolves to None
597        container.provide::<MixedDeps>();
598
599        let svc = container.get::<MixedDeps>().unwrap();
600        assert_eq!(svc.db.url, "debug://localhost");
601        assert!(svc.cache.is_none());
602    }
603
604    #[test]
605    fn test_mixed_tuple_required_missing() {
606        let container = Container::new();
607        // Database not registered - required dep missing, resolution fails
608        assert!(<(Arc<Database>, Option<Arc<Cache>>) as Resolvable>::resolve(&container).is_none());
609        assert!(!container.provide_singleton::<MixedDeps>());
610    }
611
612    #[test]
613    fn test_dependency_info() {
614        assert_eq!(
615            <() as DependencyInfo>::dependency_names(),
616            Vec::<&str>::new()
617        );
618        assert_eq!(
619            <Arc<Config> as DependencyInfo>::dependency_names(),
620            vec!["dependency_injector::verified::tests::Config"]
621        );
622        assert_eq!(
623            <(Arc<Database>, Arc<Cache>) as DependencyInfo>::dependency_names().len(),
624            2
625        );
626    }
627
628    #[test]
629    fn test_service_module() {
630        struct TestModule;
631
632        impl ServiceModule for TestModule {
633            fn register(container: &Container) {
634                container.provide::<Config>();
635                container.provide::<Cache>();
636            }
637        }
638
639        let container = Container::new();
640        TestModule::register(&container);
641
642        assert!(container.contains::<Config>());
643        assert!(container.contains::<Cache>());
644    }
645    #[test]
646    fn test_all_arc_tuple_trait_call_inference() {
647        // Pins the annotated-binding trait-call form: the element-wise
648        // generalization of the tuple impls must keep this resolving
649        // without a turbofish on the impl.
650        let container = Container::new();
651        container.singleton(Config { debug: true });
652        assert!(container.provide_singleton::<Database>());
653        assert!(container.provide_singleton::<Cache>());
654
655        let resolved: Option<(Arc<Database>, Arc<Cache>)> = Resolvable::resolve(&container);
656        assert!(resolved.is_some());
657    }
658}