use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::container::autowiring::{DependencyResolver, Injectable};
use crate::container::binding::{ServiceBinder, ServiceBindings};
use crate::container::descriptor::ServiceId;
use crate::container::lifecycle::ServiceLifecycleManager;
use crate::container::resolver::DependencyResolver as GraphDependencyResolver;
use crate::container::scope::{ScopeId, ScopedServiceManager, ServiceScope};
use crate::container::tokens::{ServiceToken, TokenRegistry};
use crate::errors::CoreError;
#[derive(Debug)]
enum ServiceInstance {
Singleton(Arc<dyn Any + Send + Sync>),
Scoped(HashMap<ScopeId, Arc<dyn Any + Send + Sync>>),
}
#[derive(Debug)]
pub struct IocContainer {
bindings: ServiceBindings,
tokens: TokenRegistry,
resolver: Option<GraphDependencyResolver>,
instances: Arc<RwLock<HashMap<ServiceId, ServiceInstance>>>,
lifecycle_manager: ServiceLifecycleManager,
scopes: Arc<RwLock<HashMap<ScopeId, Arc<ScopedServiceManager>>>>,
is_built: bool,
}
impl IocContainer {
pub fn new() -> Self {
Self {
bindings: ServiceBindings::new(),
tokens: TokenRegistry::new(),
resolver: None,
instances: Arc::new(RwLock::new(HashMap::new())),
lifecycle_manager: ServiceLifecycleManager::new(),
scopes: Arc::new(RwLock::new(HashMap::new())),
is_built: false,
}
}
pub fn from_bindings(bindings: ServiceBindings) -> Self {
Self {
bindings,
tokens: TokenRegistry::new(),
resolver: None,
instances: Arc::new(RwLock::new(HashMap::new())),
lifecycle_manager: ServiceLifecycleManager::new(),
scopes: Arc::new(RwLock::new(HashMap::new())),
is_built: false,
}
}
pub fn build(&mut self) -> Result<(), CoreError> {
if self.is_built {
return Ok(());
}
let resolver = GraphDependencyResolver::new(self.bindings.descriptors())?;
self.resolver = Some(resolver);
let service_ids = self.bindings.service_ids().into_iter().collect();
if let Some(resolver) = &self.resolver {
resolver.validate_dependencies(&service_ids)?;
}
self.is_built = true;
Ok(())
}
pub async fn initialize_async(&mut self) -> Result<(), CoreError> {
self.lifecycle_manager.initialize_all().await
}
pub async fn initialize_async_with_timeout(
&mut self,
timeout: std::time::Duration,
) -> Result<(), CoreError> {
self.lifecycle_manager
.initialize_all_with_timeout(timeout)
.await
}
pub fn create_scope(&self) -> Result<ScopeId, CoreError> {
let scope_manager = Arc::new(ScopedServiceManager::new());
let scope_id = scope_manager.scope_id().clone();
let mut scopes = self.scopes.write().map_err(|_| CoreError::LockError {
resource: "scopes".to_string(),
})?;
scopes.insert(scope_id.clone(), scope_manager);
Ok(scope_id)
}
pub fn create_child_scope(&self, parent_scope_id: &ScopeId) -> Result<ScopeId, CoreError> {
let mut scopes = self.scopes.write().map_err(|_| CoreError::LockError {
resource: "scopes".to_string(),
})?;
let parent_scope = scopes
.get(parent_scope_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!("parent scope {}", parent_scope_id),
})?
.clone();
let child_scope = Arc::new(ScopedServiceManager::create_child(parent_scope));
let child_scope_id = child_scope.scope_id().clone();
scopes.insert(child_scope_id.clone(), child_scope);
Ok(child_scope_id)
}
pub async fn dispose_scope(&self, scope_id: &ScopeId) -> Result<(), CoreError> {
let was_removed = {
let mut scopes = self.scopes.write().map_err(|_| CoreError::LockError {
resource: "scopes".to_string(),
})?;
scopes.remove(scope_id).is_some()
};
if was_removed {
let mut instances = self.instances.write().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
for (_, instance) in instances.iter_mut() {
if let ServiceInstance::Scoped(scoped_instances) = instance {
scoped_instances.remove(scope_id);
}
}
}
Ok(())
}
pub async fn dispose_all(&mut self) -> Result<(), CoreError> {
let scope_ids: Vec<ScopeId> = {
let scopes = self.scopes.read().map_err(|_| CoreError::LockError {
resource: "scopes".to_string(),
})?;
scopes.keys().cloned().collect()
};
for scope_id in scope_ids {
self.dispose_scope(&scope_id).await?;
}
self.lifecycle_manager.dispose_all().await?;
Ok(())
}
pub fn lifecycle_manager(&self) -> &ServiceLifecycleManager {
&self.lifecycle_manager
}
pub fn lifecycle_manager_mut(&mut self) -> &mut ServiceLifecycleManager {
&mut self.lifecycle_manager
}
pub fn resolve<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, CoreError> {
let service_id = ServiceId::of::<T>();
self.resolve_by_id(&service_id)
}
pub fn resolve_scoped<T: Send + Sync + 'static>(
&self,
scope_id: &ScopeId,
) -> Result<Arc<T>, CoreError> {
let service_id = ServiceId::of::<T>();
self.resolve_by_id_scoped(&service_id, scope_id)
}
pub fn resolve_named<T: Send + Sync + 'static>(&self, name: &str) -> Result<Arc<T>, CoreError> {
self.resolve_named_by_str::<T>(name)
}
fn resolve_named_by_str<T: Send + Sync + 'static>(
&self,
name: &str,
) -> Result<Arc<T>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let service_id = ServiceId::named::<T>(name.to_string());
{
let instances = self.instances.read().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
if let Some(ServiceInstance::Singleton(instance)) = instances.get(&service_id) {
return instance
.clone()
.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: format!("{}({})", std::any::type_name::<T>(), name),
});
}
}
let descriptor = self
.bindings
.get_descriptor_named::<T>(name)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!("{}({})", std::any::type_name::<T>(), name),
})?;
self.resolve_dependencies(&descriptor.dependencies)?;
let arc_instance = match &descriptor.activation_strategy {
crate::container::descriptor::ServiceActivationStrategy::Factory(factory) => {
let instance = factory()?;
let typed_instance =
instance
.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: format!("{}({})", std::any::type_name::<T>(), name),
})?;
Arc::new(*typed_instance)
}
crate::container::descriptor::ServiceActivationStrategy::AutoWired => {
return Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Service {}({}) is marked as auto-wired but resolve_named was called instead of resolve_injectable. Use resolve_injectable() for auto-wired services.",
std::any::type_name::<T>(),
name
),
});
}
};
if descriptor.lifetime == ServiceScope::Singleton {
let mut instances = self.instances.write().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
instances.insert(service_id, ServiceInstance::Singleton(arc_instance.clone()));
}
Ok(arc_instance)
}
fn resolve_by_id<T: Send + Sync + 'static>(
&self,
service_id: &ServiceId,
) -> Result<Arc<T>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
{
let instances = self.instances.read().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
if let Some(ServiceInstance::Singleton(instance)) = instances.get(service_id) {
return instance
.clone()
.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
});
}
}
let descriptor =
self.bindings
.get_descriptor(service_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
})?;
self.resolve_dependencies(&descriptor.dependencies)?;
let arc_instance = match &descriptor.activation_strategy {
crate::container::descriptor::ServiceActivationStrategy::Factory(factory) => {
let instance = factory()?;
let typed_instance =
instance
.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
})?;
Arc::new(*typed_instance)
}
crate::container::descriptor::ServiceActivationStrategy::AutoWired => {
return Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Service {} is marked as auto-wired but resolve_by_id was called instead of resolve_injectable. Use resolve_injectable() for auto-wired services.",
std::any::type_name::<T>()
),
});
}
};
if descriptor.lifetime == ServiceScope::Singleton {
let mut instances = self.instances.write().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
instances.insert(
service_id.clone(),
ServiceInstance::Singleton(arc_instance.clone()),
);
}
Ok(arc_instance)
}
fn resolve_by_id_scoped<T: Send + Sync + 'static>(
&self,
service_id: &ServiceId,
scope_id: &ScopeId,
) -> Result<Arc<T>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let descriptor =
self.bindings
.get_descriptor(service_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
})?;
match descriptor.lifetime {
ServiceScope::Singleton => {
self.resolve_by_id(service_id)
}
ServiceScope::Transient => {
self.create_service_instance::<T>(service_id, descriptor)
}
ServiceScope::Scoped => {
{
let instances = self.instances.read().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
if let Some(ServiceInstance::Scoped(scoped_instances)) =
instances.get(service_id)
{
if let Some(instance) = scoped_instances.get(scope_id) {
return instance.clone().downcast::<T>().map_err(|_| {
CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
}
});
}
}
}
let arc_instance = self.create_service_instance::<T>(service_id, descriptor)?;
let mut instances = self.instances.write().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
use std::collections::hash_map::Entry;
match instances.entry(service_id.clone()) {
Entry::Occupied(mut entry) => match entry.get_mut() {
ServiceInstance::Scoped(scoped_instances) => {
scoped_instances.insert(
scope_id.clone(),
arc_instance.clone() as Arc<dyn Any + Send + Sync>,
);
}
ServiceInstance::Singleton(_) => {
return Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Service {} is registered as both Singleton and Scoped. This is a configuration error.",
std::any::type_name::<T>()
),
});
}
},
Entry::Vacant(entry) => {
let mut scoped_map = HashMap::new();
scoped_map.insert(
scope_id.clone(),
arc_instance.clone() as Arc<dyn Any + Send + Sync>,
);
entry.insert(ServiceInstance::Scoped(scoped_map));
}
}
Ok(arc_instance)
}
}
}
fn create_service_instance<T: Send + Sync + 'static>(
&self,
service_id: &ServiceId,
descriptor: &crate::container::descriptor::ServiceDescriptor,
) -> Result<Arc<T>, CoreError> {
self.resolve_dependencies(&descriptor.dependencies)?;
match &descriptor.activation_strategy {
crate::container::descriptor::ServiceActivationStrategy::Factory(factory) => {
let instance = factory()?;
let typed_instance = instance.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: format!("{}({})",
std::any::type_name::<T>(),
service_id.name.as_deref().unwrap_or("default")
),
})?;
Ok(Arc::new(*typed_instance))
},
crate::container::descriptor::ServiceActivationStrategy::AutoWired => {
Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Service {} is marked as auto-wired but create_service_instance was called. Use resolve_injectable() for auto-wired services.",
std::any::type_name::<T>()
),
})
}
}
}
fn resolve_dependencies(&self, dependencies: &[ServiceId]) -> Result<(), CoreError> {
for dep_id in dependencies {
if !self.bindings.contains(dep_id) {
return Err(CoreError::ServiceNotFound {
service_type: format!(
"{}({})",
dep_id.type_name(),
dep_id.name.as_deref().unwrap_or("default")
),
});
}
}
Ok(())
}
pub fn try_resolve<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
self.resolve::<T>().ok()
}
pub fn try_resolve_named<T: Send + Sync + 'static>(&self, name: &str) -> Option<Arc<T>> {
self.resolve_named::<T>(name).ok()
}
pub fn resolve_injectable<T: Injectable>(&self) -> Result<Arc<T>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let service_id = ServiceId::of::<T>();
{
let instances = self.instances.read().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
if let Some(ServiceInstance::Singleton(instance)) = instances.get(&service_id) {
return instance
.clone()
.downcast::<T>()
.map_err(|_| CoreError::ServiceNotFound {
service_type: std::any::type_name::<T>().to_string(),
});
}
}
let descriptor = self.bindings.get_descriptor(&service_id).ok_or_else(|| {
CoreError::ServiceNotFound {
service_type: std::any::type_name::<T>().to_string(),
}
})?;
let arc_instance = match &descriptor.activation_strategy {
crate::container::descriptor::ServiceActivationStrategy::AutoWired => {
let service_instance = T::create(self)?;
Arc::new(service_instance)
}
crate::container::descriptor::ServiceActivationStrategy::Factory(_) => {
return Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Service {} is configured with a factory but resolve_injectable was called. Use resolve() for factory-based services.",
std::any::type_name::<T>()
),
});
}
};
if descriptor.lifetime == ServiceScope::Singleton {
let mut instances = self.instances.write().map_err(|_| CoreError::LockError {
resource: "service_instances".to_string(),
})?;
instances.insert(service_id, ServiceInstance::Singleton(arc_instance.clone()));
}
Ok(arc_instance)
}
pub fn resolve_trait<T: ?Sized + Send + Sync + 'static>(&self) -> Result<Arc<T>, CoreError> {
Err(CoreError::ServiceNotFound {
service_type: std::any::type_name::<T>().to_string(),
})
}
pub fn bind_token<Token, Impl>(&mut self) -> Result<&mut Self, CoreError>
where
Token: ServiceToken,
Impl: Send + Sync + Default + 'static,
{
self.bind_token_with_lifetime::<Token, Impl>(ServiceScope::Transient)
}
pub fn bind_token_singleton<Token, Impl>(&mut self) -> Result<&mut Self, CoreError>
where
Token: ServiceToken,
Impl: Send + Sync + Default + 'static,
{
self.bind_token_with_lifetime::<Token, Impl>(ServiceScope::Singleton)
}
pub fn bind_token_scoped<Token, Impl>(&mut self) -> Result<&mut Self, CoreError>
where
Token: ServiceToken,
Impl: Send + Sync + Default + 'static,
{
self.bind_token_with_lifetime::<Token, Impl>(ServiceScope::Scoped)
}
pub fn bind_token_with_lifetime<Token, Impl>(
&mut self,
lifetime: ServiceScope,
) -> Result<&mut Self, CoreError>
where
Token: ServiceToken,
Impl: Send + Sync + Default + 'static,
{
if self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Cannot bind tokens after container is built".to_string(),
});
}
self.tokens
.register::<Token, Impl>()
.map_err(|e| CoreError::InvalidServiceDescriptor {
message: format!("Failed to register token binding: {}", e),
})?;
let token_binding = self.tokens.get_default::<Token>().ok_or_else(|| {
CoreError::InvalidServiceDescriptor {
message: "Failed to retrieve token binding after registration".to_string(),
}
})?;
let service_id = token_binding.to_service_id();
let descriptor = crate::container::descriptor::ServiceDescriptor {
service_id,
implementation_id: std::any::TypeId::of::<Impl>(),
lifetime,
activation_strategy: crate::container::descriptor::ServiceActivationStrategy::Factory(
Box::new(|| Ok(Box::new(Impl::default()) as Box<dyn Any + Send + Sync>)),
),
dependencies: Vec::new(),
};
self.bindings.add_descriptor(descriptor);
Ok(self)
}
pub fn bind_token_named<Token, Impl>(
&mut self,
name: impl Into<String>,
) -> Result<&mut Self, CoreError>
where
Token: ServiceToken,
Impl: Send + Sync + Default + 'static,
{
if self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Cannot bind tokens after container is built".to_string(),
});
}
let name = name.into();
self.tokens
.register_named::<Token, Impl>(&name)
.map_err(|e| CoreError::InvalidServiceDescriptor {
message: format!("Failed to register named token binding: {}", e),
})?;
let token_binding = self.tokens.get_named::<Token>(&name).ok_or_else(|| {
CoreError::InvalidServiceDescriptor {
message: "Failed to retrieve named token binding after registration".to_string(),
}
})?;
let service_id = token_binding.to_service_id();
let descriptor = crate::container::descriptor::ServiceDescriptor {
service_id,
implementation_id: std::any::TypeId::of::<Impl>(),
lifetime: ServiceScope::Transient,
activation_strategy: crate::container::descriptor::ServiceActivationStrategy::Factory(
Box::new(|| Ok(Box::new(Impl::default()) as Box<dyn Any + Send + Sync>)),
),
dependencies: Vec::new(),
};
self.bindings.add_descriptor(descriptor);
Ok(self)
}
pub fn resolve_by_token<Token>(&self) -> Result<Arc<Token::Service>, CoreError>
where
Token: ServiceToken,
Token::Service: 'static,
{
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let token_binding =
self.tokens
.get_default::<Token>()
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"token {} -> {}",
Token::token_type_name(),
Token::service_type_name()
),
})?;
let service_id = token_binding.to_service_id();
self.resolve_by_id_as_trait::<Token::Service>(&service_id)
}
pub fn resolve_by_token_named<Token>(
&self,
name: &str,
) -> Result<Arc<Token::Service>, CoreError>
where
Token: ServiceToken,
Token::Service: 'static,
{
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let token_binding =
self.tokens
.get_named::<Token>(name)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"named token {}({}) -> {}",
Token::token_type_name(),
name,
Token::service_type_name()
),
})?;
let service_id = token_binding.to_service_id();
self.resolve_by_id_as_trait::<Token::Service>(&service_id)
}
pub fn try_resolve_by_token<Token>(&self) -> Option<Arc<Token::Service>>
where
Token: ServiceToken,
Token::Service: 'static,
{
self.resolve_by_token::<Token>().ok()
}
pub fn try_resolve_by_token_named<Token>(&self, name: &str) -> Option<Arc<Token::Service>>
where
Token: ServiceToken,
Token::Service: 'static,
{
self.resolve_by_token_named::<Token>(name).ok()
}
pub fn resolve_by_token_scoped<Token>(
&self,
scope_id: &ScopeId,
) -> Result<Arc<Token::Service>, CoreError>
where
Token: ServiceToken,
Token::Service: 'static,
{
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let token_binding =
self.tokens
.get_default::<Token>()
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"token {} -> {}",
Token::token_type_name(),
Token::service_type_name()
),
})?;
let service_id = token_binding.to_service_id();
self.resolve_by_id_as_trait_scoped::<Token::Service>(&service_id, scope_id)
}
pub fn resolve_by_token_named_scoped<Token>(
&self,
name: &str,
scope_id: &ScopeId,
) -> Result<Arc<Token::Service>, CoreError>
where
Token: ServiceToken,
Token::Service: 'static,
{
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let token_binding =
self.tokens
.get_named::<Token>(name)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!(
"named token {}({}) -> {}",
Token::token_type_name(),
name,
Token::service_type_name()
),
})?;
let service_id = token_binding.to_service_id();
self.resolve_by_id_as_trait_scoped::<Token::Service>(&service_id, scope_id)
}
pub fn try_resolve_by_token_scoped<Token>(
&self,
scope_id: &ScopeId,
) -> Option<Arc<Token::Service>>
where
Token: ServiceToken,
Token::Service: 'static,
{
self.resolve_by_token_scoped::<Token>(scope_id).ok()
}
pub fn try_resolve_by_token_named_scoped<Token>(
&self,
name: &str,
scope_id: &ScopeId,
) -> Option<Arc<Token::Service>>
where
Token: ServiceToken,
Token::Service: 'static,
{
self.resolve_by_token_named_scoped::<Token>(name, scope_id)
.ok()
}
pub fn contains_token<Token: ServiceToken>(&self) -> bool {
self.tokens.contains::<Token>()
}
pub fn contains_token_named<Token: ServiceToken>(&self, name: &str) -> bool {
self.tokens.contains_named::<Token>(name)
}
pub fn token_stats(&self) -> crate::container::tokens::TokenRegistryStats {
self.tokens.stats()
}
fn resolve_by_id_as_trait<T: ?Sized + Send + Sync + 'static>(
&self,
service_id: &ServiceId,
) -> Result<Arc<T>, CoreError> {
Err(CoreError::ServiceNotFound {
service_type: format!(
"trait object resolution not yet fully implemented for service {}",
service_id.type_name()
),
})
}
fn resolve_by_id_as_trait_scoped<T: ?Sized + Send + Sync + 'static>(
&self,
service_id: &ServiceId,
_scope_id: &ScopeId,
) -> Result<Arc<T>, CoreError> {
Err(CoreError::ServiceNotFound {
service_type: format!(
"scoped trait object resolution not yet fully implemented for service {}",
service_id.type_name()
),
})
}
pub fn contains<T: 'static>(&self) -> bool {
let service_id = ServiceId::of::<T>();
self.bindings.contains(&service_id)
}
pub fn contains_named<T: 'static>(&self, name: &str) -> bool {
self.bindings.contains_named::<T>(name)
}
pub fn service_count(&self) -> usize {
self.bindings.count()
}
pub fn registered_services(&self) -> Vec<ServiceId> {
self.bindings.service_ids()
}
pub fn is_built(&self) -> bool {
self.is_built
}
pub fn validate(&self) -> Result<(), CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before validation".to_string(),
});
}
if let Some(resolver) = &self.resolver {
let service_ids = self.bindings.service_ids().into_iter().collect();
resolver.validate_dependencies(&service_ids)?;
}
Ok(())
}
pub fn resolve_all<T: Send + Sync + 'static>(&self) -> Result<Vec<Arc<T>>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let mut implementations = Vec::new();
for descriptor in self.bindings.descriptors() {
if descriptor.service_id.type_id == std::any::TypeId::of::<T>() {
match self.resolve_by_id::<T>(&descriptor.service_id) {
Ok(instance) => implementations.push(instance),
Err(_) => continue, }
}
}
if implementations.is_empty() {
return Err(CoreError::ServiceNotFound {
service_type: std::any::type_name::<T>().to_string(),
});
}
Ok(implementations)
}
pub fn resolve_all_named<T: Send + Sync + 'static>(
&self,
) -> Result<std::collections::HashMap<String, Arc<T>>, CoreError> {
if !self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Container must be built before resolving services".to_string(),
});
}
let mut implementations = std::collections::HashMap::new();
for descriptor in self.bindings.descriptors() {
if descriptor.service_id.type_id == std::any::TypeId::of::<T>() {
if let Some(name) = &descriptor.service_id.name {
match self.resolve_by_id::<T>(&descriptor.service_id) {
Ok(instance) => {
implementations.insert(name.clone(), instance);
}
Err(_) => continue, }
}
}
}
if implementations.is_empty() {
return Err(CoreError::ServiceNotFound {
service_type: format!("named implementations of {}", std::any::type_name::<T>()),
});
}
Ok(implementations)
}
pub fn resolve_default<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, CoreError> {
self.resolve::<T>()
}
pub fn get_service_info<T: 'static>(&self) -> Option<String> {
let service_id = ServiceId::of::<T>();
self.bindings
.get_descriptor(&service_id)
.map(|desc| format!("{:?}", desc))
}
pub fn get_registered_services(&self) -> Vec<String> {
self.bindings
.service_ids()
.into_iter()
.map(|id| {
format!(
"{} ({})",
id.type_name(),
id.name.unwrap_or_else(|| "default".to_string())
)
})
.collect()
}
pub fn validate_all_services(&self) -> Result<(), Vec<CoreError>> {
if !self.is_built {
return Err(vec![CoreError::InvalidServiceDescriptor {
message: "Container must be built before validation".to_string(),
}]);
}
let mut errors = Vec::new();
for descriptor in self.bindings.descriptors() {
for dependency in &descriptor.dependencies {
if !self.bindings.contains(dependency) {
errors.push(CoreError::ServiceNotFound {
service_type: format!(
"{} (dependency of {})",
dependency.type_name(),
descriptor.service_id.type_name()
),
});
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn get_statistics(&self) -> ServiceStatistics {
let mut stats = ServiceStatistics::default();
stats.total_services = self.bindings.count();
stats.singleton_services = 0;
stats.transient_services = 0;
stats.scoped_services = 0;
stats.cached_instances = 0;
for descriptor in self.bindings.descriptors() {
match descriptor.lifetime {
crate::container::scope::ServiceScope::Singleton => stats.singleton_services += 1,
crate::container::scope::ServiceScope::Transient => stats.transient_services += 1,
crate::container::scope::ServiceScope::Scoped => stats.scoped_services += 1,
}
}
if let Ok(instances) = self.instances.read() {
stats.cached_instances = instances.len();
}
stats
}
}
#[derive(Debug, Default)]
pub struct ServiceStatistics {
pub total_services: usize,
pub singleton_services: usize,
pub transient_services: usize,
pub scoped_services: usize,
pub cached_instances: usize,
}
impl ServiceBinder for IocContainer {
fn add_service_descriptor(
&mut self,
descriptor: crate::container::descriptor::ServiceDescriptor,
) -> Result<&mut Self, CoreError> {
if self.is_built {
return Err(CoreError::InvalidServiceDescriptor {
message: "Cannot add service descriptors after container is built".to_string(),
});
}
self.bindings.add_descriptor(descriptor);
Ok(self)
}
fn bind<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind::<TInterface, TImpl>();
self
}
fn bind_singleton<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_singleton::<TInterface, TImpl>();
self
}
fn bind_transient<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_transient::<TInterface, TImpl>();
self
}
fn bind_factory<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
where
F: Fn() -> Result<T, CoreError> + Send + Sync + 'static,
T: Send + Sync + 'static,
{
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_factory::<TInterface, _, _>(factory);
self
}
fn bind_instance<TInterface: ?Sized + 'static, TImpl: Send + Sync + Clone + 'static>(
&mut self,
instance: TImpl,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_instance::<TInterface, TImpl>(instance);
self
}
fn bind_named<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
name: &str,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_named::<TInterface, TImpl>(name);
self
}
fn bind_injectable<T: Injectable>(&mut self) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_injectable::<T>();
self
}
fn bind_injectable_singleton<T: Injectable>(&mut self) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_injectable_singleton::<T>();
self
}
fn bind_with<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
) -> crate::container::binding::AdvancedBindingBuilder<TInterface> {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_with::<TInterface, TImpl>()
}
fn with_implementation<TInterface: ?Sized + 'static, TImpl: Send + Sync + Default + 'static>(
&mut self,
config: crate::container::binding::BindingConfig,
) -> &mut Self {
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings
.with_implementation::<TInterface, TImpl>(config);
self
}
fn bind_lazy<TInterface: ?Sized + 'static, F, T>(&mut self, factory: F) -> &mut Self
where
F: Fn() -> T + Send + Sync + 'static,
T: Send + Sync + 'static,
{
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_lazy::<TInterface, F, T>(factory);
self
}
fn bind_parameterized_factory<TInterface: ?Sized + 'static, P, F, T>(
&mut self,
factory: F,
) -> &mut Self
where
F: Fn(P) -> Result<T, CoreError> + Send + Sync + 'static,
T: Send + Sync + 'static,
P: Send + Sync + 'static,
{
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings
.bind_parameterized_factory::<TInterface, P, F, T>(factory);
self
}
fn bind_collection<TInterface: ?Sized + 'static, F>(&mut self, configure: F) -> &mut Self
where
F: FnOnce(&mut crate::container::binding::CollectionBindingBuilder<TInterface>),
{
if self.is_built {
panic!("Cannot add bindings after container is built");
}
self.bindings.bind_collection::<TInterface, F>(configure);
self
}
}
impl Default for IocContainer {
fn default() -> Self {
Self::new()
}
}
impl DependencyResolver for IocContainer {
fn resolve<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, CoreError> {
self.resolve::<T>()
}
fn resolve_named<T: Send + Sync + 'static>(&self, name: &str) -> Result<Arc<T>, CoreError> {
self.resolve_named::<T>(name)
}
fn try_resolve<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
self.try_resolve::<T>()
}
fn try_resolve_named<T: Send + Sync + 'static>(&self, name: &str) -> Option<Arc<T>> {
self.try_resolve_named::<T>(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
trait TestRepository: Send + Sync {
fn find(&self, id: u32) -> Option<String>;
}
#[derive(Default)]
struct PostgresRepository;
unsafe impl Send for PostgresRepository {}
unsafe impl Sync for PostgresRepository {}
impl TestRepository for PostgresRepository {
fn find(&self, _id: u32) -> Option<String> {
Some("postgres_data".to_string())
}
}
trait TestService: Send + Sync {
fn get_data(&self) -> String;
}
#[derive(Default)]
struct UserService;
unsafe impl Send for UserService {}
unsafe impl Sync for UserService {}
impl TestService for UserService {
fn get_data(&self) -> String {
"user_data".to_string()
}
}
#[test]
fn test_basic_binding_and_resolution() {
let mut container = IocContainer::new();
container
.bind::<PostgresRepository, PostgresRepository>()
.bind_singleton::<UserService, UserService>();
container.build().unwrap();
let repo = container.resolve::<PostgresRepository>().unwrap();
assert_eq!(repo.find(1), Some("postgres_data".to_string()));
let service = container.resolve::<UserService>().unwrap();
assert_eq!(service.get_data(), "user_data");
}
#[test]
fn test_named_services() {
let mut container = IocContainer::new();
container
.bind_named::<PostgresRepository, PostgresRepository>("postgres")
.bind_named::<PostgresRepository, PostgresRepository>("backup");
container.build().unwrap();
let postgres_repo = container
.resolve_named::<PostgresRepository>("postgres")
.unwrap();
let backup_repo = container
.resolve_named::<PostgresRepository>("backup")
.unwrap();
assert_eq!(postgres_repo.find(1), Some("postgres_data".to_string()));
assert_eq!(backup_repo.find(1), Some("postgres_data".to_string()));
}
#[test]
fn test_singleton_behavior() {
let mut container = IocContainer::new();
container.bind_singleton::<UserService, UserService>();
container.build().unwrap();
let service1 = container.resolve::<UserService>().unwrap();
let service2 = container.resolve::<UserService>().unwrap();
assert!(Arc::ptr_eq(&service1, &service2));
}
#[test]
fn test_transient_behavior() {
let mut container = IocContainer::new();
container.bind_transient::<UserService, UserService>();
container.build().unwrap();
let service1 = container.resolve::<UserService>().unwrap();
let service2 = container.resolve::<UserService>().unwrap();
assert!(!Arc::ptr_eq(&service1, &service2));
}
#[test]
#[should_panic(expected = "Cannot add bindings after container is built")]
fn test_cannot_bind_after_build() {
let mut container = IocContainer::new();
container.build().unwrap();
container.bind::<UserService, UserService>();
}
#[test]
fn test_service_not_found() {
let mut container = IocContainer::new();
container.build().unwrap();
let result = container.resolve::<UserService>();
assert!(result.is_err());
if let Err(CoreError::ServiceNotFound { service_type }) = result {
assert!(service_type.contains("UserService"));
}
}
}