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}