| Crates.io | rialo-modular-config |
| lib.rs | rialo-modular-config |
| version | 0.1.10 |
| created_at | 2025-11-14 16:47:43.622736+00 |
| updated_at | 2025-12-09 18:32:35.698729+00 |
| description | Rialo Modular Config |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1933139 |
| size | 179,734 |
A flexible layered configuration system for Rust applications. Build robust configuration systems with multiple sources, precedence handling, and optional JSON Schema validation.
Add to your Cargo.toml:
[dependencies]
rialo-modular-config = { version = "0.1", features = ["schema"] } # Optional schema validation
The crate uses a modular feature system to minimize dependencies and enable WASM compatibility:
| Feature | Description | Dependencies | Default |
|---|---|---|---|
file-system |
File-based configuration layers (FileLayer) | dirs, toml, serde_yaml |
โ |
environment-vars |
Environment variable layers (EnvironmentLayer) | None | โ |
schema |
JSON Schema validation support | jsonschema |
โ |
For WASM targets where file system access is limited, disable the file-system feature:
[dependencies]
rialo-modular-config = { version = "0.1", default-features = false, features = ["environment-vars"] }
For minimal WASM builds with only in-memory and CLI argument layers:
[dependencies]
rialo-modular-config = { version = "0.1", default-features = false }
file-system disabled:// โ FileLayer not available without file-system feature
// let layer = FileLayer::new("config.toml").build()?;
// โ
Use InMemoryLayer and CliArgsLayer instead
let config = LayeredConfigBuilder::new()
.with_layer(
InMemoryLayer::new(ConfigLayerSource::Default)
.with_value("setting", "default")
.build()
)
.with_layer(
CliArgsLayer::new()
.with_arg("setting", "override")
.build()
)
.build()?;
environment-vars disabled:// โ EnvironmentLayer not available without environment-vars feature
// let layer = EnvironmentLayer::new().build();
// โ
Use other layer types
let config = LayeredConfigBuilder::new()
.with_layer(default_layer)
.with_layer(file_layer)
.build()?;
use rialo_modular_config::*;
use std::collections::HashMap;
// Create layered configuration
let config = LayeredConfigBuilder::new()
// 1. Default values (lowest precedence)
.with_layer(
InMemoryLayer::new(ConfigLayerSource::Default)
.with_value("database.host", "localhost")
.with_value("database.port", 5432i64)
.with_value("debug", false)
.build()
)
// 2. User configuration file
.with_layer(
FileLayer::new("/home/user/.myapp/config.toml")
.required(false) // File may not exist
.build()?
)
// 3. Environment variables (higher precedence)
.with_layer(
EnvironmentLayer::new()
.with_prefix("MYAPP_") // Only MYAPP_* variables
.build()
)
// 4. CLI arguments (highest precedence)
.with_layer({
let mut cli_args = HashMap::new();
cli_args.insert("debug".to_string(), "true".to_string());
CliArgsLayer::new().with_args(cli_args).build()
})
.build()?;
// Access configuration with automatic type conversion
let host: String = config.get("database.host").unwrap();
let port: i64 = config.get("database.port").unwrap();
let debug: bool = config.get("debug").unwrap(); // true from CLI override
println!("Connecting to {}:{} (debug: {})", host, port, debug);
Load configuration from TOML, JSON, or YAML files:
// Auto-detect format from extension
let layer = FileLayer::new("config.toml").build()?;
// Explicit format
let layer = FileLayer::new("config.conf")
.with_format(FileFormat::Toml)
.required(false) // Optional file
.build()?;
// Different source types
let user_config = FileLayer::new("/home/user/.myapp.toml")
.with_source(ConfigLayerSource::User(path.clone()))
.build()?;
let project_config = FileLayer::new("./.myapp-config.toml")
.with_source(ConfigLayerSource::Project(path.clone()))
.build()?;
Extract configuration from environment variables:
// All environment variables
let layer = EnvironmentLayer::new().build();
// Prefix filtering with key transformation
let layer = EnvironmentLayer::new()
.with_prefix("MYAPP_") // Only MYAPP_* variables
.transform_keys(true) // MYAPP_DB_HOST -> database.host
.build();
// Raw keys without transformation
let layer = EnvironmentLayer::new()
.with_prefix("APP_")
.transform_keys(false) // Keep original key names
.build();
Programmatic configuration for defaults and testing:
let layer = InMemoryLayer::new(ConfigLayerSource::Default)
.with_value("api.timeout", 30000)
.with_value("api.retries", 3)
.with_value("features.auth", true)
.build();
// Bulk loading
let mut values = HashMap::new();
values.insert("key1".to_string(), "value1".to_string());
let layer = InMemoryLayer::new(ConfigLayerSource::Default)
.with_values(values)
.build();
Command-line argument overrides:
let mut args = HashMap::new();
args.insert("verbose".to_string(), "true".to_string());
args.insert("output.format".to_string(), "json".to_string());
let layer = CliArgsLayer::new()
.with_args(args)
.build();
// Single arguments
let layer = CliArgsLayer::new()
.with_arg("debug", "true")
.with_arg("port", "8080")
.build();
Layers are resolved with increasing precedence:
~/.config/app/).appconfig)dev/staging/prod)APP_*)let config = LayeredConfigBuilder::new()
.with_layer(
InMemoryLayer::new(ConfigLayerSource::Default)
.with_weight(1) // Low priority
.with_value("setting", "default")
.build()
)
.with_layer(
FileLayer::new("important.toml")
.with_weight(100) // High priority
.build()?
)
.build()?;
Prevent sensitive configuration leakage with final layers:
// Production user configuration
let user_layer = FileLayer::new("/home/user/.myapp.toml")
.with_source(ConfigLayerSource::User(user_path.clone()))
.required(false)
.build()?;
// Test environment isolation
let test_layer = InMemoryLayer::new(
ConfigLayerSource::ActiveEnvironment("/tmp/test-env".into())
)
.with_value("final", true) // ๐ Block inheritance
.with_value("database.url", "test-db")
.with_value("debug", true)
.build();
let config = LayeredConfigBuilder::new()
.with_layer(user_layer) // Contains production secrets
.with_layer(test_layer) // Final layer blocks access
.build()?;
// โ
Only test layer values accessible
let db_url: String = config.get("database.url").unwrap(); // "test-db"
let debug: bool = config.get("debug").unwrap(); // true
// โ Production secrets are blocked
let api_key: Option<String> = config.get("api_key"); // None
Enable type-safe configuration with JSON Schema validation:
[dependencies]
rialo-modular-config = { version = "0.1", features = ["schema"] }
use serde_json::json;
let schema = json!({
"type": "object",
"properties": {
"database": {
"type": "object",
"properties": {
"host": { "type": "string" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
},
"required": ["host", "port"]
},
"debug": { "type": "boolean" }
},
"required": ["database"]
});
let config = LayeredConfigBuilder::new()
.with_layer(default_layer)
.with_layer(user_layer)
.with_schema(schema) // ๐ก๏ธ Automatic validation
.build()?;
// Manual validation anytime
config.validate()?;
Schema validation provides detailed debugging information:
Schema validation failed for resolved configuration:
- /database/port: 999999 is greater than the maximum of 65535
Contributing layers: User("/home/user/.myapp.toml")
- <root>: "api_key" is a required property
Contributing layers: Default, User("/home/user/.myapp.toml"), CommandLineArgs
Key Benefits:
// Type-safe access with automatic conversion
let timeout: u64 = config.get("api.timeout").unwrap_or(30);
let enabled: bool = config.get("features.auth").unwrap_or(false);
let hosts: Vec<String> = config.get("database.replicas").unwrap_or_default();
// Optional values
let optional: Option<String> = config.get("optional.setting");
// Get all resolved values
let all_config = config.get_resolved_values();
for (key, value) in all_config {
println!("{} = {:?}", key, value);
}
// Save to highest precedence writable layer
config.set("new_setting", "value")?;
// Save to specific layer type
let user_layer = config.find_user_layer().unwrap();
config.set_in_layer("user_preference", true, user_layer)?;
let project_layer = config.find_project_layer().unwrap();
config.set_in_layer("project_setting", "local", project_layer)?;
// Layer discovery
let writable_layers = config.writable_layers();
for layer in writable_layers {
println!("Can write to: {:?}", layer.source());
}
#[derive(Debug, Clone)]
enum MyConfigSource {
Database(String),
Api(String),
Cache,
}
let layer = InMemoryLayer::new(ConfigLayerSource::Custom("database".to_string()))
.with_values(load_from_database()?)
.build();
// Debug resolution path for a specific key
let path = config.resolver.debug_resolution_path("database.host");
for (source, value, precedence) in path {
println!("Source: {:?}, Value: {:?}, Weight: {}", source, value, precedence);
}
// Inspect layers
for layer in config.layers() {
println!("Layer: {:?}", layer.source());
println!("Writable: {}", layer.is_writable());
println!("Values: {:?}", layer.values());
}
#[cfg(debug_assertions)]
let debug_layer = InMemoryLayer::new(ConfigLayerSource::Default)
.with_value("log.level", "debug")
.with_value("dev_mode", true)
.build();
#[cfg(not(debug_assertions))]
let debug_layer = InMemoryLayer::new(ConfigLayerSource::Default)
.with_value("log.level", "info")
.with_value("dev_mode", false)
.build();
let config = LayeredConfigBuilder::new()
.with_layer(debug_layer)
.with_layer(file_layer)
.build()?;
// Recommended layer structure
LayeredConfigBuilder::new()
.with_layer(create_defaults()) // 1. Sensible defaults
.with_layer(load_user_config()?) // 2. User preferences
.with_layer(load_project_config()?) // 3. Project settings
.with_layer(load_env_config()?) // 4. Environment-specific
.with_layer(parse_cli_args()) // 5. Runtime overrides
.build()?
// Use final layers for environment isolation
let test_config = test_layer
.with_value("final", true) // Block production data
.with_value("database.url", "test-db")
.build();
// Validate schemas in development
#[cfg(debug_assertions)]
let config = builder.with_schema(load_schema()).build()?;
#[cfg(not(debug_assertions))]
let config = builder.build()?; // Skip validation in production
let config = LayeredConfigBuilder::new()
.with_layer(create_defaults())
.with_layer(
FileLayer::new("config.toml")
.required(false) // Gracefully handle missing files
.build()?
)
.build()
.context("Failed to build configuration")?;
// Validate early and provide helpful errors
if let Err(e) = config.validate() {
eprintln!("Configuration validation failed:\n{}", e);
process::exit(1);
}
#[cfg(test)]
fn create_test_config() -> LayeredConfig {
LayeredConfigBuilder::new()
.with_layer(
InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment("/tmp/test".into()))
.with_value("final", true) // Isolate test environment
.with_value("database.url", "sqlite::memory:")
.with_value("log.level", "debug")
.build()
)
.build()
.unwrap()
}
Licensed under the Apache License, Version 2.0. See LICENSE for details.