pub mod lazy;
pub use lazy::*;
use std::{any::Any, collections::HashMap, fmt, sync::Arc};
#[derive(Debug)]
pub enum DependencyError {
NotFound(String),
CircularDependency(Vec<String>),
InitializationFailed {
name: String,
source: Box<dyn std::error::Error + Send + Sync>,
},
ConfigError(String),
TypeMismatch {
expected: String,
actual: String,
},
}
impl fmt::Display for DependencyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DependencyError::NotFound(name) => {
write!(f, "Dependency not found: {}", name)
}
DependencyError::CircularDependency(chain) => {
write!(f, "Circular dependency detected: {}", chain.join(" -> "))
}
DependencyError::InitializationFailed { name, source } => {
write!(f, "Failed to initialize '{}': {}", name, source)
}
DependencyError::ConfigError(msg) => {
write!(f, "Configuration error: {}", msg)
}
DependencyError::TypeMismatch { expected, actual } => {
write!(f, "Type mismatch: expected {}, got {}", expected, actual)
}
}
}
}
impl std::error::Error for DependencyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
DependencyError::InitializationFailed { source, .. } => Some(source.as_ref()),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Scope {
#[default]
Singleton,
Transient,
}
#[crate::async_trait::async_trait]
pub trait Provider<T>: Send + Sync {
async fn provide(&self) -> Result<T, DependencyError>;
}
pub trait FromEnv: Sized {
fn from_env() -> Result<Self, DependencyError>;
}
#[crate::async_trait::async_trait]
pub trait AsyncInit: Sized {
async fn init() -> Result<Self, DependencyError>;
}
#[crate::async_trait::async_trait]
pub trait AsyncInitWith<Deps>: Sized {
async fn init_with(deps: Deps) -> Result<Self, DependencyError>;
}
pub struct DependencyRegistry {
singletons: HashMap<std::any::TypeId, Arc<dyn Any + Send + Sync>>,
}
impl Default for DependencyRegistry {
fn default() -> Self {
Self::new()
}
}
impl DependencyRegistry {
pub fn new() -> Self {
Self {
singletons: HashMap::new(),
}
}
pub fn store_singleton<T: Send + Sync + 'static>(&mut self, value: T) {
let type_id = std::any::TypeId::of::<T>();
self.singletons.insert(type_id, Arc::new(value));
}
pub fn get_singleton<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
let type_id = std::any::TypeId::of::<T>();
self.singletons
.get(&type_id)
.and_then(|any| any.clone().downcast::<T>().ok())
}
pub fn has_singleton<T: 'static>(&self) -> bool {
let type_id = std::any::TypeId::of::<T>();
self.singletons.contains_key(&type_id)
}
}
pub struct ContainerBuilder {
initialization_order: Vec<String>,
initialized: std::collections::HashSet<String>,
}
impl Default for ContainerBuilder {
fn default() -> Self {
Self::new()
}
}
impl ContainerBuilder {
pub fn new() -> Self {
Self {
initialization_order: Vec::new(),
initialized: std::collections::HashSet::new(),
}
}
pub fn mark_initialized(&mut self, name: &str) {
self.initialized.insert(name.to_string());
self.initialization_order.push(name.to_string());
}
pub fn is_initialized(&self, name: &str) -> bool {
self.initialized.contains(name)
}
pub fn initialization_order(&self) -> &[String] {
&self.initialization_order
}
pub fn validate_dependencies(&self, deps: &[&str]) -> Result<(), DependencyError> {
for dep in deps {
if !self.is_initialized(dep) {
return Err(DependencyError::NotFound((*dep).to_string()));
}
}
Ok(())
}
}
pub fn env_var(name: &str) -> Result<String, DependencyError> {
std::env::var(name).map_err(|_| {
DependencyError::ConfigError(format!("Environment variable '{}' not set", name))
})
}
pub fn env_var_opt(name: &str) -> Option<String> {
std::env::var(name).ok()
}
pub fn env_var_or(name: &str, default: &str) -> String {
std::env::var(name).unwrap_or_else(|_| default.to_string())
}
pub fn env_var_parse<T: std::str::FromStr>(name: &str) -> Result<T, DependencyError>
where
T::Err: std::fmt::Display,
{
let value = env_var(name)?;
value.parse().map_err(|e: T::Err| {
DependencyError::ConfigError(format!(
"Failed to parse environment variable '{}': {}",
name, e
))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dependency_error_display() {
let err = DependencyError::NotFound("database".to_string());
assert!(err.to_string().contains("database"));
let err = DependencyError::CircularDependency(vec![
"a".to_string(),
"b".to_string(),
"a".to_string(),
]);
assert!(err.to_string().contains("a -> b -> a"));
let err = DependencyError::ConfigError("missing var".to_string());
assert!(err.to_string().contains("missing var"));
}
#[test]
fn test_scope_default() {
assert_eq!(Scope::default(), Scope::Singleton);
}
#[test]
fn test_dependency_registry() {
let mut registry = DependencyRegistry::new();
registry.store_singleton(42i32);
assert!(registry.has_singleton::<i32>());
let value = registry.get_singleton::<i32>();
assert_eq!(*value.unwrap(), 42);
assert!(!registry.has_singleton::<String>());
}
#[test]
fn test_container_builder() {
let mut builder = ContainerBuilder::new();
assert!(!builder.is_initialized("config"));
builder.mark_initialized("config");
assert!(builder.is_initialized("config"));
builder.mark_initialized("database");
assert_eq!(builder.initialization_order(), &["config", "database"]);
}
#[test]
fn test_container_builder_validate() {
let mut builder = ContainerBuilder::new();
builder.mark_initialized("config");
assert!(builder.validate_dependencies(&["config"]).is_ok());
assert!(builder.validate_dependencies(&["database"]).is_err());
}
#[test]
fn test_env_helpers() {
let value = env_var_or("NONEXISTENT_VAR_12345", "default");
assert_eq!(value, "default");
let value = env_var_opt("NONEXISTENT_VAR_12345");
assert!(value.is_none());
}
}