use crate::cache::LazyCache;
use crate::entry::{IServiceResolver, ServiceEntry, ServiceLifetime, ServiceStore};
use crate::error::RdiError;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
pub(crate) struct ValidationResolver<'a> {
store: &'a ServiceStore,
resolving: RwLock<Vec<usize>>,
stack: RwLock<Vec<(usize, ServiceLifetime)>>,
type_names: &'a HashMap<TypeId, &'static str>,
cache: &'a LazyCache,
last_error: Mutex<Option<RdiError>>,
}
impl<'a> ValidationResolver<'a> {
pub(crate) fn new(
store: &'a ServiceStore,
type_names: &'a HashMap<TypeId, &'static str>,
cache: &'a LazyCache,
) -> Self {
Self {
store,
resolving: RwLock::new(Vec::new()),
stack: RwLock::new(Vec::new()),
type_names,
cache,
last_error: Mutex::new(None),
}
}
pub(crate) fn take_error(&self) -> Option<RdiError> {
self.last_error.lock().unwrap().take()
}
fn get_type_name(&self, cache_key: usize) -> String {
self.store
.iter()
.find(|(_, entries)| entries.iter().any(|e| e.cache_key == cache_key))
.and_then(|(_, entries)| entries.first())
.map(|e| {
e.type_name
.split("::")
.last()
.unwrap_or(e.type_name)
.to_string()
})
.unwrap_or_else(|| format!("service {}", cache_key))
}
fn check_captive_dependency(&self, entry: &ServiceEntry) -> bool {
if entry.lifetime == ServiceLifetime::Scoped {
let stack = self.stack.read().unwrap();
if let Some(&(ancestor_ck, ServiceLifetime::Singleton)) = stack
.iter()
.find(|(_, lt)| *lt == ServiceLifetime::Singleton)
{
let singleton_name = self.get_type_name(ancestor_ck);
let scoped_name = self.get_type_name(entry.cache_key);
*self.last_error.lock().unwrap() =
Some(RdiError::SingletonDependsOnScoped {
singleton: singleton_name,
scoped: scoped_name,
});
return true;
}
}
false
}
fn check_cycle(&self, entry: &ServiceEntry) -> bool {
let resolving = self.resolving.read().unwrap();
if resolving.contains(&entry.cache_key) {
let type_name = self.get_type_name(entry.cache_key);
let cycle_desc = if resolving.is_empty() {
type_name
} else {
let chain: Vec<String> = resolving
.iter()
.map(|&ck| self.get_type_name(ck))
.collect();
format!("{} -> {}", chain.join(" -> "), type_name)
};
*self.last_error.lock().unwrap() = Some(RdiError::CircularDependency(cycle_desc));
return true;
}
false
}
fn resolve_via_factory(
&self,
entry: &ServiceEntry,
) -> Option<Arc<dyn Any + Send + Sync>> {
if self.check_captive_dependency(entry) {
return None;
}
if self.check_cycle(entry) {
return None;
}
self.resolving.write().unwrap().push(entry.cache_key);
self.stack
.write()
.unwrap()
.push((entry.cache_key, entry.lifetime));
let result = (entry.factory)(self as &dyn IServiceResolver);
self.stack.write().unwrap().pop();
self.resolving.write().unwrap().pop();
if entry.lifetime == ServiceLifetime::Singleton {
self.cache.prefill(entry.cache_key, result.clone());
}
Some(result)
}
}
impl IServiceResolver for ValidationResolver<'_> {
fn get_any(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
let tid = self.type_names.iter().find(|(_, name)| **name == key)?.0;
let entry = self.store.get(tid)?.iter().find(|e| e.key.is_none())?;
if let Some(cached) = self.cache.get(entry.cache_key) {
return Some(cached);
}
self.resolve_via_factory(entry)
}
fn get_keyed_any(&self, key: &str, variant: &str) -> Option<Arc<dyn Any + Send + Sync>> {
let tid = self.type_names.iter().find(|(_, name)| **name == key)?.0;
let entry = self
.store
.get(tid)?
.iter()
.find(|e| e.key.as_deref() == Some(variant))?;
if let Some(cached) = self.cache.get(entry.cache_key) {
return Some(cached);
}
self.resolve_via_factory(entry)
}
fn get_owned_any(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
let tid = self.type_names.iter().find(|(_, name)| **name == key)?.0;
let entry = self.store.get(tid)?.iter().find(|e| e.key.is_none())?;
if entry.lifetime == ServiceLifetime::Singleton {
return None;
}
self.resolve_via_factory(entry)
}
fn get_keyed_owned_any(&self, key: &str, variant: &str) -> Option<Arc<dyn Any + Send + Sync>> {
let tid = self.type_names.iter().find(|(_, name)| **name == key)?.0;
let entry = self
.store
.get(tid)?
.iter()
.find(|e| e.key.as_deref() == Some(variant))?;
if entry.lifetime == ServiceLifetime::Singleton {
return None;
}
self.resolve_via_factory(entry)
}
}