| Crates.io | reinhardt-di |
| lib.rs | reinhardt-di |
| version | 0.1.0-alpha.1 |
| created_at | 2026-01-23 04:36:13.548722+00 |
| updated_at | 2026-01-23 04:36:13.548722+00 |
| description | Dependency injection system for Reinhardt, inspired by FastAPI |
| homepage | |
| repository | https://github.com/kent8192/reinhardt-rs |
| max_upload_size | |
| id | 2063434 |
| size | 578,713 |
FastAPI-inspired dependency injection system for Reinhardt.
Provides a FastAPI-style dependency injection system with support for request-scoped and singleton-scoped dependency caching, automatic resolution of nested dependencies, and integration with authentication and database connections.
Delivers the FastAPI development experience in Rust with type-safe and async-first design.
Add reinhardt to your Cargo.toml:
[dependencies]
reinhardt = { version = "0.1.0-alpha.1", features = ["di"] }
# Or use a preset:
# reinhardt = { version = "0.1.0-alpha.1", features = ["standard"] } # Recommended
# reinhardt = { version = "0.1.0-alpha.1", features = ["full"] } # All features
Then import DI features:
use reinhardt::di::{Depends, Injectable, InjectionContext};
use reinhardt::di::{Injected, OptionalInjected, SingletonScope};
Note: DI features are included in the standard and full feature presets.
Types implementing Default + Clone + Send + Sync + 'static automatically implement the Injectable trait and can be used as dependencies.
reinhardt-di provides two wrapper types for dependency injection:
✓ Injected<T> Wrapper: Low-level dependency wrapper with metadata
Arc<T> wrapper with injection metadata (scope, cached status)Deref trait for transparent access to inner valueresolve(&ctx) - Resolve with cache (default)resolve_uncached(&ctx) - Resolve without cache.scope() and .is_cached() methods✓ Depends<T> Wrapper: High-level FastAPI-style builder
Depends::<T>::new() - Cache enabled (default)Depends::<T>::no_cache() - Cache disabledresolve(&ctx) - Dependency resolutionfrom_value(value) - Generate from value for testing✓ OptionalInjected<T> Wrapper: Optional dependency wrapper
Option<Injected<T>> for dependencies that may not be availableresolve(&ctx) - Returns Ok(None) if dependency not foundRecommendation: Use Depends<T> for most cases (more ergonomic). Use Injected<T> when you need direct control or metadata access.
✓ Injectable Trait: Define types that can be injected as dependencies
Default + Clone + Send + Sync + 'static✓ InjectionContext: Context for dependency resolution
InjectionContext::builder(singleton).build()✓ RequestScope: Caching within requests
TypeId as key)Arc<RwLock<HashMap>>)✓ SingletonScope: Application-wide caching
✓ Dependency Caching: Automatic caching within request scope
✓ Nested Dependencies: Dependencies can depend on other dependencies
✓ Dependency Overrides: Dependency overrides for testing
✓ Provider System: Async factory pattern
Provider trait - Generic interface for providing dependenciesProviderFn - Function-based providerNotFound - Dependency not foundCircularDependency - Circular dependency detectionProviderError - Provider errorsTypeMismatch - Type mismatchScopeError - Scope-related errors✓ HTTP Integration: Integration with HTTP requests/responses
✓ WebSocket Support: Dependency injection into WebSocket connections
Depends<T> in WebSocket handlersuse reinhardt::di::{Injectable, InjectionContext};
#[derive(Clone)]
struct DatabaseConnection {
// Setup
}
impl DatabaseConnection {
async fn setup() -> Self {
// Initialize connection
DatabaseConnection { }
}
async fn cleanup(self) {
// Close connection
}
}
#[derive(Clone)]
struct CallableDependency {
prefix: String,
}
impl CallableDependency {
fn call(&self, value: String) -> String {
format!("{}{}", self.prefix, value)
}
}
// Path parameter accessible in dependency
#[async_trait::async_trait]
impl Injectable for UserValidator {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
let user_id = UserId::inject(ctx).await?;
Ok(UserValidator { user_id: user_id.0 })
}
}
// Security dependency with scopes
#[async_trait::async_trait]
impl Injectable for UserData {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
let scopes = ctx.get_request::<SecurityScopes>()?;
Ok(UserData { scopes: scopes.scopes })
}
}
Depends<T>use reinhardt::di::{Depends, Injectable, InjectionContext, SingletonScope};
use std::sync::Arc;
#[derive(Clone, Default)]
struct Config {
api_key: String,
database_url: String,
}
#[tokio::main]
async fn main() {
// Creating a singleton scope
let singleton = Arc::new(SingletonScope::new());
// Creating the request context
let ctx = InjectionContext::builder(singleton).build();
// Dependency Resolution (Cache Enabled)
let config = Depends::<Config>::new()
.resolve(&ctx)
.await
.unwrap();
println!("API Key: {}", config.api_key);
}
Injected<T>use reinhardt::di::{Injected, OptionalInjected, Injectable, InjectionContext, SingletonScope};
use std::sync::Arc;
#[derive(Clone, Default)]
struct Config {
api_key: String,
database_url: String,
}
#[derive(Clone, Default)]
struct Cache {
enabled: bool,
}
#[tokio::main]
async fn main() {
let singleton = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton).build();
// Resolve with cache (default)
let config: Injected<Config> = Injected::resolve(&ctx).await.unwrap();
println!("API Key: {}", config.api_key); // Deref trait allows direct access
// Access metadata
println!("Scope: {:?}", config.scope());
println!("Cached: {}", config.is_cached());
// Resolve without cache
let fresh_config = Injected::<Config>::resolve_uncached(&ctx).await.unwrap();
// Optional dependency
let optional_cache: OptionalInjected<Cache> = OptionalInjected::resolve(&ctx).await.unwrap();
if let Some(cache) = optional_cache.as_ref() {
println!("Cache enabled: {}", cache.enabled);
}
}
use reinhardt::di::{Injectable, InjectionContext, DiResult};
struct Database {
pool: DbPool,
}
#[async_trait::async_trait]
impl Injectable for Database {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
// Custom initialization logic
let config = Config::inject(ctx).await?;
let pool = create_pool(&config.database_url).await?;
Ok(Database { pool })
}
}
#[derive(Clone)]
struct ServiceA {
db: Arc<Database>,
}
#[async_trait::async_trait]
impl Injectable for ServiceA {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
// Depends on Database
let db = Database::inject(ctx).await?;
Ok(ServiceA { db: Arc::new(db) })
}
}
#[derive(Clone)]
struct ServiceB {
service_a: Arc<ServiceA>,
config: Config,
}
#[async_trait::async_trait]
impl Injectable for ServiceB {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
// Depends on ServiceA and Config (nested dependencies)
let service_a = ServiceA::inject(ctx).await?;
let config = Config::inject(ctx).await?;
Ok(ServiceB {
service_a: Arc::new(service_a),
config,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct MockDatabase {
// Mock implementation for testing
}
#[async_trait::async_trait]
impl Injectable for MockDatabase {
async fn inject(_ctx: &InjectionContext) -> DiResult<Self> {
Ok(MockDatabase { /* ... */ })
}
}
#[tokio::test]
async fn test_with_mock_database() {
let singleton = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton).build();
// Inject mock for testing
let mock_db = MockDatabase::inject(&ctx).await.unwrap();
// Test code using mock_db
}
}
// Cache enabled (default) - Returns the same instance
let config1 = Depends::<Config>::new().resolve(&ctx).await?;
let config2 = Depends::<Config>::new().resolve(&ctx).await?;
// config1 and config2 are the same instance
// Cache disabled - Creates new instance each time
let config3 = Depends::<Config>::no_cache().resolve(&ctx).await?;
let config4 = Depends::<Config>::no_cache().resolve(&ctx).await?;
// config3 and config4 are different instances
Dependency caching is managed using type (TypeId) as the key. This allows dependencies of the same type to be automatically cached.
SingletonScope (Application level)
↓ Shared
InjectionContext (Request level)
↓ Holds
RequestScope (In-request cache)
Arc<RwLock<HashMap>>Injectable trait requires Send + SyncThe testing framework includes a comprehensive test suite:
Arc| Feature | FastAPI (Python) | reinhardt-di (Rust) |
|---|---|---|
| Basic DI | ✓ | ✓ |
| Request Scope | ✓ | ✓ |
| Singleton Scope | ✓ | ✓ |
| Dependency Caching | ✓ | ✓ |
| Nested Dependencies | ✓ | ✓ |
| Dependency Overrides | ✓ | ✓ |
yield Pattern |
✓ | ✓ |
| Type Safety | Runtime | Compile-time |
| Performance | Dynamic | Static & Fast |
The macros module provides procedural macros for simplified dependency injection setup.
#[injectable] - Struct Injection RegistrationMark a struct as injectable and automatically register it with the global registry.
Syntax:
#[injectable]
#[scope(singleton|request|transient)]
struct YourStruct {
#[no_inject]
field: Type,
}
Attributes:
`#[scope(singleton)]` - Singleton scope (default)`#[scope(request)]` - Request scope`#[scope(transient)]` - Transient scope (new instance every time)`#[no_inject]` - Exclude specific fields from automatic injectionExample:
use reinhardt::di::macros::injectable;
#[injectable]
#[scope(singleton)]
struct Config {
#[no_inject]
database_url: String,
api_key: String,
}
#[injectable_factory] - Async Function FactoryMark an async function as a dependency factory for complex initialization logic.
Syntax:
#[injectable_factory]
#[scope(singleton|request|transient)]
async fn factory_function(#[inject] dep: Arc<Dependency>) -> ReturnType {
// Initialization logic
}
Attributes:
`#[scope(singleton)]` - Singleton scope (default)`#[scope(request)]` - Request scope`#[scope(transient)]` - Transient scope`#[inject]` - Mark function parameters for automatic injectionExample:
use reinhardt::di::macros::injectable_factory;
use std::sync::Arc;
#[injectable_factory]
#[scope(singleton)]
async fn create_database(#[inject] config: Arc<Config>) -> DatabaseConnection {
DatabaseConnection::connect(&config.database_url)
.await
.expect("Failed to connect to database")
}
`Injectable` traituse reinhardt::di::{macros::injectable, InjectionContext, SingletonScope};
use std::sync::Arc;
#[injectable]
struct Logger {
level: String,
}
impl Default for Logger {
fn default() -> Self {
Logger {
level: "info".to_string(),
}
}
}
#[tokio::main]
async fn main() {
let singleton = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton).build();
let logger = Logger::inject(&ctx).await.unwrap();
println!("Log level: {}", logger.level);
}
use reinhardt::di::macros::{injectable, injectable_factory};
use std::sync::Arc;
#[injectable]
#[scope(singleton)]
struct AppConfig {
#[no_inject]
db_url: String,
#[no_inject]
cache_size: usize,
}
#[injectable_factory]
#[scope(request)]
async fn create_service(
#[inject] config: Arc<AppConfig>
) -> MyService {
MyService::new(config.db_url.clone(), config.cache_size)
}
FromRequest trait: Core abstraction for asynchronous parameter extractionParamContext: Management of path parameters and header/cookie namesParamErrorpath.rs)Path<T>: Extract single value from URL path
i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64, bool, StringDeref: *path or path.0into_inner() methodPathStruct<T>: Extract multiple path parameters into struct
DeserializeOwned"42" → 42)query.rs)Query<T>: Extract parameters from URL query string
serdeOption<T>)multi-value-arrays feature):
?q=5&q=6 → Vec<i32>header.rs, header_named.rs)Header<T>: Extract value from request headers
String and Option<String>ParamContextHeaderStruct<T>: Extract multiple headers into struct
HeaderNamed<N, T>: Compile-time header name specification
Authorization, ContentTypeString and Option<String>HeaderName traitcookie.rs, cookie_named.rs)Cookie<T>: Extract value from cookies
String and Option<String>ParamContextCookieStruct<T>: Extract multiple cookies into struct
CookieNamed<N, T>: Compile-time cookie name specification
SessionId, CsrfTokenString and Option<String>CookieName traitbody.rs, json.rs, form.rs)Body: Extract raw request body as bytesJson<T>: JSON body deserialization
serde_jsonDeref and into_inner()Form<T>: Extract application/x-www-form-urlencoded form data
serde_urlencodedmultipart.rs, requires multipart feature)Multipart: Multipart/form-data support
multer cratenext_field()validation.rs, requires validation feature)Validated<T, V>: Validated parameter wrapperWithValidation trait: Fluent API for validation constraints
min_length(), max_length()min_value(), max_value()regex()email(), url()ValidationConstraints<T>: Chainable validation builder
validate_string(): String value validationvalidate_number(): Numeric validationValidatedPath<T>, ValidatedQuery<T>, ValidatedForm<T>reinhardt-validatorsThis crate is part of the Reinhardt project and follows the same dual-license structure (MIT or Apache-2.0).