pub struct ServiceCollection { /* private fields */ }Implementations§
Source§impl ServiceCollection
impl ServiceCollection
Sourcepub fn add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> &mut Self
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()
});Sourcepub fn add_singleton_factory<T, F>(&mut self, factory: F) -> &mut Self
pub fn add_singleton_factory<T, F>(&mut self, factory: F) -> &mut Self
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>()
}
});Sourcepub fn add_scoped_factory<T, F>(&mut self, factory: F) -> &mut Self
pub fn add_scoped_factory<T, F>(&mut self, factory: F) -> &mut Self
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>()
}
});Sourcepub fn add_transient_factory<T, F>(&mut self, factory: F) -> &mut Self
pub fn add_transient_factory<T, F>(&mut self, factory: F) -> &mut Self
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() }
});Sourcepub fn add_singleton_trait<T>(&mut self, value: Arc<T>) -> &mut Self
pub fn add_singleton_trait<T>(&mut self, value: Arc<T>) -> &mut Self
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);Sourcepub fn add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
pub fn add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
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() })
});Sourcepub fn add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
pub fn add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
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()
})
});Sourcepub fn add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
pub fn add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
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)
});Sourcepub fn add_trait_implementation<T>(
&mut self,
value: Arc<T>,
lifetime: Lifetime,
) -> &mut Self
pub fn add_trait_implementation<T>( &mut self, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
Add trait implementation to multi-binding list
Sourcepub fn add_trait_factory<Trait, F>(
&mut self,
lifetime: Lifetime,
factory: F,
) -> &mut Self
pub fn add_trait_factory<Trait, F>( &mut self, lifetime: Lifetime, factory: F, ) -> &mut Self
Add trait factory to multi-binding list
Sourcepub fn get_service_descriptors(&self) -> Vec<ServiceDescriptor>
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);Sourcepub fn add_with_metadata<T, M>(
&mut self,
value: T,
lifetime: Lifetime,
metadata: M,
) -> &mut Self
pub fn add_with_metadata<T, M>( &mut self, value: T, lifetime: Lifetime, metadata: M, ) -> &mut Self
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(),
}
);Sourcepub fn get_metadata<M: 'static>(&self, key: &Key) -> Option<&M>
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());Sourcepub fn try_add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> bool
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 ignoredSourcepub fn try_add_singleton_factory<T, F>(&mut self, factory: F) -> bool
pub fn try_add_singleton_factory<T, F>(&mut self, factory: F) -> bool
Register a singleton factory if not already registered.
Sourcepub fn try_add_scoped_factory<T, F>(&mut self, factory: F) -> bool
pub fn try_add_scoped_factory<T, F>(&mut self, factory: F) -> bool
Register a scoped factory if not already registered.
Sourcepub fn try_add_transient_factory<T, F>(&mut self, factory: F) -> bool
pub fn try_add_transient_factory<T, F>(&mut self, factory: F) -> bool
Register a transient factory if not already registered.
Sourcepub fn try_add_singleton_trait<T>(&mut self, value: Arc<T>) -> bool
pub fn try_add_singleton_trait<T>(&mut self, value: Arc<T>) -> bool
Register a singleton trait if not already registered.
Sourcepub fn try_add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> bool
pub fn try_add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> bool
Register a singleton trait factory if not already registered.
Sourcepub fn try_add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> bool
pub fn try_add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> bool
Register a scoped trait factory if not already registered.
Sourcepub fn try_add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> bool
pub fn try_add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> bool
Register a transient trait factory if not already registered.
Sourcepub fn try_add_enumerable<T>(
&mut self,
value: Arc<T>,
lifetime: Lifetime,
) -> &mut Self
pub fn try_add_enumerable<T>( &mut self, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
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.
Sourcepub fn add_named_singleton<T: 'static + Send + Sync>(
&mut self,
name: &'static str,
value: T,
) -> &mut Self
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 nameSourcepub fn add_named_singleton_factory<T, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_singleton_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named singleton factory.
Sourcepub fn add_named_scoped_factory<T, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_scoped_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named scoped factory.
Sourcepub fn add_named_transient_factory<T, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_transient_factory<T, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named transient factory.
Sourcepub fn add_named_singleton_trait<T>(
&mut self,
name: &'static str,
value: Arc<T>,
) -> &mut Self
pub fn add_named_singleton_trait<T>( &mut self, name: &'static str, value: Arc<T>, ) -> &mut Self
Register a named singleton trait.
Sourcepub fn add_named_singleton_trait_factory<Trait, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_singleton_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named singleton trait factory.
Sourcepub fn add_named_scoped_trait_factory<Trait, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_scoped_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named scoped trait factory.
Sourcepub fn add_named_transient_trait_factory<Trait, F>(
&mut self,
name: &'static str,
factory: F,
) -> &mut Self
pub fn add_named_transient_trait_factory<Trait, F>( &mut self, name: &'static str, factory: F, ) -> &mut Self
Register a named transient trait factory.
Sourcepub fn add_named_trait_implementation<T>(
&mut self,
name: &'static str,
value: Arc<T>,
lifetime: Lifetime,
) -> &mut Self
pub fn add_named_trait_implementation<T>( &mut self, name: &'static str, value: Arc<T>, lifetime: Lifetime, ) -> &mut Self
Add named multi-trait registration.
Sourcepub fn add_observer(&mut self, observer: Arc<dyn DiObserver>) -> &mut Self
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 observedSourcepub fn decorate_trait<T, F>(&mut self, decorator: F) -> &mut Self
pub fn decorate_trait<T, F>(&mut self, decorator: F) -> &mut Self
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 anArc<T>and returns a wrappedArc<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"Sourcepub fn decorate_with<T, D>(&mut self, decorator: D) -> &mut Self
pub fn decorate_with<T, D>(&mut self, decorator: D) -> &mut Self
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"Sourcepub fn decorate_trait_with<T, D>(&mut self, decorator: D) -> &mut Self
pub fn decorate_trait_with<T, D>(&mut self, decorator: D) -> &mut Self
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"Sourcepub fn prewarm<T: 'static + Send + Sync>(&mut self) -> &mut Self
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 DatabaseServiceSourcepub fn prewarm_trait<T: ?Sized + 'static + Send + Sync>(&mut self) -> &mut Self
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 servicesSourcepub fn build(self) -> ServiceProvider
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
impl ServiceCollection
Sourcepub fn add_scope_local<T, F>(&mut self, factory: F) -> &mut Self
pub fn add_scope_local<T, F>(&mut self, factory: F) -> &mut Self
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))
});Sourcepub fn add_workflow_context<T, F>(&mut self, factory: F) -> &mut Self
pub fn add_workflow_context<T, F>(&mut self, factory: F) -> &mut Self
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>();Sourcepub fn add_scope_locals(&mut self) -> ScopeLocalBuilder<'_>
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
impl ServiceCollection
Sourcepub fn add_tool_singleton<T>(&mut self, tool: T) -> &mut Self
pub fn add_tool_singleton<T>(&mut self, tool: T) -> &mut Self
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);Sourcepub fn add_tool_trait<T>(&mut self, tool: Arc<T>) -> &mut Self
pub fn add_tool_trait<T>(&mut self, tool: Arc<T>) -> &mut Self
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.
impl ServiceCollection
Extension methods for ServiceCollection to enable validation.
Sourcepub fn create_validator(&self) -> ValidationBuilder<Initial>
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());
}Sourcepub fn validate(&self) -> ValidationResult
pub fn validate(&self) -> ValidationResult
Validates the current service collection configuration.
Source§impl ServiceCollection
Extensions to ServiceCollection for the Options pattern.
impl ServiceCollection
Extensions to ServiceCollection for the Options pattern.
Sourcepub fn add_options<T>(&mut self) -> OptionsBuilder<T>
pub fn add_options<T>(&mut self) -> OptionsBuilder<T>
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);