pub mod builder;
pub mod container;
pub mod descriptor;
pub mod errors;
pub mod lifetime;
pub mod service_key;
pub use builder::ContainerBuilder;
pub use container::{Container, ServiceProvider, ServiceScope};
pub use descriptor::{
ServiceDescriptor, ServiceFactory, ServiceProvider as ServiceProviderTrait, ServiceProviderExt,
};
pub use errors::{DiError, DiResult};
pub use lifetime::Lifetime;
pub use service_key::ServiceKey;
pub fn container_builder() -> ContainerBuilder {
ContainerBuilder::new()
}
pub fn container() -> Container {
Container::new()
}
#[cfg(test)]
mod integration_tests {
use super::*;
use crate::descriptor::ServiceProviderExt;
use std::sync::Arc;
trait ILogger: Send + Sync {
fn log(&self, message: &str);
fn get_logs(&self) -> Vec<String>;
}
struct ConsoleLogger {
logs: std::sync::Mutex<Vec<String>>,
}
impl ConsoleLogger {
fn new() -> Self {
Self {
logs: std::sync::Mutex::new(Vec::new()),
}
}
}
impl ILogger for ConsoleLogger {
fn log(&self, message: &str) {
if let Ok(mut logs) = self.logs.lock() {
logs.push(message.to_string());
}
}
fn get_logs(&self) -> Vec<String> {
self.logs.lock().unwrap().clone()
}
}
struct DatabaseService {
logger: Arc<ConsoleLogger>,
connection_string: Arc<String>,
}
impl DatabaseService {
fn connect(&self) -> String {
self.logger.log("Connecting to database");
format!("Connected to {}", self.connection_string)
}
}
struct UserService {
database: Arc<DatabaseService>,
logger: Arc<ConsoleLogger>,
}
impl UserService {
fn get_user(&self, id: i32) -> String {
self.logger.log(&format!("Getting user {id}"));
let _ = self.database.connect();
format!("User {id}")
}
}
#[test]
fn test_complete_dependency_injection_scenario() {
let provider = ContainerBuilder::new()
.add_instance("postgresql://localhost:5432/mydb".to_string())
.add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
.add_scoped_with_deps2::<DatabaseService, DatabaseService, ConsoleLogger, String>(
|logger, connection_string| DatabaseService {
logger,
connection_string,
},
)
.add_transient_with_deps2::<UserService, UserService, DatabaseService, ConsoleLogger>(
|database, logger| UserService { database, logger },
)
.build();
let mut scope = provider.create_scope().unwrap();
let user_service = scope.get_required_service::<UserService>().unwrap();
let result = user_service.get_user(123);
assert_eq!(result, "User 123");
let logger = scope.get_required_service::<ConsoleLogger>().unwrap();
let logs = logger.get_logs();
assert!(logs.contains(&"Getting user 123".to_string()));
assert!(logs.contains(&"Connecting to database".to_string()));
let db1 = scope.get_required_service::<DatabaseService>().unwrap();
let db2 = scope.get_required_service::<DatabaseService>().unwrap();
assert_eq!(db1.connection_string, db2.connection_string);
let user1 = scope.get_required_service::<UserService>().unwrap();
let user2 = scope.get_required_service::<UserService>().unwrap();
assert_eq!(
user1.database.connection_string,
user2.database.connection_string
);
scope.dispose();
}
#[test]
fn test_keyed_services_integration() {
let provider = ContainerBuilder::new()
.add_named_singleton_simple::<ConsoleLogger, ConsoleLogger>(
"console",
ConsoleLogger::new,
)
.add_named_singleton_simple::<ConsoleLogger, ConsoleLogger>("file", ConsoleLogger::new)
.add_named_instance("database_url", "postgresql://localhost/db".to_string())
.add_named_instance("cache_url", "redis://localhost/cache".to_string())
.build();
let console_logger = provider
.get_required_keyed_service::<ConsoleLogger>("console")
.unwrap();
let file_logger = provider
.get_required_keyed_service::<ConsoleLogger>("file")
.unwrap();
console_logger.log("Console message");
file_logger.log("File message");
assert_eq!(console_logger.get_logs(), vec!["Console message"]);
assert_eq!(file_logger.get_logs(), vec!["File message"]);
let db_url = provider
.get_required_keyed_service::<String>("database_url")
.unwrap();
let cache_url = provider
.get_required_keyed_service::<String>("cache_url")
.unwrap();
assert_eq!(*db_url, "postgresql://localhost/db");
assert_eq!(*cache_url, "redis://localhost/cache");
}
#[test]
fn test_singleton_across_scopes() {
let provider = ContainerBuilder::new()
.add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
.build();
let mut scope1 = provider.create_scope().unwrap();
let mut scope2 = provider.create_scope().unwrap();
let logger1 = scope1.get_required_service::<ConsoleLogger>().unwrap();
let logger2 = scope2.get_required_service::<ConsoleLogger>().unwrap();
logger1.log("test message");
assert_eq!(logger1.get_logs().len(), logger2.get_logs().len());
scope1.dispose();
scope2.dispose();
}
#[test]
fn test_error_handling() {
let provider = ContainerBuilder::new().build();
let result = provider.get_service::<String>();
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let result = provider.get_required_service::<String>();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DiError::ServiceNotRegistered { .. }
));
let result = provider.get_keyed_service::<String>("nonexistent");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let result = provider.get_required_keyed_service::<String>("nonexistent");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DiError::KeyedServiceNotRegistered { .. }
));
}
#[test]
fn test_dyn_compatibility() {
let provider = ContainerBuilder::new()
.add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
.build();
let dyn_provider: &dyn ServiceProviderTrait = &provider;
let raw_result = dyn_provider.get_service_raw(&ServiceKey::of_type::<ConsoleLogger>());
assert!(raw_result.is_ok());
assert!(raw_result.unwrap().is_some());
let typed_result = provider.get_service::<ConsoleLogger>();
assert!(typed_result.is_ok());
assert!(typed_result.unwrap().is_some());
}
#[test]
fn test_trait_object_storage() {
let provider = ContainerBuilder::new()
.add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
.build();
let providers: Vec<&dyn ServiceProviderTrait> = vec![&provider, &provider];
for p in providers {
let result = p.get_service_raw(&ServiceKey::of_type::<ConsoleLogger>());
assert!(result.is_ok());
}
}
}