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