Skip to main content

Crate reinhardt_di

Crate reinhardt_di 

Source
Expand description

§Reinhardt Dependency Injection

FastAPI-inspired dependency injection system for Reinhardt.

§Features

  • Type-safe: Full compile-time type checking
  • Async-first: Built for async/await
  • Scoped: Request-scoped and singleton dependencies
  • Composable: Dependencies can depend on other dependencies
  • Extensible wrappers: Custom #[inject] wrappers can implement InjectableType instead of relying on framework-defined type names
  • Cache: Automatic caching within request scope
  • Circular Dependency Detection: Automatic runtime detection with optimized performance

§Custom #[inject] Wrappers

#[inject] resolves wrapper parameters through InjectableType. This lets framework and application wrappers choose a concrete registry key while exposing a domain-specific parameter type. The keyed provider API uses FactoryOutput as the registered value and Depends as the consumer wrapper.

§Cargo features

  • testing — exposes [DependencyRegistry::register_override] and [testing::OverrideGuard] for use by reinhardt-testkit and other test harnesses. When operating on a per-context registry (via InjectionContextBuilder::with_registry), #[serial(di_registry)] is not required. Direct mutations of the global registry still require #[serial(di_registry)].

§Development Tools (dev-tools feature)

When the dev-tools feature is enabled, additional debugging and profiling tools are available:

  • Visualization: Generate dependency graphs in DOT format for Graphviz
  • Profiling: Track dependency resolution performance and identify bottlenecks
  • Advanced Caching: LRU and TTL-based caching strategies

§Generator Support (generator feature) ✅

Generator-based dependency resolution for lazy, streaming dependency injection.

Note: Uses genawaiter crate as a workaround for unstable native async yield. Will be migrated to native syntax when Rust stabilizes async generators.

// let gen = DependencyGenerator::new(|co| async move {
//     let db = resolve_database().await;
//     co.yield_(db).await;
//
//     let cache = resolve_cache().await;
//     co.yield_(cache).await;
// });

§Example

// Define a dependency
// struct Database {
//     pool: DbPool,
// }
//
// struct DatabaseKey;
// impl InjectableKey for DatabaseKey {}
//
// #[async_trait]
// impl Injectable for Database {
//     async fn inject(ctx: &InjectionContext) -> Result<Self> {
//         Ok(Database {
//             pool: get_pool().await?,
//         })
//     }
// }
//
// Use in endpoint
// #[endpoint(GET "/users")]
// async fn list_users(
//     db: Depends<DatabaseKey, Database>,
// ) -> Result<Vec<User>> {
//     db.query("SELECT * FROM users").await
// }

§InjectionContext Construction

InjectionContext is constructed using the builder pattern with a required singleton scope:

use reinhardt_di::{InjectionContext, SingletonScope};
use std::sync::Arc;

// Create singleton scope
let singleton = Arc::new(SingletonScope::new());

// Build injection context with singleton scope
let ctx = InjectionContext::builder(singleton).build();

Optional request and param context can be added:

use reinhardt_di::{InjectionContext, SingletonScope};
use reinhardt_http::Request;
use std::sync::Arc;

let singleton = Arc::new(SingletonScope::new());

// Create a dummy request for demonstration
let request = Request::builder()
    .method(hyper::Method::GET)
    .uri("/")
    .version(hyper::Version::HTTP_11)
    .headers(hyper::HeaderMap::new())
    .body(bytes::Bytes::new())
    .build()
    .unwrap();

let ctx = InjectionContext::builder(singleton)
    .with_request(request)
    .build();

§Resolve Context

The get_di_context function provides access to the active InjectionContext within #[injectable] function bodies, without requiring #[inject].

This enables factories to access the DI context for purposes like passing it to downstream consumers:

use reinhardt_di::{ContextLevel, Depends, FactoryOutput, InjectableKey, get_di_context};

struct AppConfigKey;
impl InjectableKey for AppConfigKey {}
struct RouterKey;
impl InjectableKey for RouterKey {}

#[injectable(scope = "transient")]
async fn make_router(
    #[inject] config: Depends<AppConfigKey, AppConfig>,
) -> FactoryOutput<RouterKey, Router> {
    let di_ctx = get_di_context(ContextLevel::Current);
    FactoryOutput::new(Router::new().with_di_context(di_ctx))
}

ContextLevel::Root returns the application-level context, while ContextLevel::Current returns the currently active context (which may be a request-scoped fork).

Use try_get_di_context for a non-panicking variant that returns None when called outside of a DI resolution context.

§Circular Dependency Detection

The DI system automatically detects circular dependencies at runtime using an optimized thread-local mechanism:

#[derive(Clone)]
struct ServiceA {
    b: Arc<ServiceB>,
}

#[derive(Clone)]
struct ServiceB {
    a: Arc<ServiceA>,  // Circular dependency!
}

#[async_trait]
impl Injectable for ServiceA {
    async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
        let b = ctx.resolve::<ServiceB>().await?;
        Ok(ServiceA { b })
    }
}

#[async_trait]
impl Injectable for ServiceB {
    async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
        let a = ctx.resolve::<ServiceA>().await?;
        Ok(ServiceB { a })
    }
}

let singleton = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton).build();

// This will return Err with DiError::CircularDependency
let result = ctx.resolve::<ServiceA>().await;
assert!(result.is_err());

§Performance Characteristics

  • Cache Hit: < 5% overhead (cycle detection completely skipped)
  • Cache Miss: 10-20% overhead (O(1) detection using HashSet)
  • Deep Chains: Sampling reduces linear cost (checks every 10th at depth 50+)
  • Thread Safety: Thread-local storage eliminates lock contention

§Development Tools Example

// fn visualize_dependencies() {
//     let mut graph = DependencyGraph::new();
//     graph.add_node("Database", "singleton");
//     graph.add_node("UserService", "request");
//     graph.add_dependency("UserService", "Database");
//
//     println!("{}", graph.to_dot());
// }
//
// fn profile_resolution() {
//     let mut profiler = DependencyProfiler::new();
//     profiler.start_resolve("Database");
//     // ... perform resolution ...
//     profiler.end_resolve("Database");
//
//     let report = profiler.generate_report();
//     println!("{}", report.to_string());
// }

§Auth Extractor DI Context Requirements

The reinhardt-auth crate provides injectable auth extractors that depend on specific DI context configuration. Understanding these requirements is essential for proper authentication integration.

§CurrentUser<U>

Loads the full user model from the database. Requires:

  • DatabaseConnection registered as a singleton in InjectionContext
  • AuthState present in request extensions (set by authentication middleware)
  • Feature params enabled on reinhardt-auth

Returns an injection error if any requirement is missing (fail-fast behavior).

use reinhardt_auth::CurrentUser;
use reinhardt_auth::DefaultUser;

#[get("/profile/")]
pub async fn profile(
    #[inject] CurrentUser(user): CurrentUser<DefaultUser>,
) -> ViewResult<Response> {
    let username = user.get_username();
    // ...
}

§AuthInfo (lightweight alternative)

Extracts authentication metadata without a database query. Requires:

  • AuthState present in request extensions (set by authentication middleware)
  • No DatabaseConnection needed

§Startup Validation

Call reinhardt_auth::validate_auth_extractors() during application startup to verify that required dependencies (e.g., DatabaseConnection) are registered before the first request arrives.

Re-exports§

pub use context::InjectionContext;
pub use context::InjectionContextBuilder;
pub use context::RequestContext;
pub use cycle_detection::CycleError;
pub use cycle_detection::ResolutionGuard;
pub use cycle_detection::begin_resolution;
pub use cycle_detection::begin_scoped_resolution;
pub use cycle_detection::current_dependent_scope;
pub use cycle_detection::register_type_name;
pub use cycle_detection::with_cycle_detection_scope;
pub use factory_output::FactoryOutput;
pub use function_handle::FunctionHandle;
pub use injectable_key::InjectableKey;
pub use override_registry::OverrideRegistry;
pub use depends::Depends;
pub use depends::DependsBuilder;
pub use injectable::Injectable;
pub use injectable_type::InjectableType;
pub use injected::DependencyScope as InjectedScope;
pub use injected::InjectionMetadata;
pub use provider::Provider;
pub use provider::ProviderFn;
pub use registration::DiRegistrationList;
pub use registry::DependencyRegistration;
pub use registry::DependencyRegistry;
pub use registry::DependencyScope;
pub use registry::FactoryTrait;
pub use registry::InjectableFactory;
pub use registry::InjectableRegistration;
pub use registry::global_registry;
pub use resolve_context::ContextLevel;
pub use resolve_context::get_di_context;
pub use resolve_context::try_get_di_context;
pub use scope::RequestScope;
pub use scope::Scope;
pub use scope::SingletonScope;
pub use validation::RegistryValidator;
pub use validation::ValidationError;
pub use validation::ValidationErrorKind;
pub use async_trait;
pub use inventory;

Modules§

context
Injection context for dependency resolution
cycle_detection
Task-local circular dependency detection mechanism
depends
Depends wrapper for keyed dependency injection.
factory_output
Output wrapper for keyed injectable provider functions.
function_handle
Function handle for fluent override API
graph
Dependency graph analysis and visualization
injectable
Injectable trait for dependencies
injectable_key
Key marker trait for keyed dependency providers.
injectable_type
Trait-based wrapper resolution for #[inject].
injected
Injection metadata types.
override_registry
Override registry for dependency injection
provider
Dependency providers
registration
Deferred DI registration list for propagating singleton registrations across module boundaries.
registry
Global dependency registry for FastAPI-style dependency injection
resolve_context
Task-local resolve context for accessing InjectionContext within DI factories.
scope
Dependency scopes
validation
Startup validation for the DI registry

Macros§

submit_registration
Helper macro for submitting registrations to inventory

Enums§

DiError
Errors that can occur during dependency injection resolution.

Type Aliases§

DiResult
A specialized Result type for dependency injection operations.