injectable_rs_runtime/extract.rs
1//! The `Extract` trait — Axum-inspired dependency extraction.
2//!
3//! Instead of calling `resolver.resolve::<T>()`, generated provider code calls
4//! `T::extract(ctx).await?`, which is fully typed and has no runtime type
5//! lookup.
6
7use crate::{InjectableError, InjectableResult, ResolveContext};
8
9/// Axum-inspired extractor trait for dependency resolution.
10///
11/// Types that implement `Extract` can be pulled from a [`ResolveContext`]
12/// without any `TypeId` or dynamic downcasting. The generated provider code
13/// calls `<Inject<Database> as Extract>::extract(ctx).await?` for each
14/// constructor parameter.
15///
16/// # Implementations
17///
18/// This trait is automatically implemented for:
19/// - [`Inject<T>`](crate::Inject) when `T: Injectable` — shared (`Arc`) access
20/// - `T` when `T: Injectable` — owned value access
21/// - `Option<T>` when `T: Extract` — optional dependency
22///
23/// Users never need to implement `Extract` manually.
24#[async_trait::async_trait]
25pub trait Extract: Sized {
26 /// Extract a value from the given resolution context.
27 ///
28 /// This is the core resolution method. It is called by generated provider
29 /// code for each constructor parameter or struct field marked with an
30 /// extractor type.
31 async fn extract(ctx: &ResolveContext) -> InjectableResult<Self>;
32}
33
34/// `Extract` for the unit type — always succeeds with `()`.
35///
36/// Useful as a factory input when no context value is needed.
37#[async_trait::async_trait]
38impl Extract for () {
39 async fn extract(_ctx: &ResolveContext) -> InjectableResult<Self> {
40 Ok(())
41 }
42}
43
44/// `Extract` for tuples up to 16 elements, each implementing `Extract`.
45///
46/// Extracting a tuple extracts every element independently from the same
47/// context. Combined with `Arc<T>: Into<Inject<T>>` this lets factories
48/// receive pre-extracted values — for example:
49///
50/// ```rust,ignore
51/// // field svc: Arc<WeatherService>
52/// #[injectable(inject(use_factory_sync = Clone::clone))]
53/// svc: Arc<WeatherService>,
54/// ```
55///
56/// The macro extracts `Arc<WeatherService>` from the context and passes it
57/// directly to `Clone::clone`, which returns another `Arc<WeatherService>`.
58macro_rules! impl_extract_tuple {
59 ($($T:ident),+) => {
60 #[async_trait::async_trait]
61 impl<$($T: Extract + Send + Sync + 'static),+> Extract for ($($T,)+) {
62 async fn extract(ctx: &ResolveContext) -> InjectableResult<Self> {
63 Ok(($($T::extract(ctx).await?,)+))
64 }
65 }
66 };
67}
68
69impl_extract_tuple!(T1);
70impl_extract_tuple!(T1, T2);
71impl_extract_tuple!(T1, T2, T3);
72impl_extract_tuple!(T1, T2, T3, T4);
73impl_extract_tuple!(T1, T2, T3, T4, T5);
74impl_extract_tuple!(T1, T2, T3, T4, T5, T6);
75impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7);
76impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
77impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
78impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
79impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
80impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
81impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
82impl_extract_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
83impl_extract_tuple!(
84 T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15
85);
86impl_extract_tuple!(
87 T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
88);
89
90/// Blanket `Extract` implementation for `Option<T>` where `T: Extract`.
91///
92/// If the inner extraction fails with `MissingDependency`, this returns
93/// `None` instead of propagating the error. Other errors are propagated.
94///
95/// Note: This impl takes priority over the `T: Injectable` blanket impl
96/// for `Option<T>` because `Option<T>` does not implement `Injectable`.
97#[async_trait::async_trait]
98impl<T: Extract + Send + Sync + 'static> Extract for Option<T> {
99 async fn extract(ctx: &ResolveContext) -> InjectableResult<Self> {
100 match T::extract(ctx).await {
101 Ok(value) => Ok(Some(value)),
102 Err(InjectableError::MissingDependency { .. }) => Ok(None),
103 Err(e) => Err(e),
104 }
105 }
106}