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