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
  • Cache: Automatic caching within request scope
  • Circular Dependency Detection: Automatic runtime detection with optimized performance

§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,
// }
//
// #[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<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();

§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.

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::AuthUser;
use reinhardt_auth::DefaultUser;

#[get("/profile/")]
pub async fn profile(
    #[inject] AuthUser(user): AuthUser<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

§CurrentUser<U> (deprecated)

Deprecated in favor of AuthUser<U>. Unlike AuthUser<U>, missing context causes silent fallback to anonymous instead of returning an error.

§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::register_type_name;
pub use cycle_detection::with_cycle_detection_scope;
pub use function_handle::FunctionHandle;
pub use override_registry::OverrideRegistry;
pub use depends::Depends;
pub use depends::DependsBuilder;
pub use injectable::Injectable;
pub use injected::DependencyScope as InjectedScope;
pub use injected::Injected;
pub use injected::InjectionMetadata;
pub use injected::OptionalInjected;
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::global_registry;
pub use scope::RequestScope;
pub use scope::Scope;
pub use scope::SingletonScope;
pub use inventory;

Modules§

context
Injection context for dependency resolution
cycle_detection
Task-local circular dependency detection mechanism
depends
Depends wrapper for dependency injection
function_handle
Function handle for fluent override API
graph
Dependency graph analysis and visualization
injectable
Injectable trait for dependencies
injected
Injected wrapper for dependency injection
override_registry
Override registry for dependency injection
params
Reinhardt Parameter Extraction
provider
Dependency providers
registration
Deferred DI registration list for propagating singleton registrations across module boundaries.
registry
Global dependency registry for FastAPI-style dependency injection
scope
Dependency scopes

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.