use crate::{
DiError, DiResult, Injectable, InjectionContext, begin_resolution, with_cycle_detection_scope,
};
use std::any::TypeId;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug, Clone, Copy)]
pub struct InjectionMetadata {
pub scope: DependencyScope,
pub cached: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DependencyScope {
Request,
Singleton,
}
#[deprecated(
since = "0.1.0-rc.16",
note = "use `Depends<T>` instead. `Injected<T>` will be removed in a future version."
)]
#[derive(Debug)]
pub struct Injected<T: Injectable> {
inner: Arc<T>,
metadata: InjectionMetadata,
}
#[allow(deprecated)]
impl<T: Injectable> Injected<T> {
pub async fn resolve(ctx: &InjectionContext) -> DiResult<Self> {
Self::resolve_with_cache(ctx, true).await
}
pub async fn resolve_uncached(ctx: &InjectionContext) -> DiResult<Self> {
Self::resolve_with_cache(ctx, false).await
}
async fn resolve_with_cache(ctx: &InjectionContext, use_cache: bool) -> DiResult<Self> {
with_cycle_detection_scope(async {
let inner = if use_cache {
if let Some(cached) = ctx.get_request::<T>() {
cached
} else {
let type_id = TypeId::of::<T>();
let type_name = std::any::type_name::<T>();
let _guard = begin_resolution(type_id, type_name)
.map_err(|e| DiError::CircularDependency(e.to_string()))?;
let v = T::inject(ctx).await?;
let arc = Arc::new(v);
ctx.set_request_arc(Arc::clone(&arc));
arc
}
} else {
let type_id = TypeId::of::<T>();
let type_name = std::any::type_name::<T>();
let _guard = begin_resolution(type_id, type_name)
.map_err(|e| DiError::CircularDependency(e.to_string()))?;
Arc::new(T::inject_uncached(ctx).await?)
};
Ok(Self {
inner,
metadata: InjectionMetadata {
scope: DependencyScope::Request,
cached: use_cache,
},
})
})
.await
}
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,
}),
}
}
}
#[allow(deprecated)]
impl<T: Injectable + Clone> Injected<T> {
pub fn into_inner(self) -> T {
Arc::try_unwrap(self.inner).unwrap_or_else(|arc| (*arc).clone())
}
}
#[allow(deprecated)]
impl<T: Injectable> Deref for Injected<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[allow(deprecated)]
impl<T: Injectable> Clone for Injected<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
metadata: self.metadata,
}
}
}
#[allow(deprecated)]
impl<T: Injectable> AsRef<T> for Injected<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
#[deprecated(
since = "0.1.0-rc.16",
note = "use `Option<Depends<T>>` instead. `OptionalInjected<T>` will be removed in a future version."
)]
#[allow(deprecated)]
pub type OptionalInjected<T> = Option<Injected<T>>;
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
use crate::SingletonScope;
#[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(),
})
}
}
#[tokio::test]
async fn test_injected_from_value() {
let config = TestConfig {
value: "custom".to_string(),
};
let injected = Injected::from_value(config);
assert_eq!(injected.value, "custom");
}
#[tokio::test]
async fn test_injected_into_inner() {
let config = TestConfig {
value: "test".to_string(),
};
let injected = Injected::from_value(config);
let extracted = injected.into_inner();
assert_eq!(extracted.value, "test");
}
#[tokio::test]
async fn test_injected_clone() {
let config = TestConfig {
value: "test".to_string(),
};
let injected1 = Injected::from_value(config);
let injected2 = injected1.clone();
assert_eq!(injected1.value, "test");
assert_eq!(injected2.value, "test");
}
#[tokio::test]
async fn test_injected_deref() {
let config = TestConfig {
value: "test".to_string(),
};
let injected = Injected::from_value(config);
assert_eq!(injected.value, "test");
}
#[tokio::test]
async fn test_injected_metadata() {
let config = TestConfig {
value: "test".to_string(),
};
let injected = Injected::from_value(config);
let metadata = injected.metadata();
assert_eq!(metadata.scope, DependencyScope::Request);
assert!(!metadata.cached);
}
#[tokio::test]
async fn test_optional_injected_some() {
let config = TestConfig {
value: "test".to_string(),
};
let optional: OptionalInjected<TestConfig> = Some(Injected::from_value(config));
assert!(optional.is_some());
if let Some(injected) = optional {
assert_eq!(injected.value, "test");
}
}
#[tokio::test]
async fn test_optional_injected_none() {
let optional: OptionalInjected<TestConfig> = None;
assert!(optional.is_none());
}
#[test]
fn test_dependency_scope_equality() {
assert_eq!(DependencyScope::Request, DependencyScope::Request);
assert_eq!(DependencyScope::Singleton, DependencyScope::Singleton);
assert_ne!(DependencyScope::Request, DependencyScope::Singleton);
}
#[test]
fn test_dependency_scope_debug() {
let request = DependencyScope::Request;
let singleton = DependencyScope::Singleton;
let request_debug = format!("{:?}", request);
let singleton_debug = format!("{:?}", singleton);
assert!(request_debug.contains("Request"));
assert!(singleton_debug.contains("Singleton"));
}
#[test]
fn test_dependency_scope_clone() {
let request = DependencyScope::Request;
let cloned = request;
assert_eq!(request, cloned);
}
#[test]
fn test_injection_metadata_debug() {
let metadata = InjectionMetadata {
scope: DependencyScope::Request,
cached: true,
};
let debug_str = format!("{:?}", metadata);
assert!(debug_str.contains("InjectionMetadata"));
assert!(debug_str.contains("Request"));
assert!(debug_str.contains("true"));
}
#[test]
fn test_injection_metadata_clone() {
let metadata = InjectionMetadata {
scope: DependencyScope::Singleton,
cached: false,
};
let cloned = metadata;
assert_eq!(cloned.scope, DependencyScope::Singleton);
assert!(!cloned.cached);
}
#[test]
fn test_injection_metadata_copy() {
let metadata = InjectionMetadata {
scope: DependencyScope::Request,
cached: true,
};
fn takes_copy<T: Copy>(_: T) {}
takes_copy(metadata);
assert_eq!(metadata.scope, DependencyScope::Request);
assert!(metadata.cached);
}
#[tokio::test]
async fn test_injected_as_arc() {
let config = TestConfig {
value: "arc_test".to_string(),
};
let injected = Injected::from_value(config);
let arc = injected.as_arc();
assert_eq!(arc.value, "arc_test");
assert_eq!(Arc::strong_count(arc), 1);
}
#[tokio::test]
async fn test_injected_as_ref() {
let config = TestConfig {
value: "ref_test".to_string(),
};
let injected = Injected::from_value(config);
let reference: &TestConfig = injected.as_ref();
assert_eq!(reference.value, "ref_test");
}
#[tokio::test]
async fn test_injected_debug() {
let config = TestConfig {
value: "debug_test".to_string(),
};
let injected = Injected::from_value(config);
let debug_str = format!("{:?}", injected);
assert!(debug_str.contains("Injected"));
}
#[tokio::test]
async fn test_injected_resolve_with_context() {
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let config = Injected::<TestConfig>::resolve(&ctx).await.unwrap();
assert_eq!(config.value, "test");
assert!(config.metadata().cached);
assert_eq!(config.metadata().scope, DependencyScope::Request);
}
#[tokio::test]
async fn test_injected_resolve_uncached_with_context() {
let singleton_scope = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton_scope).build();
let config = Injected::<TestConfig>::resolve_uncached(&ctx)
.await
.unwrap();
assert_eq!(config.value, "test");
assert!(!config.metadata().cached);
}
#[tokio::test]
async fn test_injected_clone_shares_arc() {
let config = TestConfig {
value: "shared".to_string(),
};
let injected1 = Injected::from_value(config);
let injected2 = injected1.clone();
assert_eq!(Arc::strong_count(injected1.as_arc()), 2);
assert_eq!(Arc::strong_count(injected2.as_arc()), 2);
assert!(Arc::ptr_eq(injected1.as_arc(), injected2.as_arc()));
}
#[tokio::test]
async fn test_injected_metadata_preserved_on_clone() {
let config = TestConfig {
value: "metadata".to_string(),
};
let injected1 = Injected::from_value(config);
let injected2 = injected1.clone();
assert_eq!(injected1.metadata().scope, injected2.metadata().scope);
assert_eq!(injected1.metadata().cached, injected2.metadata().cached);
}
#[tokio::test]
async fn test_injected_into_inner_with_single_reference() {
let config = TestConfig {
value: "single".to_string(),
};
let injected = Injected::from_value(config);
let inner = injected.into_inner();
assert_eq!(inner.value, "single");
}
#[tokio::test]
async fn test_injected_into_inner_with_multiple_references() {
let config = TestConfig {
value: "multiple".to_string(),
};
let injected1 = Injected::from_value(config);
let _injected2 = injected1.clone();
let inner = injected1.into_inner();
assert_eq!(inner.value, "multiple");
}
#[tokio::test]
async fn test_injected_try_unwrap_success() {
let config = TestConfig {
value: "owned".to_string(),
};
let injected = Injected::from_value(config);
let result = injected.try_unwrap();
assert!(result.is_ok());
assert_eq!(result.unwrap().value, "owned");
}
#[tokio::test]
async fn test_injected_try_unwrap_err_multiple_refs() {
let config = TestConfig {
value: "shared".to_string(),
};
let injected = Injected::from_value(config);
let _clone = injected.clone();
let result = injected.try_unwrap();
let returned = result.unwrap_err();
assert_eq!(returned.value, "shared");
assert_eq!(returned.metadata().scope, DependencyScope::Request);
}
}