Skip to main content

reinhardt_di/
lib.rs

1//! # Reinhardt Dependency Injection
2//!
3//! FastAPI-inspired dependency injection system for Reinhardt.
4//!
5//! ## Features
6//!
7//! - **Type-safe**: Full compile-time type checking
8//! - **Async-first**: Built for async/await
9//! - **Scoped**: Request-scoped and singleton dependencies
10//! - **Composable**: Dependencies can depend on other dependencies
11//! - **Cache**: Automatic caching within request scope
12//! - **Circular Dependency Detection**: Automatic runtime detection with optimized performance
13//!
14//! ## Development Tools (dev-tools feature)
15//!
16//! When the `dev-tools` feature is enabled, additional debugging and profiling tools are available:
17//!
18//! - **Visualization**: Generate dependency graphs in DOT format for Graphviz
19//! - **Profiling**: Track dependency resolution performance and identify bottlenecks
20//! - **Advanced Caching**: LRU and TTL-based caching strategies
21//!
22//! ## Generator Support (generator feature) ✅
23//!
24//! Generator-based dependency resolution for lazy, streaming dependency injection.
25//!
26//! **Note**: Uses `genawaiter` crate as a workaround for unstable native async yield.
27//! Will be migrated to native syntax when Rust stabilizes async generators.
28//!
29//! ```rust,no_run
30//! # #[cfg(feature = "generator")]
31//! # use reinhardt_di::generator::DependencyGenerator;
32//! # #[cfg(feature = "generator")]
33//! # async fn example() {
34//! // let gen = DependencyGenerator::new(|co| async move {
35//! //     let db = resolve_database().await;
36//! //     co.yield_(db).await;
37//! //
38//! //     let cache = resolve_cache().await;
39//! //     co.yield_(cache).await;
40//! // });
41//! # }
42//! ```
43//!
44//! ## Example
45//!
46//! ```rust,no_run
47//! # use reinhardt_di::{Depends, Injectable};
48//! # #[tokio::main]
49//! # async fn main() {
50//! // Define a dependency
51//! // struct Database {
52//! //     pool: DbPool,
53//! // }
54//! //
55//! // #[async_trait]
56//! // impl Injectable for Database {
57//! //     async fn inject(ctx: &InjectionContext) -> Result<Self> {
58//! //         Ok(Database {
59//! //             pool: get_pool().await?,
60//! //         })
61//! //     }
62//! // }
63//! //
64//! // Use in endpoint
65//! // #[endpoint(GET "/users")]
66//! // async fn list_users(
67//! //     db: Depends<Database>,
68//! // ) -> Result<Vec<User>> {
69//! //     db.query("SELECT * FROM users").await
70//! // }
71//! # }
72//! ```
73//!
74//! ## InjectionContext Construction
75//!
76//! InjectionContext is constructed using the builder pattern with a required singleton scope:
77//!
78//! ```rust
79//! use reinhardt_di::{InjectionContext, SingletonScope};
80//! use std::sync::Arc;
81//!
82//! // Create singleton scope
83//! let singleton = Arc::new(SingletonScope::new());
84//!
85//! // Build injection context with singleton scope
86//! let ctx = InjectionContext::builder(singleton).build();
87//! ```
88//!
89//! Optional request and param context can be added:
90//!
91//! ```ignore
92//! use reinhardt_di::{InjectionContext, SingletonScope};
93//! use reinhardt_http::Request;
94//! use std::sync::Arc;
95//!
96//! let singleton = Arc::new(SingletonScope::new());
97//!
98//! // Create a dummy request for demonstration
99//! let request = Request::builder()
100//!     .method(hyper::Method::GET)
101//!     .uri("/")
102//!     .version(hyper::Version::HTTP_11)
103//!     .headers(hyper::HeaderMap::new())
104//!     .body(bytes::Bytes::new())
105//!     .build()
106//!     .unwrap();
107//!
108//! let ctx = InjectionContext::builder(singleton)
109//!     .with_request(request)
110//!     .build();
111//! ```
112//!
113//! ## Circular Dependency Detection
114//!
115//! The DI system automatically detects circular dependencies at runtime using an optimized
116//! thread-local mechanism:
117//!
118//! ```ignore
119//! # use reinhardt_di::{Injectable, InjectionContext, SingletonScope, DiResult};
120//! # use async_trait::async_trait;
121//! # use std::sync::Arc;
122//! #[derive(Clone)]
123//! struct ServiceA {
124//!     b: Arc<ServiceB>,
125//! }
126//!
127//! #[derive(Clone)]
128//! struct ServiceB {
129//!     a: Arc<ServiceA>,  // Circular dependency!
130//! }
131//!
132//! #[async_trait]
133//! impl Injectable for ServiceA {
134//!     async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
135//!         let b = ctx.resolve::<ServiceB>().await?;
136//!         Ok(ServiceA { b })
137//!     }
138//! }
139//!
140//! #[async_trait]
141//! impl Injectable for ServiceB {
142//!     async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
143//!         let a = ctx.resolve::<ServiceA>().await?;
144//!         Ok(ServiceB { a })
145//!     }
146//! }
147//!
148//! let singleton = Arc::new(SingletonScope::new());
149//! let ctx = InjectionContext::builder(singleton).build();
150//!
151//! // This will return Err with DiError::CircularDependency
152//! let result = ctx.resolve::<ServiceA>().await;
153//! assert!(result.is_err());
154//! ```
155//!
156//! ### Performance Characteristics
157//!
158//! - **Cache Hit**: < 5% overhead (cycle detection completely skipped)
159//! - **Cache Miss**: 10-20% overhead (O(1) detection using HashSet)
160//! - **Deep Chains**: Sampling reduces linear cost (checks every 10th at depth 50+)
161//! - **Thread Safety**: Thread-local storage eliminates lock contention
162//!
163//! ## Development Tools Example
164//!
165//! ```ignore
166//! # #[cfg(feature = "dev-tools")]
167//! # use reinhardt_di::{visualization::DependencyGraph, profiling::DependencyProfiler};
168//! # #[cfg(feature = "dev-tools")]
169//! # fn main() {
170//! // fn visualize_dependencies() {
171//! //     let mut graph = DependencyGraph::new();
172//! //     graph.add_node("Database", "singleton");
173//! //     graph.add_node("UserService", "request");
174//! //     graph.add_dependency("UserService", "Database");
175//! //
176//! //     println!("{}", graph.to_dot());
177//! // }
178//! //
179//! // fn profile_resolution() {
180//! //     let mut profiler = DependencyProfiler::new();
181//! //     profiler.start_resolve("Database");
182//! //     // ... perform resolution ...
183//! //     profiler.end_resolve("Database");
184//! //
185//! //     let report = profiler.generate_report();
186//! //     println!("{}", report.to_string());
187//! // }
188//! # }
189//! ```
190//!
191//! ## Auth Extractor DI Context Requirements
192//!
193//! The `reinhardt-auth` crate provides injectable auth extractors that depend on
194//! specific DI context configuration. Understanding these requirements is essential
195//! for proper authentication integration.
196//!
197//! ### `AuthUser<U>` (recommended)
198//!
199//! Loads the full user model from the database. Requires:
200//!
201//! - **`DatabaseConnection`** registered as a singleton in `InjectionContext`
202//! - **`AuthState`** present in request extensions (set by authentication middleware)
203//! - Feature `params` enabled on `reinhardt-auth`
204//!
205//! Returns an injection error if any requirement is missing (fail-fast behavior).
206//!
207//! ```rust,ignore
208//! use reinhardt_auth::AuthUser;
209//! use reinhardt_auth::DefaultUser;
210//!
211//! #[get("/profile/")]
212//! pub async fn profile(
213//!     #[inject] AuthUser(user): AuthUser<DefaultUser>,
214//! ) -> ViewResult<Response> {
215//!     let username = user.get_username();
216//!     // ...
217//! }
218//! ```
219//!
220//! ### `AuthInfo` (lightweight alternative)
221//!
222//! Extracts authentication metadata without a database query. Requires:
223//!
224//! - **`AuthState`** present in request extensions (set by authentication middleware)
225//! - No `DatabaseConnection` needed
226//!
227//! ### `CurrentUser<U>` (deprecated)
228//!
229//! Deprecated in favor of `AuthUser<U>`. Unlike `AuthUser<U>`, missing context
230//! causes silent fallback to anonymous instead of returning an error.
231//!
232//! ### Startup Validation
233//!
234//! Call `reinhardt_auth::validate_auth_extractors()` during application startup
235//! to verify that required dependencies (e.g., `DatabaseConnection`) are registered
236//! before the first request arrives.
237
238#![warn(missing_docs)]
239
240pub mod params;
241
242pub mod context;
243pub mod cycle_detection;
244pub mod depends;
245pub mod function_handle;
246pub mod graph;
247pub mod injectable;
248pub mod injected;
249pub mod override_registry;
250pub mod provider;
251pub mod registration;
252pub mod registry;
253pub mod scope;
254
255use thiserror::Error;
256
257pub use context::{InjectionContext, InjectionContextBuilder, RequestContext};
258pub use cycle_detection::{
259	CycleError, ResolutionGuard, begin_resolution, register_type_name, with_cycle_detection_scope,
260};
261pub use function_handle::FunctionHandle;
262pub use override_registry::OverrideRegistry;
263
264#[cfg(feature = "params")]
265pub use context::{ParamContext, Request};
266pub use depends::{Depends, DependsBuilder};
267pub use injectable::Injectable;
268pub use injected::{
269	DependencyScope as InjectedScope, Injected, InjectionMetadata, OptionalInjected,
270};
271pub use provider::{Provider, ProviderFn};
272pub use registration::DiRegistrationList;
273pub use registry::{
274	DependencyRegistration, DependencyRegistry, DependencyScope, FactoryTrait, global_registry,
275};
276pub use scope::{RequestScope, Scope, SingletonScope};
277
278// Re-export inventory for macro use
279pub use inventory;
280
281// Re-export macros
282#[cfg(feature = "macros")]
283pub use reinhardt_di_macros::{injectable, injectable_factory};
284
285/// Errors that can occur during dependency injection resolution.
286#[derive(Debug, Error)]
287pub enum DiError {
288	/// The requested dependency was not found in the container.
289	#[error("Dependency not found: {0}")]
290	NotFound(String),
291
292	/// A circular dependency chain was detected during resolution.
293	#[error("Circular dependency detected: {0}")]
294	CircularDependency(String),
295
296	/// An error occurred in a dependency provider function.
297	#[error("Provider error: {0}")]
298	ProviderError(String),
299
300	/// The resolved type did not match the expected type.
301	#[error("Type mismatch: expected {expected}, got {actual}")]
302	TypeMismatch {
303		/// The type that was expected.
304		expected: String,
305		/// The type that was actually resolved.
306		actual: String,
307	},
308
309	/// An error related to dependency scoping (request vs singleton).
310	#[error("Scope error: {0}")]
311	ScopeError(String),
312
313	/// The requested type was not registered in the dependency registry.
314	#[error("Type '{type_name}' not registered. {hint}")]
315	NotRegistered {
316		/// The name of the unregistered type.
317		type_name: String,
318		/// A hint message suggesting how to register the type.
319		hint: String,
320	},
321
322	/// A required dependency was not registered.
323	#[error("Dependency not registered: {type_name}")]
324	DependencyNotRegistered {
325		/// The name of the unregistered dependency type.
326		type_name: String,
327	},
328
329	/// An internal error in the DI system.
330	#[error("Internal error: {message}")]
331	Internal {
332		/// A description of the internal error.
333		message: String,
334	},
335}
336
337impl From<DiError> for reinhardt_core::exception::Error {
338	fn from(err: DiError) -> Self {
339		match &err {
340			DiError::NotFound(_)
341			| DiError::NotRegistered { .. }
342			| DiError::DependencyNotRegistered { .. } => reinhardt_core::exception::Error::NotFound(
343				format!("Dependency injection error: {}", err),
344			),
345			_ => reinhardt_core::exception::Error::Internal(format!(
346				"Dependency injection error: {}",
347				err
348			)),
349		}
350	}
351}
352
353/// A specialized `Result` type for dependency injection operations.
354pub type DiResult<T> = std::result::Result<T, DiError>;
355
356// Generator support
357#[cfg(feature = "generator")]
358pub mod generator;
359
360// Development tools
361#[cfg(feature = "dev-tools")]
362pub mod visualization;
363
364#[cfg(feature = "dev-tools")]
365pub mod profiling;
366
367#[cfg(feature = "dev-tools")]
368pub mod advanced_cache;