use crate::injected::DependencyScope;
use crate::{
DiError, DiResult, Injectable, context::InjectionContext, injected::InjectionMetadata,
};
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug)]
pub struct Depends<T: Send + Sync + 'static> {
inner: Arc<T>,
metadata: InjectionMetadata,
}
impl<T: Send + Sync + 'static> Depends<T> {
pub fn builder() -> DependsBuilder<T> {
DependsBuilder {
use_cache: true,
_phantom: std::marker::PhantomData,
}
}
pub fn builder_no_cache() -> DependsBuilder<T> {
DependsBuilder {
use_cache: false,
_phantom: std::marker::PhantomData,
}
}
pub async fn resolve(ctx: &InjectionContext, use_cache: bool) -> DiResult<Self>
where
T: Injectable,
{
let arc = match ctx.resolve::<T>().await {
Ok(arc) => arc,
Err(DiError::DependencyNotRegistered { .. }) => Arc::new(T::inject(ctx).await?),
Err(e) => return Err(e),
};
Ok(Self {
inner: arc,
metadata: InjectionMetadata {
scope: DependencyScope::Request,
cached: use_cache,
},
})
}
pub async fn resolve_from_registry(ctx: &InjectionContext, use_cache: bool) -> DiResult<Self> {
let arc = ctx.resolve::<T>().await?;
Ok(Self {
inner: arc,
metadata: InjectionMetadata {
scope: DependencyScope::Request,
cached: use_cache,
},
})
}
pub fn from_value(value: T) -> Self {
Self {
inner: Arc::new(value),
metadata: InjectionMetadata {
scope: DependencyScope::Request,
cached: false,
},
}
}
pub fn as_arc(&self) -> &Arc<T> {
&self.inner
}
pub fn metadata(&self) -> &InjectionMetadata {
&self.metadata
}
pub fn try_unwrap(self) -> Result<T, Self> {
match Arc::try_unwrap(self.inner) {
Ok(val) => Ok(val),
Err(arc) => Err(Self {
inner: arc,
metadata: self.metadata,
}),
}
}
}
impl<T: Clone + Send + Sync + 'static> Depends<T> {
pub fn into_inner(self) -> T {
Arc::try_unwrap(self.inner).unwrap_or_else(|arc| (*arc).clone())
}
}
pub type DependsResult<T, E> = Depends<Result<T, E>>;
pub type DependsOption<T> = Depends<Option<T>>;
pub struct DependsBuilder<T: Send + Sync + 'static> {
use_cache: bool,
_phantom: std::marker::PhantomData<T>,
}
impl<T: Send + Sync + 'static> DependsBuilder<T> {
pub async fn resolve(self, ctx: &InjectionContext) -> DiResult<Depends<T>>
where
T: Injectable,
{
Depends::resolve(ctx, self.use_cache).await
}
}
impl<T: Send + Sync + 'static> Deref for Depends<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Send + Sync + 'static> Clone for Depends<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
metadata: self.metadata,
}
}
}
impl<T: Send + Sync + 'static> AsRef<T> for Depends<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DependencyScope as RegistryScope, SingletonScope, global_registry};
use rstest::rstest;
#[derive(Clone, Default, Debug)]
struct TestConfig {
value: String,
}
#[async_trait::async_trait]
impl Injectable for TestConfig {
async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
Ok(TestConfig {
value: "test".to_string(),
})
}
}
fn register_test_config() {
let registry = global_registry();
if !registry.is_registered::<TestConfig>() {
registry.register_async::<TestConfig, _, _>(RegistryScope::Request, |_ctx| async {
Ok(TestConfig {
value: "test".to_string(),
})
});
}
}
#[rstest]
#[tokio::test]
async fn test_depends_from_value() {
let config = TestConfig {
value: "custom".to_string(),
};
let depends = Depends::from_value(config);
assert_eq!(depends.value, "custom");
}
#[rstest]
#[tokio::test]
async fn test_depends_into_inner() {
let config = TestConfig {
value: "test".to_string(),
};
let depends = Depends::from_value(config);
let extracted = depends.into_inner();
assert_eq!(extracted.value, "test");
}
#[rstest]
#[tokio::test]
async fn test_depends_clone() {
let config = TestConfig {
value: "test".to_string(),
};
let depends1 = Depends::from_value(config);
let depends2 = depends1.clone();
assert_eq!(depends1.value, "test");
assert_eq!(depends2.value, "test");
assert!(Arc::ptr_eq(depends1.as_arc(), depends2.as_arc()));
}
#[rstest]
#[tokio::test]
async fn test_depends_deref() {
let config = TestConfig {
value: "test".to_string(),
};
let depends = Depends::from_value(config);
assert_eq!(depends.value, "test");
}
#[rstest]
#[tokio::test]
async fn test_depends_metadata() {
let config = TestConfig {
value: "test".to_string(),
};
let depends = Depends::from_value(config);
let metadata = depends.metadata();
assert_eq!(metadata.scope, DependencyScope::Request);
assert!(!metadata.cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_as_arc() {
let config = TestConfig {
value: "arc_test".to_string(),
};
let depends = Depends::from_value(config);
let arc = depends.as_arc();
assert_eq!(arc.value, "arc_test");
assert_eq!(Arc::strong_count(arc), 1);
}
#[rstest]
#[tokio::test]
async fn test_depends_as_ref() {
let config = TestConfig {
value: "ref_test".to_string(),
};
let depends = Depends::from_value(config);
let reference: &TestConfig = depends.as_ref();
assert_eq!(reference.value, "ref_test");
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_with_context() {
register_test_config();
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<TestConfig>::resolve(&ctx, true).await.unwrap();
assert_eq!(depends.value, "test");
assert!(depends.metadata().cached);
assert_eq!(depends.metadata().scope, DependencyScope::Request);
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_uncached() {
register_test_config();
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<TestConfig>::resolve(&ctx, false).await.unwrap();
assert_eq!(depends.value, "test");
assert!(!depends.metadata().cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_builder_resolve() {
register_test_config();
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<TestConfig>::builder()
.resolve(&ctx)
.await
.unwrap();
assert_eq!(depends.value, "test");
assert!(depends.metadata().cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_builder_no_cache_resolve() {
register_test_config();
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<TestConfig>::builder_no_cache()
.resolve(&ctx)
.await
.unwrap();
assert_eq!(depends.value, "test");
assert!(!depends.metadata().cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_or_inject_failing_injectable_returns_error() {
#[derive(Debug)]
struct FailingType;
#[async_trait::async_trait]
impl Injectable for FailingType {
async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
Err(crate::DiError::NotRegistered {
type_name: "FailingType".into(),
hint: "intentionally failing".into(),
})
}
}
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let result = Depends::<FailingType>::resolve(&ctx, true).await;
assert!(result.is_err());
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_from_registry_unregistered_type_returns_error() {
#[derive(Debug)]
struct UnregisteredType;
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let result = Depends::<UnregisteredType>::resolve_from_registry(&ctx, true).await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DiError::DependencyNotRegistered { .. }
),);
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_from_registry_factory_type_without_injectable() {
#[derive(Debug, Clone)]
struct FactoryOnlyType {
value: String,
}
let registry = global_registry();
if !registry.is_registered::<FactoryOnlyType>() {
registry.register_async::<FactoryOnlyType, _, _>(
RegistryScope::Request,
|_ctx| async {
Ok(FactoryOnlyType {
value: "from_factory".to_string(),
})
},
);
}
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<FactoryOnlyType>::resolve_from_registry(&ctx, true)
.await
.unwrap();
assert_eq!(depends.value, "from_factory");
}
#[rstest]
#[tokio::test]
async fn test_depends_resolve_manual_injectable_fallback() {
#[derive(Debug, Clone)]
struct ManualType {
origin: String,
}
#[async_trait::async_trait]
impl Injectable for ManualType {
async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
Ok(ManualType {
origin: "from_inject".to_string(),
})
}
}
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<ManualType>::resolve(&ctx, true).await.unwrap();
assert_eq!(depends.origin, "from_inject");
}
#[rstest]
#[tokio::test]
async fn test_depends_metadata_preserved_on_clone() {
let config = TestConfig {
value: "metadata".to_string(),
};
let depends1 = Depends::from_value(config);
let depends2 = depends1.clone();
assert_eq!(depends1.metadata().scope, depends2.metadata().scope);
assert_eq!(depends1.metadata().cached, depends2.metadata().cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_non_clone_type() {
#[derive(Debug)]
struct NonCloneService {
id: u32,
}
let service = NonCloneService { id: 42 };
let depends = Depends::from_value(service);
let cloned_depends = depends.clone();
assert_eq!(depends.id, 42);
assert_eq!(cloned_depends.id, 42);
assert!(Arc::ptr_eq(depends.as_arc(), cloned_depends.as_arc()));
assert_eq!(depends.as_ref().id, 42);
}
#[rstest]
#[tokio::test]
async fn test_depends_non_clone_type_resolve() {
#[derive(Debug)]
struct NonCloneRouter {
prefix: String,
}
#[async_trait::async_trait]
impl Injectable for NonCloneRouter {
async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
Ok(NonCloneRouter {
prefix: "/api".to_string(),
})
}
}
let registry = global_registry();
if !registry.is_registered::<NonCloneRouter>() {
registry.register_async::<NonCloneRouter, _, _>(RegistryScope::Request, |_ctx| async {
Ok(NonCloneRouter {
prefix: "/api".to_string(),
})
});
}
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let depends = Depends::<NonCloneRouter>::resolve(&ctx, true)
.await
.unwrap();
assert_eq!(depends.prefix, "/api");
assert!(depends.metadata().cached);
}
#[rstest]
#[tokio::test]
async fn test_depends_try_unwrap_success() {
let config = TestConfig {
value: "owned".to_string(),
};
let depends = Depends::from_value(config);
let result = depends.try_unwrap();
assert!(result.is_ok());
assert_eq!(result.unwrap().value, "owned");
}
#[rstest]
#[tokio::test]
async fn test_depends_try_unwrap_err_multiple_refs() {
let config = TestConfig {
value: "shared".to_string(),
};
let depends = Depends::from_value(config);
let _clone = depends.clone();
let result = depends.try_unwrap();
let returned = result.unwrap_err();
assert_eq!(returned.value, "shared");
assert_eq!(returned.metadata().scope, DependencyScope::Request);
}
#[rstest]
#[tokio::test]
async fn test_depends_try_unwrap_non_clone_type() {
#[derive(Debug, PartialEq)]
struct NonCloneRouter {
prefix: String,
}
let router = NonCloneRouter {
prefix: "/api".to_string(),
};
let depends = Depends::from_value(router);
let result = depends.try_unwrap();
assert_eq!(
result.unwrap(),
NonCloneRouter {
prefix: "/api".to_string()
}
);
}
#[rstest]
#[tokio::test]
async fn test_depends_result_type_alias_ok_variant() {
let ok_value: Result<String, String> = Ok("success".to_string());
let depends: DependsResult<String, String> = Depends::from_value(ok_value);
assert!(depends.is_ok());
assert_eq!(depends.as_ref().as_ref().unwrap(), "success");
}
#[rstest]
#[tokio::test]
async fn test_depends_result_type_alias_err_variant() {
let err_value: Result<String, String> = Err("failure".to_string());
let depends: DependsResult<String, String> = Depends::from_value(err_value);
assert!(depends.is_err());
assert_eq!(depends.as_ref().as_ref().unwrap_err(), "failure");
}
#[rstest]
#[tokio::test]
async fn test_depends_option_type_alias_some_variant() {
let some_value: Option<String> = Some("present".to_string());
let depends: DependsOption<String> = Depends::from_value(some_value);
assert!(depends.is_some());
assert_eq!(depends.as_ref().as_ref().unwrap(), "present");
}
#[rstest]
#[tokio::test]
async fn test_depends_option_type_alias_none_variant() {
let none_value: Option<String> = None;
let depends: DependsOption<String> = Depends::from_value(none_value);
assert!(depends.is_none());
}
}