Skip to main content

injectable_rs_runtime/
provider.rs

1//! The `Provider` trait — async construction of injectable values.
2//!
3//! Each `Injectable` type has an associated `Provider` that implements
4//! this trait. The provider encodes the full dependency tree at compile
5//! time through recursive `Extract` calls.
6
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use crate::{FactoryCtx, InjectableResult, ResolveContext};
12
13/// A provider that can asynchronously construct a value of type `T`.
14///
15/// # Generated Implementation
16///
17/// The `#[derive(Injectable)]` macro generates a provider struct and
18/// implements this trait. The generated `provide` method:
19///
20/// 1. Extracts each constructor parameter via `Extract::extract(ctx)`
21/// 2. Calls the constructor with the extracted values
22/// 3. Invokes `post_construct` hooks if present
23/// 4. Returns the fully constructed value
24///
25/// # No Runtime Lookup
26///
27/// All dependency resolution happens through static dispatch. There is
28/// no `HashMap<TypeId, Box<dyn Any>>`, no downcasting, no reflection.
29///
30/// # Example (Generated Code)
31///
32/// ```rust,ignore
33/// pub struct UserServiceProvider;
34///
35/// #[async_trait]
36/// impl Provider<UserService> for UserServiceProvider {
37///     async fn provide(ctx: &ResolveContext) -> InjectableResult<UserService> {
38///         let db = Inject::<Database>::extract(ctx).await?;
39///         let cache = Inject::<Cache>::extract(ctx).await?;
40///         let instance = UserService::new(db, cache).await;
41///         instance.post_construct().await;
42///         Ok(instance)
43///     }
44/// }
45/// ```
46#[async_trait::async_trait]
47pub trait Provider<T>: Send + Sync + 'static {
48    /// Asynchronously provide a value of type `T`.
49    ///
50    /// This method extracts all dependencies from the context,
51    /// constructs the value, and runs lifecycle hooks.
52    async fn provide(ctx: &ResolveContext) -> InjectableResult<T>;
53}
54
55/// Type-erased async closure signature for dynamic providers.
56///
57/// The closure receives an `Arc<ResolveContext>` internally (not `FactoryCtx`);
58/// `FactoryCtx` is created at the call site and wraps this `Arc` before
59/// handing it to the user-facing `with_ctx` closure.
60type DynProviderFn<T> = Box<
61    dyn Fn(Arc<ResolveContext>) -> Pin<Box<dyn Future<Output = InjectableResult<T>> + Send>>
62        + Send
63        + Sync,
64>;
65
66/// A dynamic, closure-based provider for types that cannot derive `Injectable`.
67///
68/// This is the key building block for injecting **external types** — types
69/// from third-party crates that you don't control and therefore can't add
70/// `#[derive(Injectable)]` to.
71///
72/// # When to Use
73///
74/// Use `DynProvider` when you need to inject a type you don't own:
75///
76/// - `reqwest::Client`
77/// - `sqlx::SqlitePool`
78/// - `redis::Client`
79/// - Any type from a dependency
80///
81/// # How It Works
82///
83/// Instead of a compile-time generated provider, `DynProvider` wraps an
84/// async closure that constructs the value. The closure receives an
85/// `Arc<ResolveContext>` so it can itself resolve dependencies.
86///
87/// # Registration
88///
89/// `DynProvider` instances are registered via `ContainerBuilder::register()`
90/// in the public `injectable` crate:
91///
92/// ```rust,ignore
93/// let container = Container::builder()
94///     .register("", DynProvider::new(async {
95///         Ok(reqwest::Client::new())
96///     }))
97///     .build()
98///     .await?;
99/// ```
100///
101/// Or with context access for dependent construction:
102///
103/// ```rust,ignore
104/// let container = Container::builder()
105///     .register("", DynProvider::with_ctx(|ctx| async move {
106///         let config = ctx.resolve::<Config>().await?;
107///         Ok(Database::connect(&config.connection_string).await?)
108///     }))
109///     .build()
110///     .await?;
111/// ```
112pub struct DynProvider<T> {
113    f: DynProviderFn<T>,
114}
115
116impl<T: Send + Sync + 'static> DynProvider<T> {
117    /// Create a `DynProvider` from a closure that returns a future.
118    ///
119    /// Use this for types that can be constructed without resolving
120    /// other dependencies from the container. The closure is called
121    /// each time the provider is invoked, producing a fresh future.
122    ///
123    /// # Example
124    ///
125    /// ```rust,ignore
126    /// DynProvider::new(|| async { Ok(reqwest::Client::new()) })
127    /// ```
128    pub fn new<F, Fut>(f: F) -> Self
129    where
130        F: Fn() -> Fut + Send + Sync + 'static,
131        Fut: Future<Output = InjectableResult<T>> + Send + 'static,
132    {
133        Self {
134            f: Box::new(move |_ctx| {
135                let fut = f();
136                Box::pin(fut)
137            }),
138        }
139    }
140
141    /// Create a `DynProvider` from a sync closure returning `InjectableResult<T>`.
142    ///
143    /// Use this for synchronous construction of external types.
144    /// This is the most ergonomic option for simple cases.
145    ///
146    /// # Example
147    ///
148    /// ```rust,ignore
149    /// DynProvider::sync(|| Ok(HttpClient::new(5000)))
150    /// ```
151    pub fn sync<F>(f: F) -> Self
152    where
153        F: Fn() -> InjectableResult<T> + Send + Sync + 'static,
154    {
155        Self {
156            f: Box::new(move |_ctx| {
157                let result = f();
158                Box::pin(std::future::ready(result))
159            }),
160        }
161    }
162
163    /// Create a `DynProvider` from a closure that receives a [`FactoryCtx`].
164    ///
165    /// Use this for types that need to resolve other dependencies during
166    /// construction.  `FactoryCtx` exposes only scope-safe operations
167    /// (`extract` and `resolve_external`) so the factory cannot bypass the
168    /// singleton cache or violate transient/singleton scope semantics.
169    ///
170    /// # Migrating from `ctx.resolve::<T>()`
171    ///
172    /// ```rust,ignore
173    /// // Before (bypassed singleton cache):
174    /// DynProvider::with_ctx(|ctx| async move {
175    ///     let config = ctx.resolve::<AppConfig>().await?;   // ← dangerous
176    ///     Ok(Database::connect(&config.db_url).await?)
177    /// })
178    ///
179    /// // After (scope-safe):
180    /// DynProvider::with_ctx(|ctx| async move {
181    ///     let config: Inject<AppConfig> = ctx.extract().await?;
182    ///     Ok(Database::connect(&config.db_url).await?)
183    /// })
184    /// ```
185    pub fn with_ctx<F, Fut>(f: F) -> Self
186    where
187        F: Fn(FactoryCtx) -> Fut + Send + Sync + 'static,
188        Fut: Future<Output = InjectableResult<T>> + Send + 'static,
189    {
190        Self {
191            f: Box::new(move |ctx_arc| {
192                let fut = f(FactoryCtx::new(ctx_arc));
193                Box::pin(fut)
194            }),
195        }
196    }
197
198    /// Register a pre-built value. On each resolution the value is cloned.
199    ///
200    /// Useful in tests to inject a pre-configured mock without writing a closure:
201    /// ```rust,ignore
202    /// container.register("", DynProvider::from_value(MockDb::default()));
203    /// ```
204    pub fn from_value(value: T) -> Self
205    where
206        T: Clone,
207    {
208        Self::from_arc(Arc::new(value))
209    }
210
211    /// Register a pre-built `Arc<T>`. On each resolution the inner value is cloned.
212    ///
213    /// Use this when you already hold an `Arc<T>` and want to avoid double-wrapping:
214    /// ```rust,ignore
215    /// let shared = Arc::new(MockDb::default());
216    /// container.register("", DynProvider::from_arc(Arc::clone(&shared)));
217    /// ```
218    pub fn from_arc(arc: Arc<T>) -> Self
219    where
220        T: Clone,
221    {
222        Self {
223            f: Box::new(move |_ctx| {
224                let val = (*arc).clone();
225                Box::pin(std::future::ready(Ok(val)))
226            }),
227        }
228    }
229
230    /// Invoke the dynamic provider to construct a value.
231    pub(crate) async fn provide(&self, ctx: Arc<ResolveContext>) -> InjectableResult<T> {
232        (self.f)(ctx).await
233    }
234}