reinhardt_di/injectable.rs
1//! Injectable trait for dependencies
2
3use crate::{DiResult, context::InjectionContext};
4
5/// Injectable trait for dependencies.
6///
7/// This trait defines how a type can be injected as a dependency.
8/// Types implementing this trait can be used with `Depends<T>`.
9///
10/// # Blanket Implementations
11///
12/// The following blanket implementations are provided:
13///
14/// - **`Arc<T>`** where `T: Injectable` — injects the inner `T` and wraps it in `Arc`
15/// - **`Depends<T>`** where `T: Send + Sync + 'static` — resolves `T` via the global
16/// registry with caching and circular dependency detection
17/// - **`Option<T>`** where `T: Injectable` — returns `None` on injection failure
18/// instead of propagating the error
19///
20/// # Custom Implementation
21///
22/// To make a type injectable, use one of these approaches:
23///
24/// 1. **`#[injectable]` attribute macro** — generates an `Injectable` impl from
25/// a constructor function
26/// 2. **`#[injectable_factory]` attribute macro** — generates an `Injectable` impl
27/// from a factory function
28/// 3. **Manual `impl Injectable`** — implement the trait directly with
29/// `#[async_trait]`
30///
31/// # Example
32///
33/// ```rust,no_run
34/// use reinhardt_di::{Injectable, InjectionContext, DiResult, Depends};
35/// use async_trait::async_trait;
36///
37/// # #[derive(Clone)]
38/// # struct DbPool;
39/// # impl DbPool {
40/// # async fn connect() -> DiResult<Self> { Ok(DbPool) }
41/// # }
42/// struct Database {
43/// pool: DbPool,
44/// }
45///
46/// #[async_trait]
47/// impl Injectable for Database {
48/// async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
49/// Ok(Database {
50/// pool: DbPool::connect().await?,
51/// })
52/// }
53/// }
54/// ```
55#[async_trait::async_trait]
56pub trait Injectable: Sized + Send + Sync + 'static {
57 /// Creates an instance of this type by resolving dependencies from the injection context.
58 async fn inject(ctx: &InjectionContext) -> DiResult<Self>;
59
60 /// Inject without using cache (for `cache = false` support).
61 ///
62 /// This method creates a new instance without checking or updating any cache.
63 /// By default, it delegates to `inject()`, but types can override this
64 /// to provide cache-free injection.
65 async fn inject_uncached(ctx: &InjectionContext) -> DiResult<Self> {
66 Self::inject(ctx).await
67 }
68}
69
70/// Blanket implementation of Injectable for `Arc<T>`
71///
72/// This allows using `Arc<T>` directly in endpoint handlers with `#[inject]`:
73///
74/// ```ignore
75/// # use reinhardt_di::Injectable;
76/// # use std::sync::Arc;
77/// # struct DatabaseConnection;
78/// # struct Response;
79/// # type ViewResult<T> = Result<T, Box<dyn std::error::Error>>;
80/// # use reinhardt_core::endpoint;
81/// #[endpoint]
82/// async fn handler(
83/// #[inject] db: Arc<DatabaseConnection>,
84/// ) -> ViewResult<Response> {
85/// // ...
86/// # Ok(Response)
87/// }
88/// ```
89///
90/// The implementation injects `T` first, then wraps it in `Arc`.
91#[async_trait::async_trait]
92impl<T> Injectable for std::sync::Arc<T>
93where
94 T: Injectable,
95{
96 async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
97 T::inject(ctx).await.map(std::sync::Arc::new)
98 }
99
100 async fn inject_uncached(ctx: &InjectionContext) -> DiResult<Self> {
101 T::inject_uncached(ctx).await.map(std::sync::Arc::new)
102 }
103}
104
105/// Blanket implementation of Injectable for `Depends<T>`
106///
107/// This allows using `Depends<T>` directly in endpoint handlers with `#[inject]`:
108///
109/// ```ignore
110/// # use reinhardt_di::{Depends, Injectable};
111/// # struct DatabaseConnection;
112/// # struct Response;
113/// # type ViewResult<T> = Result<T, Box<dyn std::error::Error>>;
114/// # use reinhardt_core::endpoint;
115/// #[endpoint]
116/// async fn handler(
117/// #[inject] db: Depends<DatabaseConnection>,
118/// ) -> ViewResult<Response> {
119/// // ...
120/// # Ok(Response)
121/// }
122/// ```
123///
124/// The implementation delegates to `Depends::resolve()`, which resolves `T`
125/// from the global registry with caching and circular dependency detection.
126/// Falls back to `T::inject()` if the type is not in the global registry.
127#[async_trait::async_trait]
128impl<T> Injectable for crate::depends::Depends<T>
129where
130 T: Injectable,
131{
132 async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
133 crate::depends::Depends::<T>::resolve(ctx, true).await
134 }
135
136 async fn inject_uncached(ctx: &InjectionContext) -> DiResult<Self> {
137 crate::depends::Depends::<T>::resolve(ctx, false).await
138 }
139}
140
141/// Blanket implementation of Injectable for `Option<T>`
142///
143/// This allows optional injection where failure results in `None`
144/// instead of an error. Useful for endpoints that serve both
145/// authenticated and anonymous users.
146///
147/// # Security Note
148///
149/// `Option<T>` swallows ALL injection errors into `None`.
150/// For security-critical endpoints, use `T` directly to ensure
151/// errors are surfaced as HTTP 401/500.
152///
153/// ```ignore
154/// # use reinhardt_di::Injectable;
155/// # struct AuthInfo;
156/// # struct Response;
157/// # type ViewResult<T> = Result<T, Box<dyn std::error::Error>>;
158/// # use reinhardt_core::endpoint;
159/// #[endpoint]
160/// async fn handler(
161/// #[inject] auth: Option<AuthInfo>,
162/// ) -> ViewResult<Response> {
163/// // auth is None if not authenticated
164/// # Ok(Response)
165/// }
166/// ```
167#[async_trait::async_trait]
168impl<T> Injectable for Option<T>
169where
170 T: Injectable,
171{
172 async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
173 match T::inject(ctx).await {
174 Ok(value) => Ok(Some(value)),
175 Err(_) => Ok(None),
176 }
177 }
178
179 async fn inject_uncached(ctx: &InjectionContext) -> DiResult<Self> {
180 match T::inject_uncached(ctx).await {
181 Ok(value) => Ok(Some(value)),
182 Err(_) => Ok(None),
183 }
184 }
185}