Expand description
§config_hierarchy
A robust, production-ready hierarchical configuration management system for Rust applications featuring a 6-level priority resolution system, automatic source tracking, and cross-platform file operations with atomic writes.
§Overview
config_hierarchy provides a flexible framework for managing application configuration across multiple sources with clear precedence rules. It eliminates configuration ambiguity by tracking the exact source of every configuration value and supports multiple storage formats and output representations.
Key Capabilities:
- Priority-based resolution across 6 configuration layers
- Source tracking for every configuration value
- Atomic file operations with cross-platform file locking
- Multiple formats for input (YAML/JSON/TOML) and output (table/JSON/YAML)
- Type detection with intelligent string-to-type conversion
- Trait-based architecture for application-specific customization
§Design Principles
This crate focuses exclusively on configuration management concerns:
Core Responsibilities:
- Hierarchical configuration resolution and merging
- Configuration persistence and retrieval
- Source tracking and validation
- Format conversion and display
Intentional Non-Goals:
- Application-specific configuration logic (delegated to consuming applications)
- CLI argument parsing (handled by application layer)
- External tool integration (managed by specialized crates)
- Domain-specific validation rules (implemented via traits by consumers)
§Installation
Add config_hierarchy to your project’s Cargo.toml:
[dependencies]
config_hierarchy = { version = "0.2", features = ["full"] }§Feature Flags
The crate provides granular feature flags for selective capability inclusion:
| Feature | Description | Includes |
|---|---|---|
default | Core resolution engine | Configuration hierarchy, source tracking, type detection |
file_ops | File persistence layer | YAML I/O, atomic writes, file locking, path discovery |
display_table | Table output formatter | Tabular configuration display with tree formatting |
display_json | JSON output formatter | JSON serialization for configuration data |
display_yaml | YAML output formatter | YAML serialization for configuration data |
full | All features enabled | Complete functionality for production use |
Recommendation: Use full for applications unless binary size is a critical constraint.
§Quick Start
Create a configuration manager for your application by implementing three core traits:
use config_hierarchy::{ ConfigManager, ConfigDefaults, ConfigPaths, ConfigValidator };
use std::collections::HashMap;
use serde_json::Value as JsonValue;
// Define default configuration values
struct AppDefaults;
impl ConfigDefaults for AppDefaults
{
fn get_defaults() -> HashMap< String, JsonValue >
{
let mut map = HashMap::new();
map.insert( "timeout".into(), JsonValue::Number( 30.into() ) );
map.insert( "retries".into(), JsonValue::Number( 3.into() ) );
map
}
fn get_parameter_names() -> Vec< &'static str >
{
vec![ "timeout", "retries" ]
}
}
// Define configuration file paths (automatically derives all paths from app name)
struct AppPaths;
impl ConfigPaths for AppPaths
{
fn app_name() -> &'static str { "myapp" }
}
// Define validation rules
struct AppValidator;
impl ConfigValidator for AppValidator
{
fn validate_parameter( _name : &str, _value : &JsonValue )
-> Result< (), config_hierarchy::ValidationError >
{
Ok( () )
}
fn validate_all( _config : &HashMap< String, ( JsonValue, config_hierarchy::ConfigSource ) > )
-> Vec< config_hierarchy::ValidationError >
{
Vec::new()
}
}
// Compose the configuration manager from the three traits
type AppConfig = ConfigManager< AppDefaults, AppPaths, AppValidator >;
fn main()
{
// Resolve configuration from all sources
let runtime_overrides = HashMap::new();
let config = AppConfig::resolve_all_config( &runtime_overrides );
// Display resolved configuration with source tracking
for ( key, ( value, source ) ) in &config
{
println!( "{key}: {value:?} (from {source:?})" );
}
}Output Example:
timeout: Number(30) (from Default)
retries: Number(3) (from Default)If environment variable MYAPP_TIMEOUT=60 is set:
timeout: Number(60) (from Environment)
retries: Number(3) (from Default)§Configuration Hierarchy
The crate implements a 6-level priority system where higher priority sources override lower priority ones. Configuration values are resolved by searching through layers in order until a value is found:
§Priority Levels (Highest to Lowest)
1. Runtime Parameters
- Explicitly provided at program execution
- Passed directly to
resolve_all_config() - Use case: Command-line overrides, testing, dynamic configuration
2. Environment Variables
- Format:
{APP_NAME}_{PARAMETER}(uppercase with underscores) - Example:
MYAPP_TIMEOUT=60 - Use case: Container deployments, CI/CD pipelines, system-level configuration
3. Local Configuration (Current Directory)
- Temporary:
-./.{app_name}/config.yaml(gitignored) - Permanent:
./.{app_name}/config.yaml(tracked in VCS) - Temporary configs override permanent when both exist
- Use case: Project-specific settings, developer preferences
4. Local Configuration (Parent Directories)
- Searches ancestor directories from current to root
- Nearest configuration takes precedence
- Use case: Workspace-wide settings, monorepo configuration
5. Global Configuration
- Location:
$PRO/.persistent/.{app_name}/config.yaml - Requires
PROenvironment variable - Use case: User-wide defaults, machine-specific settings
6. Default Values
- Defined in
ConfigDefaultstrait implementation - Always available as final fallback
- Use case: Application baseline configuration
§Source Tracking
Every resolved value includes its source, enabling debugging and auditing:
for ( key, ( value, source ) ) in &config
{
match source
{
ConfigSource::Runtime => println!( "{key} overridden at runtime" ),
ConfigSource::Environment => println!( "{key} from environment" ),
ConfigSource::LocalCurrent( path ) => println!( "{key} from local config: {}", path.display() ),
ConfigSource::LocalParent( path ) => println!( "{key} from parent config: {}", path.display() ),
ConfigSource::Global( path ) => println!( "{key} from global config: {}", path.display() ),
ConfigSource::Default => println!( "{key} using default" ),
}
}§Path Conventions
All configuration paths follow a consistent naming scheme derived from the application name:
§Path Patterns
| Scope | Type | Pattern | Git Tracking |
|---|---|---|---|
| Local | Temporary | -./.{app_name}/config.yaml | Ignored (hyphen prefix) |
| Local | Permanent | ./.{app_name}/config.yaml | Tracked (dot prefix) |
| Global | Persistent | $PRO/.persistent/.{app_name}/config.yaml | N/A |
§Dual Local Configuration Pattern
The crate supports both temporary and permanent local configurations:
- Temporary configs (
-./) are developer-specific and gitignored - Permanent configs (
./) are project-specific and version-controlled - When both exist, temporary takes precedence
- Use temporary for local experiments, permanent for team defaults
§Automatic Path Derivation
All paths are automatically derived from ConfigPaths::app_name():
struct MyApp;
impl ConfigPaths for MyApp
{
fn app_name() -> &'static str { "myapp" }
}
// Automatically generates:
// - Local: ./.myapp/config.yaml or -./.myapp/config.yaml
// - Global: $PRO/.persistent/.myapp/config.yamlSecurity Requirements:
The app_name() method is validated at runtime to prevent security vulnerabilities:
- Must not be empty (prevents invalid paths like
./config.yaml) - Must not contain path separators
/or\(prevents directory traversal) - Must not contain parent directory references
..(prevents path traversal attacks)
Recommended: Use alphanumeric characters, hyphens, underscores (my-app, my_app123). Unicode is supported but whitespace should be avoided.
§File Operations
§Atomic Writes with File Locking
When the file_ops feature is enabled, all file operations use atomic writes with cross-platform file locking:
// Safely modify configuration with exclusive lock
atomic_config_modify( Path::new( "./.myapp/config.yaml" ), | config |
{
config.insert( "timeout".into(), JsonValue::Number( 60.into() ) );
Ok( () )
} )?;Concurrency Guarantees:
- Exclusive file locking prevents concurrent write conflicts
- Uses
fs2crate for cross-platform advisory locks - Works on Linux, macOS, and Windows
- Preserves metadata (created_at timestamp) during updates
§Metadata Preservation
Configuration files include automatic metadata tracking:
metadata:
version: "1.0"
created_at: "2025-01-15T10:30:00Z"
last_modified: "2025-01-15T14:45:00Z"
parameters:
timeout: 60
retries: 3§Advanced Usage
§Multiple Output Formats
Display configuration in different formats for different use cases:
let config = AppConfig::resolve_all_config( &HashMap::new() );
let options = DisplayOptions::default();
// Table format for CLI display
println!( "{}", format_config_table::< AppDefaults, AppPaths >( &config, &[], &options ) );
// JSON for programmatic consumption
println!( "{}", format_config_json( &config, &[], &options ) );
// YAML for file export
println!( "{}", format_config_yaml( &config, &[], &options ) );§Type Detection
Configuration values are automatically typed from strings:
"123"→Number(123)"true"/"false"→Bool(true/false)"3.14"→Number(3.14)"hello"→String("hello")
Override with environment variables:
MYAPP_TIMEOUT=60 # Becomes Number(60)
MYAPP_ENABLED=true # Becomes Bool(true)
MYAPP_NAME=production # Becomes String("production")§Path Customization
The ConfigPaths trait provides 14 optional methods for customizing paths and naming conventions. Override any method to change defaults:
use config_hierarchy::{ ConfigPaths, EnvVarCasing };
struct CustomPaths;
impl ConfigPaths for CustomPaths
{
fn app_name() -> &'static str { "myapp" }
// Customize local paths
fn local_permanent_prefix() -> &'static str { "_" }
fn local_config_filename() -> &'static str { "settings.toml" }
// Customize environment variables
fn env_var_prefix() -> &'static str { "MY_CUSTOM_APP" }
fn env_var_separator() -> &'static str { "__" }
fn env_var_casing() -> EnvVarCasing { EnvVarCasing::LowerCase }
// Customize global paths
fn global_persistent_dir() -> &'static str { ".config" }
fn global_config_filename() -> &'static str { "app.yaml" }
}Results:
- Local config:
./_myapp/settings.toml - Global config:
$PRO/.config/.myapp/app.yaml - Environment variables:
my_custom_app__timeout,my_custom_app__debug
Available Customization Methods:
| Category | Methods | Default |
|---|---|---|
| Environment Variables | env_var_prefix() | app_name().to_uppercase() |
env_var_separator() | "_" | |
env_var_casing() | EnvVarCasing::UpperCase | |
| Local Paths | local_permanent_prefix() | "." |
local_temporary_prefix() | "-" | |
local_config_filename() | "config.yaml" | |
| Global Paths | global_persistent_dir() | ".persistent" |
global_config_filename() | "config.yaml" | |
| Environment Names | pro_env_var(), home_env_var() | "PRO", "HOME" |
xdg_config_home_var(), appdata_var() | Platform-specific | |
| OS-Specific Bases | linux_config_base() | ".config" |
macos_config_base() | "Library/Application Support" |
See ConfigPaths trait documentation for complete details.
§Testing
Run the complete test suite:
# Unit and integration tests
cargo nextest run --all-features
# Documentation tests
cargo test --doc --all-features
# Clippy lints
cargo clippy --all-targets --all-featuresTest coverage includes:
- Hierarchical resolution across all 6 layers
- Concurrent file access with locking
- Cross-platform path handling
- Type detection and conversion
- Display formatters
§License
MIT Generic hierarchical configuration with 6-level priority and source tracking
Implement 3 traits then use ConfigManager for complete config handling
Structs§
- Config
Manager - Generic configuration manager with hierarchical resolution
- Validation
Error - Configuration validation error
Enums§
- Config
Source - Configuration source tracking where values come from in the hierarchy
- EnvVar
Casing - Environment variable casing strategy
Traits§
- Config
Defaults - Provides application-specific default configuration values
- Config
Paths - Provides application-specific path configuration
- Config
Validator - Provides application-specific validation logic
Functions§
- atomic_
config_ modify - Atomically modify config file with file locking
- delete_
config_ file - Delete configuration file
- detect_
and_ convert_ value - Intelligently detect value type and convert to appropriate JSON representation
- discover_
local_ configs - Discover all local config files from current directory up to root Returns paths in priority order (current dir first, root last)
- get_
global_ config_ dir - Get OS-specific global config directory
Priority:
$PRO/.persistent/.{app_name}> OS-specific config dir - get_
global_ config_ path - Get global config file path
- get_
local_ config_ path - Get local config file path in current directory
- json_
to_ yaml - Convert JSON value to YAML value
- json_
value_ to_ display_ string - Extract clean string value from
JsonValue - load_
config_ file - Load config from a single YAML file
- resolve_
config_ value - Resolve configuration value using full 6-level hierarchy Returns (value, source) tuple
- save_
config_ file - Save configuration to file with metadata
- yaml_
to_ json - Convert YAML value to JSON value