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}