pub struct OptionsBuilder<T>{ /* private fields */ }Expand description
Options builder for configuring complex options with dependencies.
The OptionsBuilder provides a fluent API for configuring options that depend on
other services from the DI container. It supports default value creation, configuration
phases, post-configuration for computed values, and validation.
This builder allows you to configure options using other services from the DI container, post-process them after all configurations are applied, and validate the final result. It follows the builder pattern popularized by Microsoft.Extensions.DependencyInjection.
§Examples
use ferrous_di::{ServiceCollection, Resolver, Options};
use std::sync::Arc;
#[derive(Default)]
struct ApiConfig {
base_url: String,
timeout_ms: u64,
api_key: Option<String>,
}
// Mock config provider
trait ConfigProvider: Send + Sync {
fn get(&self, key: &str) -> Option<String>;
}
struct EnvConfig;
impl ConfigProvider for EnvConfig {
fn get(&self, key: &str) -> Option<String> {
std::env::var(key).ok()
}
}
let mut services = ServiceCollection::new();
services.add_singleton_trait::<dyn ConfigProvider>(Arc::new(EnvConfig));
services.add_options::<ApiConfig>()
.default_with(|| ApiConfig {
base_url: "https://api.example.com".to_string(),
timeout_ms: 5000,
api_key: None,
})
.configure(|resolver, config| {
let provider = resolver.get_required_trait::<dyn ConfigProvider>();
if let Some(url) = provider.get("API_BASE_URL") {
config.base_url = url;
}
if let Some(timeout) = provider.get("API_TIMEOUT").and_then(|s| s.parse().ok()) {
config.timeout_ms = timeout;
}
config.api_key = provider.get("API_KEY");
})
.post_configure(|_resolver, config| {
// Normalize the base URL
if !config.base_url.ends_with('/') {
config.base_url.push('/');
}
})
.validate(|config| {
if config.timeout_ms == 0 {
return Err("timeout_ms must be greater than 0".to_string());
}
if config.base_url.is_empty() {
return Err("base_url cannot be empty".to_string());
}
Ok(())
})
.register();
let provider = services.build();
let options = provider.get_required::<Options<ApiConfig>>();
let api_config = options.get();
assert!(api_config.timeout_ms > 0);
assert!(api_config.base_url.ends_with('/'));Implementations§
Source§impl<T> OptionsBuilder<T>
impl<T> OptionsBuilder<T>
Sourcepub fn default_with<F>(self, f: F) -> Self
pub fn default_with<F>(self, f: F) -> Self
Provide a custom default value creator (otherwise T::default()).
This allows you to set up initial values that differ from the type’s Default implementation.
§Examples
use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
#[derive(Default)]
struct ServerConfig {
host: String,
port: u16,
}
let mut services = ServiceCollection::new();
services.add_options::<ServerConfig>()
.default_with(|| ServerConfig {
host: "0.0.0.0".to_string(),
port: 8080,
})
.register();
let provider = services.build();
let options = provider.get_required::<Options<ServerConfig>>();
let config = options.get();
assert_eq!(config.host, "0.0.0.0");
assert_eq!(config.port, 8080);Sourcepub fn configure<F>(self, f: F) -> Self
pub fn configure<F>(self, f: F) -> Self
Configure options by providing a callback that can resolve other services from the container.
Configure callbacks are executed in the order they were added. The callback receives a resolver that can be used to access other registered services.
§Examples
use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
#[derive(Default)]
struct AppConfig {
feature_enabled: bool,
}
let mut services = ServiceCollection::new();
services.add_singleton("production".to_string()); // Environment name
services.add_options::<AppConfig>()
.configure(|resolver, config| {
let env = resolver.get_required::<String>();
config.feature_enabled = env.as_str() == "production";
})
.register();
let provider = services.build();
let options = provider.get_required::<Options<AppConfig>>();
let config = options.get();
assert!(config.feature_enabled);Sourcepub fn post_configure<F>(self, f: F) -> Self
pub fn post_configure<F>(self, f: F) -> Self
Post-configure options after all configure actions have been applied.
Post-configure callbacks run after all configure callbacks and are useful for computed values, normalization, or cross-field validation and correction.
§Examples
use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
#[derive(Default)]
struct UrlConfig {
base_url: String,
api_path: String,
full_url: String, // Computed field
}
let mut services = ServiceCollection::new();
services.add_options::<UrlConfig>()
.configure(|_resolver, config| {
config.base_url = "https://api.example.com".to_string();
config.api_path = "/v1/users".to_string();
})
.post_configure(|_resolver, config| {
// Compute the full URL after base configuration
config.full_url = format!("{}{}", config.base_url, config.api_path);
})
.register();
let provider = services.build();
let options = provider.get_required::<Options<UrlConfig>>();
let config = options.get();
assert_eq!(config.full_url, "https://api.example.com/v1/users");Sourcepub fn validate<F>(self, f: F) -> Self
pub fn validate<F>(self, f: F) -> Self
Validate the final options after all configuration steps.
Validation callbacks are executed after all configure and post-configure callbacks. If any validation fails, the application will panic with a descriptive error message following the fail-fast principle for configuration errors.
§Examples
use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
#[derive(Default)]
struct DatabaseConfig {
connection_string: String,
pool_size: u32,
}
let mut services = ServiceCollection::new();
services.add_options::<DatabaseConfig>()
.configure(|_resolver, config| {
config.connection_string = "".to_string(); // Invalid!
config.pool_size = 0; // Also invalid!
})
.validate(|config| {
if config.connection_string.is_empty() {
return Err("connection_string cannot be empty".to_string());
}
if config.pool_size == 0 {
return Err("pool_size must be greater than 0".to_string());
}
Ok(())
})
.register();
// This will panic during service provider build when Options<DatabaseConfig> is first resolved
let provider = services.build();
let _options = provider.get_required::<Options<DatabaseConfig>>(); // Panics hereSourcepub fn register(self)
pub fn register(self)
Finish building and register Options<T> as a singleton in the DI container.
This method consumes the builder and registers:
Options<T>as a singleton factory that builds the configured options
To access the configured options, resolve Options<T> and call .get() to get Arc<T>.
The configuration process follows this order:
- Create initial value (default_with or T::default())
- Run all configure callbacks in order
- Run all post_configure callbacks in order
- Run all validate callbacks - panic on any failure
- Wrap in
Options<T>and register as singleton
§Panics
Panics if any validation callback returns an error. This implements fail-fast behavior for configuration issues.