ServiceCollection

Struct ServiceCollection 

Source
pub struct ServiceCollection { /* private fields */ }

Implementations§

Source§

impl ServiceCollection

Source

pub fn new() -> Self

Creates a new empty service collection.

Source

pub fn add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> &mut Self

Registers a singleton instance that will be shared across the entire application.

The instance is created immediately and wrapped in an Arc for thread-safe sharing. All requests for this service type will return the same instance.

§Examples
struct Config { 
    database_url: String 
}

let mut services = ServiceCollection::new();
services.add_singleton(Config {
    database_url: "postgres://localhost".to_string()
});
Source

pub fn add_singleton_factory<T, F>(&mut self, factory: F) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Registers a singleton factory that creates the instance on first request.

The factory is called only once, and the result is cached and shared across all subsequent requests. The factory receives a ResolverContext to resolve dependencies.

§Examples
struct Database { url: String }
struct UserService { db: Arc<Database> }

let mut services = ServiceCollection::new();
services.add_singleton(Database { url: "postgres://localhost".to_string() });
services.add_singleton_factory::<UserService, _>(|resolver| {
    UserService {
        db: resolver.get_required::<Database>()
    }
});
Source

pub fn add_scoped_factory<T, F>(&mut self, factory: F) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Registers a scoped factory that creates one instance per scope.

Each scope gets its own instance, but within a scope, the same instance is reused. Perfect for per-request services in web applications.

§Examples
struct Database { url: String }
struct RequestContext { request_id: String }
struct UserService { db: Arc<Database>, context: Arc<RequestContext> }

let mut services = ServiceCollection::new();
services.add_singleton(Database { url: "postgres://localhost".to_string() });
services.add_scoped_factory::<RequestContext, _>(|_| {
    RequestContext { request_id: "req-123".to_string() }
});
services.add_scoped_factory::<UserService, _>(|resolver| {
    UserService {
        db: resolver.get_required::<Database>(),
        context: resolver.get_required::<RequestContext>()
    }
});
Source

pub fn add_transient_factory<T, F>(&mut self, factory: F) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Registers a transient factory that creates a new instance on every request.

No caching is performed - the factory is called every time this service is resolved, even within the same scope.

§Examples
struct Database { url: String }
struct Logger { timestamp: std::time::SystemTime }

let mut services = ServiceCollection::new();
services.add_singleton(Database { url: "postgres://localhost".to_string() });
services.add_transient_factory::<Logger, _>(|_| {
    Logger { timestamp: std::time::SystemTime::now() }
});
Source

pub fn add_singleton_trait<T>(&mut self, value: Arc<T>) -> &mut Self
where T: ?Sized + 'static + Send + Sync,

Registers a singleton trait implementation.

Binds a concrete implementation to a trait, creating a single instance that’s shared across the entire application. The implementation must already be wrapped in an Arc.

§Examples
trait Logger: Send + Sync {
    fn log(&self, message: &str);
}

struct FileLogger { path: String }
impl Logger for FileLogger {
    fn log(&self, message: &str) {
        // Write to file
    }
}

let mut services = ServiceCollection::new();
let logger = Arc::new(FileLogger { path: "/var/log/app.log".to_string() });
services.add_singleton_trait::<dyn Logger>(logger);
Source

pub fn add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Registers a singleton trait factory.

The factory creates a trait implementation on first request, and the result is cached as a singleton. The factory must return an Arc<Trait>.

§Examples
trait Logger: Send + Sync {
    fn log(&self, message: &str);
}

struct FileLogger { path: String }
impl Logger for FileLogger {
    fn log(&self, message: &str) {
        // Write to file
    }
}

let mut services = ServiceCollection::new();
services.add_singleton_trait_factory::<dyn Logger, _>(|_| {
    Arc::new(FileLogger { path: "/var/log/app.log".to_string() })
});
Source

pub fn add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Registers a scoped trait factory.

Creates one trait implementation per scope. Within a scope, the same instance is reused, but different scopes get different instances.

§Examples
trait RequestLogger: Send + Sync {
    fn log_request(&self, path: &str);
}

struct FileRequestLogger { 
    request_id: String,
    file_handle: std::fs::File 
}
impl RequestLogger for FileRequestLogger {
    fn log_request(&self, path: &str) {
        // Log with request ID
    }
}

let mut services = ServiceCollection::new();
services.add_scoped_trait_factory::<dyn RequestLogger, _>(|_| {
    Arc::new(FileRequestLogger { 
        request_id: "req-456".to_string(),
        file_handle: std::fs::File::create("/tmp/request.log").unwrap()
    })
});
Source

pub fn add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Registers a transient trait factory.

Creates a new trait implementation on every request. No caching is performed, making this suitable for lightweight, stateless services.

§Examples
trait TimeProvider: Send + Sync {
    fn now(&self) -> std::time::SystemTime;
}

struct SystemTimeProvider;
impl TimeProvider for SystemTimeProvider {
    fn now(&self) -> std::time::SystemTime {
        std::time::SystemTime::now()
    }
}

let mut services = ServiceCollection::new();
services.add_transient_trait_factory::<dyn TimeProvider, _>(|_| {
    Arc::new(SystemTimeProvider)
});
Source

pub fn add_trait_implementation<T>( &mut self, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
where T: ?Sized + 'static + Send + Sync,

Add trait implementation to multi-binding list

Source

pub fn add_trait_factory<Trait, F>( &mut self, lifetime: Lifetime, factory: F, ) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Add trait factory to multi-binding list

Source

pub fn get_service_descriptors(&self) -> Vec<ServiceDescriptor>

Get all service descriptors for introspection and diagnostics.

Returns a vector of ServiceDescriptor objects that describe all registered services, including their keys, lifetimes, and implementation type information when available.

§Examples
use ferrous_di::{ServiceCollection, Lifetime};
use std::sync::Arc;

let mut services = ServiceCollection::new();
services.add_singleton(42usize);
services.add_scoped_factory::<String, _>(|_| "hello".to_string());

let descriptors = services.get_service_descriptors();
assert_eq!(descriptors.len(), 2);
 
// Find the usize singleton
let usize_desc = descriptors.iter()
    .find(|d| d.type_name().contains("usize"))
    .unwrap();
assert_eq!(usize_desc.lifetime, Lifetime::Singleton);
Source

pub fn add_with_metadata<T, M>( &mut self, value: T, lifetime: Lifetime, metadata: M, ) -> &mut Self
where T: 'static + Send + Sync, M: Send + Sync + 'static,

Register a service with custom metadata.

Metadata can be used for diagnostics, configuration, or other runtime introspection. The metadata must implement Send + Sync + ’static.

§Examples
use ferrous_di::{ServiceCollection, Lifetime};
use std::sync::Arc;

#[derive(Debug)]
struct ServiceMetadata {
    description: String,
    version: String,
}

let mut services = ServiceCollection::new();
services.add_with_metadata(
    42usize,
    Lifetime::Singleton,
    ServiceMetadata {
        description: "Answer to everything".to_string(),
        version: "1.0".to_string(),
    }
);
Source

pub fn get_metadata<M: 'static>(&self, key: &Key) -> Option<&M>

Get metadata for a specific service key.

Returns the metadata if it exists and can be downcast to the specified type.

§Examples
let key = Key::Type(TypeId::of::<usize>(), "usize");
let metadata = services.get_metadata::<ServiceMetadata>(&key);
assert!(metadata.is_some());
Source

pub fn try_add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> bool

Register a singleton if not already registered.

This method only registers the service if no service of type T is currently registered. It returns true if the service was registered, false if it was already registered.

§Examples
use ferrous_di::ServiceCollection;

let mut services = ServiceCollection::new();
 
let registered1 = services.try_add_singleton(42usize);
assert!(registered1); // First registration succeeds
 
let registered2 = services.try_add_singleton(100usize);
assert!(!registered2); // Second registration is ignored
Source

pub fn try_add_singleton_factory<T, F>(&mut self, factory: F) -> bool
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a singleton factory if not already registered.

Source

pub fn try_add_scoped_factory<T, F>(&mut self, factory: F) -> bool
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a scoped factory if not already registered.

Source

pub fn try_add_transient_factory<T, F>(&mut self, factory: F) -> bool
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a transient factory if not already registered.

Source

pub fn try_add_singleton_trait<T>(&mut self, value: Arc<T>) -> bool
where T: ?Sized + 'static + Send + Sync,

Register a singleton trait if not already registered.

Source

pub fn try_add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> bool
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a singleton trait factory if not already registered.

Source

pub fn try_add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> bool
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a scoped trait factory if not already registered.

Source

pub fn try_add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> bool
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a transient trait factory if not already registered.

Source

pub fn try_add_enumerable<T>( &mut self, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
where T: ?Sized + 'static + Send + Sync,

Add enumerable trait registration (always adds, doesn’t check for existing).

This method is equivalent to add_trait_implementation but with a name that matches Microsoft.Extensions.DependencyInjection conventions.

Source

pub fn add_named_singleton<T: 'static + Send + Sync>( &mut self, name: &'static str, value: T, ) -> &mut Self

Register a named singleton service.

Named services allow multiple registrations of the same type distinguished by name. This is useful for scenarios like multiple database connections, different configurations, etc.

§Examples
use ferrous_di::ServiceCollection;

let mut services = ServiceCollection::new();
services.add_named_singleton("primary", 42usize);
services.add_named_singleton("secondary", 100usize);
 
let provider = services.build();
// These would be resolved separately by name
Source

pub fn add_named_singleton_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a named singleton factory.

Source

pub fn add_named_scoped_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a named scoped factory.

Source

pub fn add_named_transient_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where T: 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> T + Send + Sync + 'static,

Register a named transient factory.

Source

pub fn add_named_singleton_trait<T>( &mut self, name: &'static str, value: Arc<T>, ) -> &mut Self
where T: ?Sized + 'static + Send + Sync,

Register a named singleton trait.

Source

pub fn add_named_singleton_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a named singleton trait factory.

Source

pub fn add_named_scoped_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a named scoped trait factory.

Source

pub fn add_named_transient_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
where Trait: ?Sized + 'static + Send + Sync, F: Fn(&ResolverContext<'_>) -> Arc<Trait> + Send + Sync + 'static,

Register a named transient trait factory.

Source

pub fn add_named_trait_implementation<T>( &mut self, name: &'static str, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
where T: ?Sized + 'static + Send + Sync,

Add named multi-trait registration.

Source

pub fn add_observer(&mut self, observer: Arc<dyn DiObserver>) -> &mut Self

Adds a diagnostic observer for DI resolution events.

Observers enable structured tracing and monitoring of the dependency injection container’s behavior. This is particularly valuable for agentic systems where you need to correlate DI events with agent execution steps and debug complex resolution chains.

§Performance

Observer calls are made synchronously during resolution. Keep observer implementations lightweight to avoid impacting performance.

§Examples
use ferrous_di::{ServiceCollection, LoggingObserver, DiObserver};
use std::sync::Arc;

// Using the built-in logging observer
let mut services = ServiceCollection::new();
services.add_observer(Arc::new(LoggingObserver::new()));

// Using a custom observer
struct MetricsObserver {
    counter: std::sync::Arc<std::sync::atomic::AtomicU64>,
}

impl DiObserver for MetricsObserver {
    fn resolving(&self, key: &ferrous_di::Key) {
        self.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
    }

    fn resolved(&self, _key: &ferrous_di::Key, _duration: std::time::Duration) {}
    fn factory_panic(&self, _key: &ferrous_di::Key, _message: &str) {}
}

let counter = Arc::new(std::sync::atomic::AtomicU64::new(0));
services.add_observer(Arc::new(MetricsObserver { counter: counter.clone() }));

let provider = services.build();
// All resolutions will be observed
Source

pub fn decorate_trait<T, F>(&mut self, decorator: F) -> &mut Self
where T: ?Sized + 'static + Send + Sync, F: Fn(Arc<T>) -> Arc<T> + Send + Sync + 'static,

Decorates all registrations of a trait with a wrapper function.

This enables cross-cutting concerns like logging, timeouts, retries, rate limiting, authentication, and PII scrubbing without modifying the original implementations. The decorator function is applied to both single-binding and multi-binding registrations.

This is particularly powerful for agentic systems where you need to apply consistent policies across all tools or services.

§Arguments
  • decorator - A function that takes an Arc<T> and returns a wrapped Arc<T>
§Examples
use ferrous_di::{ServiceCollection, Resolver};
use std::sync::Arc;

trait Tool: Send + Sync {
    fn execute(&self, input: &str) -> String;
}

struct FileTool;
impl Tool for FileTool {
    fn execute(&self, input: &str) -> String {
        format!("File operation: {}", input)
    }
}

struct LoggingWrapper<T: ?Sized> {
    inner: Arc<T>,
}

impl<T: ?Sized> LoggingWrapper<T> {
    fn new(inner: Arc<T>) -> Self { Self { inner } }
}

impl<T: Tool + ?Sized> Tool for LoggingWrapper<T> {
    fn execute(&self, input: &str) -> String {
        println!("Executing tool with input: {}", input);
        let result = self.inner.execute(input);
        println!("Tool result: {}", result);
        result
    }
}

let mut services = ServiceCollection::new();
 
// Register tools
services.add_singleton_trait::<dyn Tool>(Arc::new(FileTool));

// Apply logging to all tools
services.decorate_trait::<dyn Tool, _>(|tool| {
    Arc::new(LoggingWrapper::new(tool))
});

let provider = services.build();
let tool = provider.get_required_trait::<dyn Tool>();
let result = tool.execute("test.txt");
// Logs: "Executing tool with input: test.txt"
// Logs: "Tool result: File operation: test.txt"
Source

pub fn decorate_with<T, D>(&mut self, decorator: D) -> &mut Self
where T: 'static + Send + Sync, D: ServiceDecorator<T> + 'static,

Decorates a concrete service type with a first-class decorator.

This is the modern, type-safe approach to service decoration that provides access to the resolver during decoration. Perfect for workflow engines that need to inject dependencies during decoration.

§Examples
use ferrous_di::{ServiceCollection, ServiceDecorator, Resolver};
use std::sync::Arc;

struct UserService {
    name: String,
}

struct LoggingDecorator;

impl ServiceDecorator<UserService> for LoggingDecorator {
    fn decorate(&self, original: Arc<UserService>, _resolver: &dyn ferrous_di::traits::ResolverCore) -> Arc<UserService> {
        println!("Accessing user: {}", original.name);
        original
    }
}

let mut services = ServiceCollection::new();
services.add_singleton(UserService { name: "Alice".to_string() });
services.decorate_with::<UserService, _>(LoggingDecorator);

let provider = services.build();
let user = provider.get_required::<UserService>(); // Logs: "Accessing user: Alice"
Source

pub fn decorate_trait_with<T, D>(&mut self, decorator: D) -> &mut Self
where T: ?Sized + 'static + Send + Sync, D: TraitDecorator<T> + 'static,

Decorates a trait service type with a first-class decorator.

Similar to decorate_with but works with trait objects for maximum flexibility. Essential for workflow engines that need to wrap trait implementations.

§Examples
use ferrous_di::{ServiceCollection, TraitDecorator, Resolver};
use std::sync::Arc;

trait Logger: Send + Sync {
    fn log(&self, message: &str);
}

struct ConsoleLogger;
impl Logger for ConsoleLogger {
    fn log(&self, message: &str) {
        println!("LOG: {}", message);
    }
}

struct PrefixDecorator;

impl TraitDecorator<dyn Logger> for PrefixDecorator {
    fn decorate(&self, original: Arc<dyn Logger>, _resolver: &dyn ferrous_di::traits::ResolverCore) -> Arc<dyn Logger> {
        struct PrefixLogger {
            inner: Arc<dyn Logger>,
        }
        impl Logger for PrefixLogger {
            fn log(&self, message: &str) {
                self.inner.log(&format!("[WORKFLOW] {}", message));
            }
        }
        Arc::new(PrefixLogger { inner: original })
    }
}

let mut services = ServiceCollection::new();
services.add_singleton_trait::<dyn Logger>(Arc::new(ConsoleLogger));
services.decorate_trait_with::<dyn Logger, _>(PrefixDecorator);

let provider = services.build();
let logger = provider.get_required_trait::<dyn Logger>();
logger.log("Hello"); // Outputs: "[WORKFLOW] LOG: Hello"
Source

pub fn prewarm<T: 'static + Send + Sync>(&mut self) -> &mut Self

Marks a concrete service type for pre-warming during startup.

Pre-warmed services are resolved during the ServiceProvider::ready() call, eliminating cold-start penalties during agent execution. This is particularly useful for expensive-to-initialize services like ML models, database connections, and authentication tokens.

Services that implement ReadyCheck will also have their readiness verified during the pre-warm phase.

§Examples
use ferrous_di::{ServiceCollection, ReadyCheck};
use async_trait::async_trait;

struct DatabaseService {
    connection: String,
}

#[async_trait]
impl ReadyCheck for DatabaseService {
    async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        // Test database connection
        Ok(())
    }
}

let mut services = ServiceCollection::new();
services.add_singleton(DatabaseService {
    connection: "postgres://localhost".to_string()
});
services.prewarm::<DatabaseService>(); // Pre-warm during startup

// Later during startup:
let provider = services.build();
// let report = provider.ready().await?; // Resolves and checks DatabaseService
Source

pub fn prewarm_trait<T: ?Sized + 'static + Send + Sync>(&mut self) -> &mut Self

Marks a trait service type for pre-warming during startup.

Pre-warmed trait services have all their implementations resolved during the ServiceProvider::ready() call.

§Examples
use ferrous_di::{ServiceCollection, ReadyCheck};
use async_trait::async_trait;
use std::sync::Arc;

trait CacheService: Send + Sync {
    fn get(&self, key: &str) -> Option<String>;
}

struct RedisCache;
impl CacheService for RedisCache {
    fn get(&self, key: &str) -> Option<String> {
        // Redis implementation
        None
    }
}

#[async_trait]
impl ReadyCheck for RedisCache {
    async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        // Test Redis connection
        Ok(())
    }
}

let mut services = ServiceCollection::new();
services.add_singleton_trait::<dyn CacheService>(Arc::new(RedisCache));
services.prewarm_trait::<dyn CacheService>(); // Pre-warm all cache implementations

let provider = services.build();
// let report = provider.ready().await?; // Resolves and checks all cache services
Source

pub fn build(self) -> ServiceProvider

Builds the final service provider from this collection.

This method consumes the ServiceCollection and creates a ServiceProvider that can resolve registered services. The service provider is thread-safe and can be used to create scoped contexts for request-scoped services.

§Returns

A ServiceProvider that can resolve all registered services according to their configured lifetimes.

§Examples
use ferrous_di::{ServiceCollection, Resolver};
use std::sync::Arc;

let mut collection = ServiceCollection::new();
collection.add_singleton(42usize);
collection.add_transient_factory::<String, _>(|_| "Hello".to_string());

let provider = collection.build();
let number = provider.get_required::<usize>();
let text = provider.get_required::<String>();

assert_eq!(*number, 42);
assert_eq!(&*text, "Hello");
Source§

impl ServiceCollection

Source

pub fn add_scope_local<T, F>(&mut self, factory: F) -> &mut Self
where T: Send + Sync + 'static, F: Fn(&ResolverContext<'_>) -> Arc<T> + Send + Sync + 'static,

Registers a scope-local value factory.

The factory is called once per scope to create a value that will be shared across all services within that scope. This is perfect for per-run context like trace IDs, execution budgets, cancellation tokens, and other run-scoped state.

The factory receives a resolver that can access other services, making it possible to build context values that depend on configuration or other services.

§Type Safety

The registered factory creates ScopeLocal<T> instances that can be resolved from any service within the scope using resolver.get_required::<ScopeLocal<T>>().

§Examples
use ferrous_di::{ServiceCollection, ScopeLocal, Resolver, Options};
use std::sync::Arc;
use std::sync::atomic::AtomicU32;

#[derive(Default)]
struct AgentConfig {
    max_steps: u32,
    timeout_ms: u64,
}

struct RunContext {
    trace_id: String,
    max_steps: u32,
    steps_remaining: AtomicU32,
}

let mut services = ServiceCollection::new();
 
// Register configuration
services.add_options::<AgentConfig>()
    .configure(|_r, config| {
        config.max_steps = 100;
        config.timeout_ms = 30000;
    })
    .register();

// Register scope-local context that uses config
services.add_scope_local::<RunContext, _>(|_resolver| {
    Arc::new(RunContext {
        trace_id: "trace-12345".to_string(),
        max_steps: 100,
        steps_remaining: AtomicU32::new(100),
    })
});

// Services can access both config and context
services.add_scoped_factory::<String, _>(|resolver| {
    let ctx = resolver.get_required::<ScopeLocal<RunContext>>();
    let remaining = ctx.steps_remaining.load(std::sync::atomic::Ordering::Relaxed);
    format!("Trace {} has {} steps remaining", ctx.trace_id, remaining)
});

let provider = services.build();
let scope = provider.create_scope();
let status = scope.get_required::<String>();
§Advanced Usage: Multiple Context Types

You can register multiple scope-local context types for different concerns:

use ferrous_di::{ServiceCollection, ScopeLocal, Resolver};
use std::sync::Arc;

struct TraceContext { trace_id: String }
struct BudgetContext { tokens_remaining: std::sync::atomic::AtomicU32 }
struct SecurityContext { user_id: String, permissions: Vec<String> }

let mut services = ServiceCollection::new();

services.add_scope_local::<TraceContext, _>(|_r| {
    Arc::new(TraceContext { 
        trace_id: "trace-12345".to_string() 
    })
});

services.add_scope_local::<BudgetContext, _>(|_r| {
    Arc::new(BudgetContext {
        tokens_remaining: std::sync::atomic::AtomicU32::new(10000)
    })
});

services.add_scope_local::<SecurityContext, _>(|_r| {
    Arc::new(SecurityContext {
        user_id: "agent-user-123".to_string(),
        permissions: vec!["read".to_string(), "write".to_string()],
    })
});

// Each context type can be resolved independently
services.add_scoped_factory::<String, _>(|resolver| {
    let trace = resolver.get_required::<ScopeLocal<TraceContext>>();
    let budget = resolver.get_required::<ScopeLocal<BudgetContext>>();
    let security = resolver.get_required::<ScopeLocal<SecurityContext>>();
     
    format!("User {} (trace: {}) has {} tokens", 
        security.user_id,
        trace.trace_id,
        budget.tokens_remaining.load(std::sync::atomic::Ordering::Relaxed))
});
Source

pub fn add_workflow_context<T, F>(&mut self, factory: F) -> &mut Self
where T: Send + Sync + 'static, F: Fn(&ResolverContext<'_>) -> Arc<T> + Send + Sync + 'static,

Registers a workflow-specific scope-local context factory.

This is a specialized version of add_scope_local that automatically provides common workflow context features like run IDs, execution metadata, and hierarchical scope information.

Perfect for n8n-style workflow engines where each execution run needs rich context information.

§Examples
use ferrous_di::{ServiceCollection, ScopeLocal, WorkflowContext, Resolver};
use std::sync::Arc;

let mut services = ServiceCollection::new();
 
// Register workflow context with auto-generated run ID
services.add_workflow_context::<WorkflowContext, _>(|_resolver| {
    Arc::new(WorkflowContext::new("user_registration_flow"))
});

// Services can access rich workflow context
services.add_scoped_factory::<String, _>(|resolver| {
    let ctx = resolver.get_required::<ScopeLocal<WorkflowContext>>();
    format!("Executing {} (run: {})", ctx.workflow_name(), ctx.run_id())
});

let provider = services.build();
let scope = provider.create_scope();
let status = scope.get_required::<String>();
Source

pub fn add_scope_locals(&mut self) -> ScopeLocalBuilder<'_>

Registers multiple scope-local contexts in a batch.

Convenient for workflow engines that need to register several context types (security, tracing, budgets, etc.) in one operation.

§Examples
use ferrous_di::{ServiceCollection, ScopeLocal, Resolver};
use std::sync::Arc;

struct TraceContext { run_id: String }
struct SecurityContext { user_id: String }
struct BudgetContext { tokens: u32 }

let mut services = ServiceCollection::new();
 
services.add_scope_locals()
    .add::<TraceContext, _>(|_| Arc::new(TraceContext { run_id: "run-123".into() }))
    .add::<SecurityContext, _>(|_| Arc::new(SecurityContext { user_id: "user-456".into() }))
    .add::<BudgetContext, _>(|_| Arc::new(BudgetContext { tokens: 1000 }))
    .register();

let provider = services.build();
let scope = provider.create_scope();
 
// All contexts are available
let trace = scope.get_required::<ScopeLocal<TraceContext>>();
let security = scope.get_required::<ScopeLocal<SecurityContext>>();
let budget = scope.get_required::<ScopeLocal<BudgetContext>>();
Source§

impl ServiceCollection

Source

pub fn add_tool_singleton<T>(&mut self, tool: T) -> &mut Self
where T: ToolCapability + Send + Sync + 'static,

Registers a service as a tool with capabilities.

This combines service registration with capability metadata registration, making the tool discoverable through the capability system.

§Examples
use ferrous_di::{ServiceCollection, ToolCapability};
use std::sync::Arc;

struct WebSearchTool {
    api_key: String,
}

impl ToolCapability for WebSearchTool {
    fn name(&self) -> &str { "web_search" }
    fn description(&self) -> &str { "Search the web for information" }
    fn version(&self) -> &str { "2.1.0" }
    fn capabilities(&self) -> Vec<&str> { vec!["web_search", "information_retrieval"] }
    fn requires(&self) -> Vec<&str> { vec!["internet_access", "api_key"] }
    fn tags(&self) -> Vec<&str> { vec!["external", "search", "web"] }
    fn estimated_cost(&self) -> Option<f64> { Some(0.001) } // $0.001 per query
    fn reliability(&self) -> Option<f64> { Some(0.95) } // 95% reliable
}

let mut services = ServiceCollection::new();
let tool = WebSearchTool {
    api_key: "secret-key".to_string(),
};
services.add_tool_singleton(tool);
Source

pub fn add_tool_trait<T>(&mut self, tool: Arc<T>) -> &mut Self
where T: ?Sized + ToolCapability + Send + Sync + 'static,

Registers a trait as a tool with capabilities.

§Examples
use ferrous_di::{ServiceCollection, ToolCapability};
use std::sync::Arc;

trait SearchTool: ToolCapability + Send + Sync {
    fn search(&self, query: &str) -> Vec<String>;
}

struct GoogleSearchTool;

impl ToolCapability for GoogleSearchTool {
    fn name(&self) -> &str { "google_search" }
    fn description(&self) -> &str { "Search using Google" }
    fn version(&self) -> &str { "1.0.0" }
    fn capabilities(&self) -> Vec<&str> { vec!["web_search"] }
    fn requires(&self) -> Vec<&str> { vec!["internet"] }
}

impl SearchTool for GoogleSearchTool {
    fn search(&self, query: &str) -> Vec<String> {
        // Implementation here
        vec![format!("Results for: {}", query)]
    }
}

let mut services = ServiceCollection::new();
let tool = Arc::new(GoogleSearchTool);
services.add_tool_trait::<dyn SearchTool>(tool);
Source§

impl ServiceCollection

Extension methods for ServiceCollection to enable validation.

Source

pub fn create_validator(&self) -> ValidationBuilder<Initial>

Creates a validation builder from this service collection.

This allows you to validate an existing service collection configuration and catch potential issues.

§Examples
use ferrous_di::ServiceCollection;

struct UserService;
struct DatabaseService;

let mut services = ServiceCollection::new();
// ... register services ...

let validation_result = services.create_validator()
    .depends_on::<UserService, DatabaseService>()
    .validate_runtime();

if !validation_result.is_valid() {
    eprintln!("DI Configuration Issues:\n{}", validation_result.format_issues());
}
Source

pub fn validate(&self) -> ValidationResult

Validates the current service collection configuration.

Source§

impl ServiceCollection

Extensions to ServiceCollection for the Options pattern.

Source

pub fn add_options<T>(&mut self) -> OptionsBuilder<T>
where T: Default + Send + Sync + 'static,

Start building Options<T>. Call .register() to finalize.

This method begins the configuration of strongly-typed options that will be available through dependency injection. The options follow an immutable snapshot model where configuration is resolved once during container setup.

§Examples
use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};

#[derive(Default)]
struct MySettings {
    enabled: bool,
    timeout: u64,
}

let mut services = ServiceCollection::new();
services.add_options::<MySettings>()
    .configure(|_resolver, settings| {
        settings.enabled = true;
        settings.timeout = 5000;
    })
    .register();

let provider = services.build();
let options = provider.get_required::<Options<MySettings>>();
let settings = options.get();
assert!(settings.enabled);
assert_eq!(settings.timeout, 5000);

Trait Implementations§

Source§

impl Default for ServiceCollection

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl ServiceCollectionExt for ServiceCollection

Source§

fn add_module<M: ServiceModule>(self, module: M) -> DiResult<Self>

Add a module to the service collection using extension method syntax. Read more
Source§

impl ServiceCollectionModuleExt for ServiceCollection

Source§

fn add_module_mut<M: ServiceModule>(&mut self, module: M) -> DiResult<&mut Self>

Add a module to the service collection in-place. Returns a DiResult to handle any registration errors.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.