use std::sync::Arc;
use injectable_rs_runtime::{
DynProvider, EmptySingletonStore, Injectable, InjectableError, InjectableResult, Provider,
ProviderRegistry, ResolveContext, SingletonStore,
};
#[derive(Debug, Clone)]
pub struct Container {
ctx: ResolveContext,
}
impl Container {
pub fn builder() -> ContainerBuilder {
ContainerBuilder::new()
}
pub async fn resolve<T: Injectable>(&self) -> InjectableResult<T> {
T::Provider::provide(&self.ctx).await
}
pub async fn resolve_external<T: Send + Sync + 'static>(&self) -> InjectableResult<T> {
self.ctx.resolve_external::<T>().await
}
pub async fn resolve_external_with_token<T: Send + Sync + 'static>(
&self,
token: &str,
) -> InjectableResult<T> {
self.ctx.resolve_external_with_token::<T>(token).await
}
pub async fn try_resolve_external_with_token<T: Send + Sync + 'static>(
&self,
token: &str,
) -> InjectableResult<Option<T>> {
match self.resolve_external_with_token::<T>(token).await {
Ok(v) => Ok(Some(v)),
Err(InjectableError::MissingDependency { .. }) => Ok(None),
Err(e) => Err(e),
}
}
pub fn context(&self) -> &ResolveContext {
&self.ctx
}
pub fn registered_types(&self) -> Vec<&'static str> {
injectable_rs_runtime::inventory::iter::<injectable_rs_runtime::InjectableArcFactory>()
.map(|f| f.type_name)
.collect()
}
pub async fn try_resolve<T: Injectable>(&self) -> InjectableResult<Option<T>> {
match self.resolve::<T>().await {
Ok(v) => Ok(Some(v)),
Err(InjectableError::MissingDependency { .. }) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn try_resolve_external<T: Send + Sync + 'static>(
&self,
) -> InjectableResult<Option<T>> {
match self.resolve_external::<T>().await {
Ok(v) => Ok(Some(v)),
Err(InjectableError::MissingDependency { .. }) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn shutdown(&self) -> InjectableResult<()> {
match self.ctx.run_destructors().await {
Ok(()) => Ok(()),
Err(errors) => Err(InjectableError::ShutdownFailed { errors }),
}
}
pub async fn destructor_count(&self) -> usize {
self.ctx.destructor_count().await
}
}
pub struct ContainerBuilder {
store: Option<Arc<dyn SingletonStore>>,
registry: ProviderRegistry,
}
impl std::fmt::Debug for ContainerBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContainerBuilder")
.field(
"store",
&self.store.as_ref().map(|_| "Arc<dyn SingletonStore>"),
)
.field("registry", &self.registry)
.finish()
}
}
impl ContainerBuilder {
pub fn new() -> Self {
Self {
store: None,
registry: ProviderRegistry::new(),
}
}
pub fn with_store(mut self, store: Arc<dyn SingletonStore>) -> Self {
self.store = Some(store);
self
}
pub fn register<T: Send + Sync + 'static>(
mut self,
token: impl Into<String>,
provider: DynProvider<T>,
) -> Self {
self.registry.register(token, provider);
self
}
pub fn register_or_replace<T: Send + Sync + 'static>(
mut self,
token: impl Into<String>,
provider: DynProvider<T>,
) -> Self {
self.registry.register_or_replace(token, provider);
self
}
pub async fn build(self) -> InjectableResult<Container> {
let store = self.store.unwrap_or_else(|| Arc::new(EmptySingletonStore));
if let Err(e) = store.validate() {
return Err(InjectableError::ConstructionFailed {
type_name: "Container",
reason: format!("singleton store validation failed: {e}"),
});
}
let nodes: Vec<injectable_rs_graph::GraphNode> =
inventory::iter::<injectable_rs_graph::GraphNode>()
.cloned()
.collect();
if !nodes.is_empty() {
let graph = injectable_rs_graph::DependencyGraph::new(nodes);
if let Err(errors) = graph.validate() {
return Err(InjectableError::GraphValidationFailed {
errors: errors.iter().map(|e| e.to_string()).collect(),
});
}
}
let dups = self.registry.duplicates();
if !dups.is_empty() {
let errors: Vec<String> = dups
.iter()
.map(|t| format!("DynProvider registered more than once for type `{t}`"))
.collect();
return Err(InjectableError::GraphValidationFailed { errors });
}
let ctx = ResolveContext::new(store, Arc::new(self.registry));
Ok(Container { ctx })
}
}
impl Default for ContainerBuilder {
fn default() -> Self {
Self::new()
}
}