allframe_core/di/
mod.rs

1//! Dependency Injection Infrastructure
2//!
3//! This module provides compile-time dependency injection with support for:
4//! - Async initialization
5//! - Explicit dependency declaration
6//! - Singleton and transient scoping
7//! - Environment-based configuration
8//!
9//! # Example
10//!
11//! ```rust,ignore
12//! use allframe_core::di::{Provider, Scope, DependencyError};
13//! use allframe_macros::di_container;
14//!
15//! #[di_container]
16//! struct AppContainer {
17//!     #[provide(from_env)]
18//!     config: Config,
19//!
20//!     #[provide(singleton, async)]
21//!     #[depends(config)]
22//!     database: DatabasePool,
23//!
24//!     #[provide(transient)]
25//!     service: MyService,
26//! }
27//!
28//! // Generated async build method
29//! let container = AppContainer::build().await?;
30//! ```
31
32use std::any::Any;
33use std::collections::HashMap;
34use std::fmt;
35use std::sync::Arc;
36
37/// Error type for dependency injection operations
38#[derive(Debug)]
39pub enum DependencyError {
40    /// A required dependency was not found
41    NotFound(String),
42    /// Circular dependency detected
43    CircularDependency(Vec<String>),
44    /// Failed to initialize a dependency
45    InitializationFailed {
46        /// Name of the dependency that failed
47        name: String,
48        /// The underlying error
49        source: Box<dyn std::error::Error + Send + Sync>,
50    },
51    /// Configuration error (e.g., missing environment variable)
52    ConfigError(String),
53    /// Type mismatch when resolving dependency
54    TypeMismatch {
55        /// Expected type name
56        expected: String,
57        /// Actual type name
58        actual: String,
59    },
60}
61
62impl fmt::Display for DependencyError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            DependencyError::NotFound(name) => {
66                write!(f, "Dependency not found: {}", name)
67            }
68            DependencyError::CircularDependency(chain) => {
69                write!(f, "Circular dependency detected: {}", chain.join(" -> "))
70            }
71            DependencyError::InitializationFailed { name, source } => {
72                write!(f, "Failed to initialize '{}': {}", name, source)
73            }
74            DependencyError::ConfigError(msg) => {
75                write!(f, "Configuration error: {}", msg)
76            }
77            DependencyError::TypeMismatch { expected, actual } => {
78                write!(f, "Type mismatch: expected {}, got {}", expected, actual)
79            }
80        }
81    }
82}
83
84impl std::error::Error for DependencyError {
85    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
86        match self {
87            DependencyError::InitializationFailed { source, .. } => Some(source.as_ref()),
88            _ => None,
89        }
90    }
91}
92
93/// Scope determines the lifecycle of a dependency
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum Scope {
96    /// Single instance shared across all requests (wrapped in Arc)
97    #[default]
98    Singleton,
99    /// New instance created for each request
100    Transient,
101}
102
103/// Trait for types that can provide dependencies
104///
105/// This trait is implemented automatically by the `#[di_container]` macro
106/// for container types, but can also be implemented manually for custom
107/// providers.
108#[crate::async_trait::async_trait]
109pub trait Provider<T>: Send + Sync {
110    /// Provide an instance of the dependency
111    async fn provide(&self) -> Result<T, DependencyError>;
112}
113
114/// Trait for types that can be loaded from environment variables
115pub trait FromEnv: Sized {
116    /// Load configuration from environment variables
117    fn from_env() -> Result<Self, DependencyError>;
118}
119
120/// Trait for async initialization
121#[crate::async_trait::async_trait]
122pub trait AsyncInit: Sized {
123    /// Initialize the type asynchronously
124    async fn init() -> Result<Self, DependencyError>;
125}
126
127/// Trait for types that can be initialized with dependencies
128#[crate::async_trait::async_trait]
129pub trait AsyncInitWith<Deps>: Sized {
130    /// Initialize the type asynchronously with dependencies
131    async fn init_with(deps: Deps) -> Result<Self, DependencyError>;
132}
133
134/// A type-erased container for storing dependencies
135pub struct DependencyRegistry {
136    singletons: HashMap<std::any::TypeId, Arc<dyn Any + Send + Sync>>,
137}
138
139impl Default for DependencyRegistry {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl DependencyRegistry {
146    /// Create a new empty registry
147    pub fn new() -> Self {
148        Self {
149            singletons: HashMap::new(),
150        }
151    }
152
153    /// Store a singleton instance
154    pub fn store_singleton<T: Send + Sync + 'static>(&mut self, value: T) {
155        let type_id = std::any::TypeId::of::<T>();
156        self.singletons.insert(type_id, Arc::new(value));
157    }
158
159    /// Get a singleton instance
160    pub fn get_singleton<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
161        let type_id = std::any::TypeId::of::<T>();
162        self.singletons
163            .get(&type_id)
164            .and_then(|any| any.clone().downcast::<T>().ok())
165    }
166
167    /// Check if a singleton exists
168    pub fn has_singleton<T: 'static>(&self) -> bool {
169        let type_id = std::any::TypeId::of::<T>();
170        self.singletons.contains_key(&type_id)
171    }
172}
173
174/// Builder for constructing dependency containers with explicit ordering
175pub struct ContainerBuilder {
176    initialization_order: Vec<String>,
177    initialized: std::collections::HashSet<String>,
178}
179
180impl Default for ContainerBuilder {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl ContainerBuilder {
187    /// Create a new container builder
188    pub fn new() -> Self {
189        Self {
190            initialization_order: Vec::new(),
191            initialized: std::collections::HashSet::new(),
192        }
193    }
194
195    /// Mark a dependency as initialized
196    pub fn mark_initialized(&mut self, name: &str) {
197        self.initialized.insert(name.to_string());
198        self.initialization_order.push(name.to_string());
199    }
200
201    /// Check if a dependency has been initialized
202    pub fn is_initialized(&self, name: &str) -> bool {
203        self.initialized.contains(name)
204    }
205
206    /// Get the initialization order
207    pub fn initialization_order(&self) -> &[String] {
208        &self.initialization_order
209    }
210
211    /// Validate that all dependencies for a type are initialized
212    pub fn validate_dependencies(&self, deps: &[&str]) -> Result<(), DependencyError> {
213        for dep in deps {
214            if !self.is_initialized(dep) {
215                return Err(DependencyError::NotFound((*dep).to_string()));
216            }
217        }
218        Ok(())
219    }
220}
221
222/// Helper to load a value from an environment variable
223pub fn env_var(name: &str) -> Result<String, DependencyError> {
224    std::env::var(name).map_err(|_| {
225        DependencyError::ConfigError(format!("Environment variable '{}' not set", name))
226    })
227}
228
229/// Helper to load an optional value from an environment variable
230pub fn env_var_opt(name: &str) -> Option<String> {
231    std::env::var(name).ok()
232}
233
234/// Helper to load a value from an environment variable with a default
235pub fn env_var_or(name: &str, default: &str) -> String {
236    std::env::var(name).unwrap_or_else(|_| default.to_string())
237}
238
239/// Helper to parse a value from an environment variable
240pub fn env_var_parse<T: std::str::FromStr>(name: &str) -> Result<T, DependencyError>
241where
242    T::Err: std::fmt::Display,
243{
244    let value = env_var(name)?;
245    value.parse().map_err(|e: T::Err| {
246        DependencyError::ConfigError(format!(
247            "Failed to parse environment variable '{}': {}",
248            name, e
249        ))
250    })
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_dependency_error_display() {
259        let err = DependencyError::NotFound("database".to_string());
260        assert!(err.to_string().contains("database"));
261
262        let err = DependencyError::CircularDependency(vec![
263            "a".to_string(),
264            "b".to_string(),
265            "a".to_string(),
266        ]);
267        assert!(err.to_string().contains("a -> b -> a"));
268
269        let err = DependencyError::ConfigError("missing var".to_string());
270        assert!(err.to_string().contains("missing var"));
271    }
272
273    #[test]
274    fn test_scope_default() {
275        assert_eq!(Scope::default(), Scope::Singleton);
276    }
277
278    #[test]
279    fn test_dependency_registry() {
280        let mut registry = DependencyRegistry::new();
281
282        registry.store_singleton(42i32);
283        assert!(registry.has_singleton::<i32>());
284
285        let value = registry.get_singleton::<i32>();
286        assert_eq!(*value.unwrap(), 42);
287
288        assert!(!registry.has_singleton::<String>());
289    }
290
291    #[test]
292    fn test_container_builder() {
293        let mut builder = ContainerBuilder::new();
294
295        assert!(!builder.is_initialized("config"));
296        builder.mark_initialized("config");
297        assert!(builder.is_initialized("config"));
298
299        builder.mark_initialized("database");
300        assert_eq!(builder.initialization_order(), &["config", "database"]);
301    }
302
303    #[test]
304    fn test_container_builder_validate() {
305        let mut builder = ContainerBuilder::new();
306        builder.mark_initialized("config");
307
308        assert!(builder.validate_dependencies(&["config"]).is_ok());
309        assert!(builder.validate_dependencies(&["database"]).is_err());
310    }
311
312    #[test]
313    fn test_env_helpers() {
314        // Test env_var_or with default
315        let value = env_var_or("NONEXISTENT_VAR_12345", "default");
316        assert_eq!(value, "default");
317
318        // Test env_var_opt
319        let value = env_var_opt("NONEXISTENT_VAR_12345");
320        assert!(value.is_none());
321    }
322}