use crate::container::autowiring::{DependencyResolver, Injectable};
use crate::container::binding::ServiceBinder;
use crate::container::descriptor::ServiceId;
use crate::container::ioc_container::IocContainer;
use crate::errors::CoreError;
use std::sync::Arc;
#[derive(Default)]
pub struct SqliteUserRepository {
connection_string: String,
}
impl SqliteUserRepository {
pub fn find_user(&self, id: u32) -> Option<User> {
println!(
"Finding user {} from SQLite at {}",
id, self.connection_string
);
Some(User {
id,
name: format!("User {}", id),
email: format!("user{}@example.com", id),
})
}
}
#[derive(Default)]
pub struct PostgresProductRepository;
impl PostgresProductRepository {
pub fn find_product(&self, id: u32) -> Option<Product> {
println!("Finding product {} from Postgres", id);
Some(Product {
id,
name: format!("Product {}", id),
price: 29.99,
})
}
}
#[derive(Default)]
pub struct SmtpEmailService {
smtp_server: String,
}
impl SmtpEmailService {
pub fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
println!(
"Sending email via {} to {}: {} - {}",
self.smtp_server, to, subject, body
);
Ok(())
}
}
#[derive(Default)]
pub struct PaymentProcessor;
impl PaymentProcessor {
pub fn process_payment(&self, amount: f64) -> Result<String, String> {
println!("Processing payment of ${:.2}", amount);
Ok(format!("payment_id_{}", (amount * 100.0) as u32))
}
}
#[derive(Default)]
pub struct MetricsCollector;
impl MetricsCollector {
pub fn record_metric(&self, name: &str, value: f64) {
println!("Recording metric: {} = {}", name, value);
}
}
#[derive(Debug)]
pub struct User {
pub id: u32,
pub name: String,
pub email: String,
}
#[derive(Debug)]
pub struct Product {
pub id: u32,
pub name: String,
pub price: f64,
}
#[derive(Debug)]
pub struct Order {
pub user: User,
pub product: Product,
pub payment_id: String,
}
pub struct OrderService {
user_repo: Arc<SqliteUserRepository>,
product_repo: Arc<PostgresProductRepository>,
email_service: Arc<SmtpEmailService>,
payment_processor: Arc<PaymentProcessor>,
metrics: Option<Arc<MetricsCollector>>, }
impl OrderService {
pub fn new(
user_repo: Arc<SqliteUserRepository>,
product_repo: Arc<PostgresProductRepository>,
email_service: Arc<SmtpEmailService>,
payment_processor: Arc<PaymentProcessor>,
metrics: Option<Arc<MetricsCollector>>,
) -> Self {
Self {
user_repo,
product_repo,
email_service,
payment_processor,
metrics,
}
}
pub fn create_order(&self, user_id: u32, product_id: u32) -> Result<Order, String> {
if let Some(metrics) = &self.metrics {
metrics.record_metric("order.created", 1.0);
}
let user = self.user_repo.find_user(user_id).ok_or("User not found")?;
let product = self
.product_repo
.find_product(product_id)
.ok_or("Product not found")?;
let payment_id = self.payment_processor.process_payment(product.price)?;
self.email_service.send_email(
&user.email,
"Order Confirmation",
&format!("Your order for {} has been confirmed!", product.name),
)?;
Ok(Order {
user,
product,
payment_id,
})
}
}
impl Injectable for OrderService {
fn dependencies() -> Vec<ServiceId> {
vec![
ServiceId::of::<SqliteUserRepository>(),
ServiceId::of::<PostgresProductRepository>(),
ServiceId::of::<SmtpEmailService>(),
ServiceId::of::<PaymentProcessor>(),
ServiceId::of::<MetricsCollector>(), ]
}
fn create<R: DependencyResolver>(resolver: &R) -> Result<Self, CoreError> {
let user_repo = resolver.resolve::<SqliteUserRepository>()?;
let product_repo = resolver.resolve::<PostgresProductRepository>()?;
let email_service = resolver.resolve::<SmtpEmailService>()?;
let payment_processor = resolver.resolve::<PaymentProcessor>()?;
let metrics = resolver.try_resolve::<MetricsCollector>();
Ok(OrderService::new(
user_repo,
product_repo,
email_service,
payment_processor,
metrics,
))
}
}
pub fn run_autowiring_example() -> Result<(), CoreError> {
println!("🚀 IoC Phase 2: Constructor Injection & Auto-wiring Example");
println!("============================================================");
let mut container = IocContainer::new();
container
.bind::<SqliteUserRepository, SqliteUserRepository>()
.bind::<PostgresProductRepository, PostgresProductRepository>()
.bind::<SmtpEmailService, SmtpEmailService>()
.bind::<PaymentProcessor, PaymentProcessor>()
.bind::<MetricsCollector, MetricsCollector>() .bind_injectable::<OrderService>();
container.build()?;
println!(
"\n📦 Container built successfully with {} services",
container.service_count()
);
let order_service = container.resolve_injectable::<OrderService>()?;
println!("\n🎯 OrderService resolved with auto-wiring!");
println!(" Dependencies automatically injected:");
println!(" - SqliteUserRepository");
println!(" - PostgresProductRepository");
println!(" - SmtpEmailService");
println!(" - PaymentProcessor");
println!(" - MetricsCollector (optional)");
println!("\n📋 Creating order...");
let order = order_service
.create_order(1, 101)
.map_err(|e| CoreError::InvalidServiceDescriptor { message: e })?;
println!("\n✅ Order created successfully!");
println!(" User: {} ({})", order.user.name, order.user.email);
println!(
" Product: {} - ${:.2}",
order.product.name, order.product.price
);
println!(" Payment ID: {}", order.payment_id);
println!("\n🔧 Testing without optional dependency...");
let mut container_minimal = IocContainer::new();
container_minimal
.bind::<SqliteUserRepository, SqliteUserRepository>()
.bind::<PostgresProductRepository, PostgresProductRepository>()
.bind::<SmtpEmailService, SmtpEmailService>()
.bind::<PaymentProcessor, PaymentProcessor>();
container_minimal.build()?;
let user_repo = container_minimal.resolve::<SqliteUserRepository>()?;
let product_repo = container_minimal.resolve::<PostgresProductRepository>()?;
let email_service = container_minimal.resolve::<SmtpEmailService>()?;
let payment_processor = container_minimal.resolve::<PaymentProcessor>()?;
let metrics = container_minimal.try_resolve::<MetricsCollector>();
let order_service_minimal = OrderService::new(
user_repo,
product_repo,
email_service,
payment_processor,
metrics,
);
println!("✅ OrderService created even without optional MetricsCollector!");
let _order2 = order_service_minimal
.create_order(2, 102)
.map_err(|e| CoreError::InvalidServiceDescriptor { message: e })?;
println!("✅ Order processing works without metrics service");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autowiring_example() {
run_autowiring_example().expect("Autowiring example should work");
}
#[test]
fn test_dependency_resolution() {
let deps = OrderService::dependencies();
assert_eq!(deps.len(), 5);
assert!(deps.contains(&ServiceId::of::<SqliteUserRepository>()));
assert!(deps.contains(&ServiceId::of::<PostgresProductRepository>()));
assert!(deps.contains(&ServiceId::of::<SmtpEmailService>()));
assert!(deps.contains(&ServiceId::of::<PaymentProcessor>()));
assert!(deps.contains(&ServiceId::of::<MetricsCollector>()));
}
#[test]
fn test_optional_dependency_handling() {
let mut container = IocContainer::new();
container
.bind::<SqliteUserRepository, SqliteUserRepository>()
.bind::<PostgresProductRepository, PostgresProductRepository>()
.bind::<SmtpEmailService, SmtpEmailService>()
.bind::<PaymentProcessor, PaymentProcessor>();
container.build().unwrap();
let user_repo = container.resolve::<SqliteUserRepository>().unwrap();
let product_repo = container.resolve::<PostgresProductRepository>().unwrap();
let email_service = container.resolve::<SmtpEmailService>().unwrap();
let payment_processor = container.resolve::<PaymentProcessor>().unwrap();
let metrics = container.try_resolve::<MetricsCollector>();
assert!(
metrics.is_none(),
"MetricsCollector should not be available"
);
let order_service = OrderService::new(
user_repo,
product_repo,
email_service,
payment_processor,
metrics,
);
let order = order_service.create_order(1, 101).unwrap();
assert_eq!(order.user.id, 1);
assert_eq!(order.product.id, 101);
assert!(!order.payment_id.is_empty());
}
#[test]
fn test_complex_dependency_graph() {
let mut container = IocContainer::new();
container
.bind::<SqliteUserRepository, SqliteUserRepository>()
.bind::<PostgresProductRepository, PostgresProductRepository>()
.bind::<SmtpEmailService, SmtpEmailService>()
.bind::<PaymentProcessor, PaymentProcessor>()
.bind::<MetricsCollector, MetricsCollector>()
.bind_injectable::<OrderService>();
container.build().unwrap();
assert!(container.resolve::<SqliteUserRepository>().is_ok());
assert!(container.resolve::<PostgresProductRepository>().is_ok());
assert!(container.resolve::<SmtpEmailService>().is_ok());
assert!(container.resolve::<PaymentProcessor>().is_ok());
assert!(container.resolve::<MetricsCollector>().is_ok());
assert!(container.resolve_injectable::<OrderService>().is_ok());
}
}