use crate::{DiResult, Injectable, InjectionContext};
use async_trait::async_trait;
use dashmap::DashMap;
use std::any::{Any, TypeId};
use std::future::Future;
use std::marker::PhantomData;
use std::sync::{Arc, OnceLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DependencyScope {
#[default]
Singleton,
Request,
Transient,
}
impl DependencyScope {
pub fn outlives(&self, dependent: DependencyScope) -> bool {
!matches!(
(dependent, *self),
(DependencyScope::Singleton, DependencyScope::Request)
)
}
}
#[async_trait]
pub trait FactoryTrait: Send + Sync {
async fn create(&self, ctx: &InjectionContext) -> DiResult<Arc<dyn Any + Send + Sync>>;
}
pub struct AsyncFactory<F, Fut, T>
where
F: Fn(Arc<InjectionContext>) -> Fut + Send + Sync,
Fut: Future<Output = DiResult<T>> + Send,
T: Any + Send + Sync + 'static,
{
factory: F,
_phantom: std::marker::PhantomData<fn() -> (Fut, T)>,
}
impl<F, Fut, T> AsyncFactory<F, Fut, T>
where
F: Fn(Arc<InjectionContext>) -> Fut + Send + Sync,
Fut: Future<Output = DiResult<T>> + Send,
T: Any + Send + Sync + 'static,
{
pub fn new(factory: F) -> Self {
Self {
factory,
_phantom: std::marker::PhantomData,
}
}
}
#[async_trait]
impl<F, Fut, T> FactoryTrait for AsyncFactory<F, Fut, T>
where
F: Fn(Arc<InjectionContext>) -> Fut + Send + Sync,
Fut: Future<Output = DiResult<T>> + Send + 'static,
T: Any + Send + Sync + 'static,
{
async fn create(&self, ctx: &InjectionContext) -> DiResult<Arc<dyn Any + Send + Sync>> {
let ctx_arc = Arc::new(ctx.clone());
let instance = (self.factory)(ctx_arc).await?;
Ok(Arc::new(instance))
}
}
pub struct InjectableFactory<T>(PhantomData<T>);
impl<T> Default for InjectableFactory<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T> InjectableFactory<T> {
pub fn new() -> Self {
Self::default()
}
}
#[async_trait]
impl<T: Injectable + Any + Send + Sync + 'static> FactoryTrait for InjectableFactory<T> {
async fn create(&self, ctx: &InjectionContext) -> DiResult<Arc<dyn Any + Send + Sync>> {
let ctx_arc = Arc::new(ctx.clone());
let resolve_ctx = crate::resolve_context::ResolveContext {
root: crate::resolve_context::RESOLVE_CTX
.try_with(|outer| Arc::clone(&outer.root))
.unwrap_or_else(|_| Arc::clone(&ctx_arc)),
current: Arc::clone(&ctx_arc),
};
let value = crate::resolve_context::RESOLVE_CTX
.scope(resolve_ctx, T::inject(ctx))
.await?;
Ok(Arc::new(value))
}
}
type BoxedFactory = Box<dyn FactoryTrait>;
pub struct DependencyRegistry {
factories: DashMap<TypeId, BoxedFactory>,
scopes: DashMap<TypeId, DependencyScope>,
dependencies: DashMap<TypeId, Vec<TypeId>>,
type_names: DashMap<TypeId, &'static str>,
qualified_type_names: DashMap<TypeId, &'static str>,
}
impl DependencyRegistry {
pub fn new() -> Self {
Self {
factories: DashMap::new(),
scopes: DashMap::new(),
dependencies: DashMap::new(),
type_names: DashMap::new(),
qualified_type_names: DashMap::new(),
}
}
pub fn register<T: Any + Send + Sync + 'static>(
&self,
scope: DependencyScope,
factory: impl FactoryTrait + 'static,
) {
let type_id = TypeId::of::<T>();
let type_name = std::any::type_name::<T>();
if self.factories.contains_key(&type_id) {
let short = type_name.rsplit("::").next().unwrap_or(type_name);
panic!(
"Duplicate DependencyRegistry registration for type `{type_name}`.\n\
\n\
Hint: reinhardt DI uses TypeId as the sole registry key. Two factories\n\
returning the same type will conflict regardless of function name or scope.\n\
Use a distinct newtype (e.g., `struct Primary{short}({short})`) for each."
);
}
self.factories.insert(type_id, Box::new(factory));
self.scopes.insert(type_id, scope);
}
pub fn register_async<T, F, Fut>(&self, scope: DependencyScope, factory: F)
where
T: Any + Send + Sync + 'static,
F: Fn(Arc<InjectionContext>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = DiResult<T>> + Send + 'static,
{
self.register::<T>(scope, AsyncFactory::new(factory));
}
pub fn get_scope<T: Any + 'static>(&self) -> Option<DependencyScope> {
let type_id = TypeId::of::<T>();
self.scopes.get(&type_id).map(|entry| *entry.value())
}
pub fn is_registered<T: Any + 'static>(&self) -> bool {
let type_id = TypeId::of::<T>();
self.factories.contains_key(&type_id)
}
pub fn len(&self) -> usize {
self.factories.len()
}
pub fn is_empty(&self) -> bool {
self.factories.is_empty()
}
pub async fn create<T: Any + Send + Sync + 'static>(
&self,
ctx: &InjectionContext,
) -> DiResult<Arc<T>> {
let type_id = TypeId::of::<T>();
let factory = self.factories.get(&type_id).ok_or_else(|| {
crate::DiError::DependencyNotRegistered {
type_name: std::any::type_name::<T>().to_string(),
}
})?;
let any_arc = factory.create(ctx).await?;
any_arc
.downcast::<T>()
.map_err(|_| crate::DiError::Internal {
message: format!(
"Failed to downcast dependency: expected {}, got different type",
std::any::type_name::<T>()
),
})
}
pub fn get_dependencies(&self, type_id: TypeId) -> Vec<TypeId> {
self.dependencies
.get(&type_id)
.map(|deps| deps.value().clone())
.unwrap_or_default()
}
pub fn get_all_dependencies(&self) -> std::collections::HashMap<TypeId, Vec<TypeId>> {
self.dependencies
.iter()
.map(|entry| (*entry.key(), entry.value().clone()))
.collect()
}
pub fn get_type_names(&self) -> std::collections::HashMap<TypeId, &'static str> {
self.type_names
.iter()
.map(|entry| (*entry.key(), *entry.value()))
.collect()
}
#[doc(hidden)]
pub fn register_dependencies(&self, type_id: TypeId, deps: impl AsRef<[TypeId]>) {
self.dependencies.insert(type_id, deps.as_ref().to_vec());
}
#[doc(hidden)]
pub fn register_type_name(&self, type_id: TypeId, type_name: &'static str) {
self.type_names.insert(type_id, type_name);
}
pub(crate) fn is_registered_by_id(&self, type_id: TypeId) -> bool {
self.factories.contains_key(&type_id)
}
pub(crate) fn get_scope_by_id(&self, type_id: TypeId) -> Option<DependencyScope> {
self.scopes.get(&type_id).map(|entry| *entry.value())
}
pub(crate) fn get_type_name(&self, type_id: TypeId) -> Option<&'static str> {
self.type_names.get(&type_id).map(|entry| *entry.value())
}
#[doc(hidden)]
pub fn register_qualified_type_name(&self, type_id: TypeId, qualified_name: &'static str) {
self.qualified_type_names.insert(type_id, qualified_name);
}
pub fn get_qualified_type_name(&self, type_id: &TypeId) -> Option<&'static str> {
self.qualified_type_names.get(type_id).map(|r| *r.value())
}
pub fn iter_qualified_type_names(&self) -> impl Iterator<Item = (TypeId, &'static str)> + '_ {
self.qualified_type_names
.iter()
.map(|entry| (*entry.key(), *entry.value()))
}
}
#[cfg(feature = "testing")]
impl DependencyRegistry {
pub fn register_override<T, F, Fut>(
self: &std::sync::Arc<Self>,
scope: crate::registry::DependencyScope,
factory: F,
) -> crate::testing::OverrideGuard
where
T: std::any::Any + Send + Sync + 'static,
F: Fn(std::sync::Arc<crate::InjectionContext>) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = crate::DiResult<T>> + Send + 'static,
{
let type_id = std::any::TypeId::of::<T>();
let async_factory = crate::registry::AsyncFactory::new(factory);
let boxed: Box<dyn crate::registry::FactoryTrait> = Box::new(async_factory);
let previous_factory = self.factories.insert(type_id, boxed);
let previous_scope = self.scopes.insert(type_id, scope);
debug_assert!(
previous_factory.is_some() == previous_scope.is_some(),
"torn override state: factories/scopes diverged for `{}`",
std::any::type_name::<T>()
);
let previous = match (previous_factory, previous_scope) {
(Some(f), Some(s)) => Some((f, s)),
_ => None,
};
crate::testing::OverrideGuard {
type_id,
previous,
registry: std::sync::Arc::downgrade(self),
}
}
pub(crate) fn restore_override(
&self,
type_id: std::any::TypeId,
factory: Box<dyn crate::registry::FactoryTrait>,
scope: crate::registry::DependencyScope,
) {
self.factories.insert(type_id, factory);
self.scopes.insert(type_id, scope);
}
pub(crate) fn remove_override(&self, type_id: std::any::TypeId) {
self.factories.remove(&type_id);
self.scopes.remove(&type_id);
}
}
impl Default for DependencyRegistry {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_REGISTRY: OnceLock<Arc<DependencyRegistry>> = OnceLock::new();
pub fn global_registry() -> &'static Arc<DependencyRegistry> {
GLOBAL_REGISTRY.get_or_init(|| {
let registry = Arc::new(DependencyRegistry::new());
initialize_registry(®istry);
registry
})
}
#[cfg(test)]
pub fn reset_global_registry() {
unsafe {
let ptr = std::ptr::addr_of!(GLOBAL_REGISTRY) as *mut OnceLock<Arc<DependencyRegistry>>;
std::ptr::write(ptr, OnceLock::new());
}
}
pub struct DependencyRegistration {
pub type_id: TypeId,
pub type_name: &'static str,
pub scope: DependencyScope,
pub dependencies: &'static [TypeId],
pub register_fn: fn(&DependencyRegistry),
}
impl DependencyRegistration {
pub const fn new<T: Send + Sync + 'static>(
type_name: &'static str,
scope: DependencyScope,
register_fn: fn(&DependencyRegistry),
) -> Self {
Self {
type_id: TypeId::of::<T>(),
type_name,
scope,
dependencies: &[],
register_fn,
}
}
pub const fn new_with_deps<T: Send + Sync + 'static>(
type_name: &'static str,
scope: DependencyScope,
dependencies: &'static [TypeId],
register_fn: fn(&DependencyRegistry),
) -> Self {
Self {
type_id: TypeId::of::<T>(),
type_name,
scope,
dependencies,
register_fn,
}
}
}
inventory::collect!(DependencyRegistration);
pub struct InjectableRegistration {
pub register_fn: fn(&DependencyRegistry),
}
impl InjectableRegistration {
pub const fn new(register_fn: fn(&DependencyRegistry)) -> Self {
Self { register_fn }
}
}
inventory::collect!(InjectableRegistration);
fn initialize_registry(registry: &DependencyRegistry) {
for registration in inventory::iter::<DependencyRegistration> {
(registration.register_fn)(registry);
}
for registration in inventory::iter::<InjectableRegistration> {
(registration.register_fn)(registry);
}
}
#[macro_export]
macro_rules! submit_registration {
($registration:expr) => {
$crate::inventory::submit! {
$registration
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scope::SingletonScope;
use rstest::*;
#[derive(Clone)]
struct TestService {
value: i32,
}
#[rstest]
#[tokio::test]
async fn test_registry_basic() {
let registry = DependencyRegistry::new();
registry.register_async::<TestService, _, _>(DependencyScope::Singleton, |_ctx| async {
Ok(TestService { value: 42 })
});
assert!(registry.is_registered::<TestService>());
assert_eq!(
registry.get_scope::<TestService>(),
Some(DependencyScope::Singleton)
);
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let service = registry.create::<TestService>(&ctx).await.unwrap();
assert_eq!(service.value, 42);
}
#[rstest]
#[tokio::test]
async fn test_registry_not_registered() {
let registry = DependencyRegistry::new();
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let result = registry.create::<TestService>(&ctx).await;
assert!(result.is_err());
}
#[rstest]
fn test_duplicate_registration_panics() {
let registry = DependencyRegistry::new();
registry.register_async::<TestService, _, _>(DependencyScope::Singleton, |_ctx| async {
Ok(TestService { value: 1 })
});
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
registry.register_async::<TestService, _, _>(DependencyScope::Request, |_ctx| async {
Ok(TestService { value: 2 })
});
}));
let err = result.expect_err("expected panic on duplicate registration");
let msg = err
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| err.downcast_ref::<&str>().copied())
.expect("panic payload should be a string");
assert!(
msg.contains("Duplicate DependencyRegistry registration"),
"missing duplicate prefix: {msg}"
);
assert!(
msg.contains("TestService"),
"missing type name in panic message: {msg}"
);
assert!(
msg.contains("newtype"),
"missing newtype hint in panic message: {msg}"
);
}
#[rstest]
fn test_is_registered_guard_allows_skip() {
let registry = DependencyRegistry::new();
registry.register_async::<TestService, _, _>(DependencyScope::Singleton, |_ctx| async {
Ok(TestService { value: 1 })
});
if !registry.is_registered::<TestService>() {
registry.register_async::<TestService, _, _>(DependencyScope::Request, |_ctx| async {
Ok(TestService { value: 2 })
});
}
assert!(registry.is_registered::<TestService>());
}
}