Trail Config
Simple Rust library to help with reading (and formatting) values from config files. Supports YAML format (uses serde_yaml_bw library).
Features
- 📖 Simple path-based config value access
- 🔧 Customizable path separators (
/,::, etc.) - 🌍 Environment-specific config files
- 📝 String formatting and interpolation
- ✅ Comprehensive error handling with custom
ConfigErrortype - 📋 Type conversion for strings, numbers, booleans, and sequences
- 🔐 Escape sequence support for keys containing separators
- 🔄 Hot reload support for detecting configuration changes at runtime
Quick Start
use Config;
// Load config.yaml file
let config = default;
// Get values with lenient API (returns empty/None on missing)
let port = config.str; // -> "8080"
let timeout = config.get_int; // -> Some(30)
// Or use strict API for explicit error handling
match config.str_strict
Loading Configuration
Trail Config provides different loading strategies for different use cases:
Production Code (Strict)
Use Config::load_required() when the configuration file must exist:
use Config;
let config = load_required?;
// Will error if file is missing, invalid YAML, or permission denied
Testing/Optional Configs (Lenient)
Use Config::default() when missing config is acceptable:
let config = default; // Never panics, gracefully handles missing config.yaml
Custom Loading
// With custom separator
let config = new?;
// With environment substitution
let config = new?; // Loads config.dev.yaml
// From YAML string
let config = load_yaml?;
API Overview
Trail Config organizes methods into two API styles:
| Goal | Method Style | Returns |
|---|---|---|
| Lenient access (handles missing gracefully) | get(), str(), list(), etc. |
Option<T> or empty defaults |
| Strict access (explicit error handling) | get_strict(), str_strict(), etc. |
Result<T, ConfigError> |
Both styles share the same path syntax and navigate nested YAML using separators (default: /).
Main Methods (Lenient API)
Lenient methods return None or empty values for missing paths or type mismatches.
Reading Values
get(path)→Option<Value>- Get rawserde_yaml::Valuestr(path)→String- Get string representation (empty if missing)list(path)→Vec<String>- Get sequence as vector (empty if missing)contains(path)→bool- Check if path exists
Type Conversion
get_int(path)→Option<i64>- Get integer valueget_float(path)→Option<f64>- Get floating-point valueget_bool(path)→Option<bool>- Get boolean value
Formatting
fmt(format, path)→String- Format multiple values (empty on any error)
Configuration Metadata
get_filename()→&str- Get loaded config filenameenvironment()→Option<&str>- Get environment name (if used)
Hot Reload
reload()→Result<(), ConfigError>- Reload from current filereload_from(filename)→Result<(), ConfigError>- Load from different file
Strict Methods (Error Handling API)
Strict methods return Result<T, ConfigError> for explicit error handling.
get_strict(path)- Get value, fails withPathNotFoundif missingstr_strict(path)- Get string, fails withPathNotFoundif missinglist_strict(path)- Get sequence, fails withPathNotFoundif missingfmt_strict(format, path)- Format values, fails withPathNotFoundorFormatErrorget_int_strict(path)- Get integer, fails withPathNotFoundorFormatErroron type mismatchget_float_strict(path)- Get float, fails withPathNotFoundorFormatErroron type mismatchget_bool_strict(path)- Get boolean, fails withPathNotFoundorFormatErroron type mismatch
Type Conversion
Convert config values to typed Rust values safely:
let config = default;
// Lenient - returns None on missing or type mismatch
let port = config.get_int;
let timeout = config.get_float;
let debug = config.get_bool;
if let Some = port
// Strict - returns error details
match config.get_int_strict
Example config (YAML):
app:
port: 8080
timeout: 30.5
debug: true
Error Handling
Trail Config uses a custom ConfigError enum for precise error handling:
Error Types
use ConfigError;
// Four error variants:
// - IoError(io::Error) - File I/O errors (missing file, permission denied, etc.)
// - YamlError(String) - YAML parsing errors
// - PathNotFound(String) - Configuration path not found in document
// - FormatError(String) - String formatting or configuration errors
Basic Error Handling
use ;
match load_required
Strict Method Error Handling
use ;
let config = default;
match config.str_strict
// Type conversion with error details
match config.get_int_strict
Hot Reload
Detect and apply configuration changes at runtime without restarting:
let mut config = load_required?;
// Reload from the same file
config.reload?; // Updates content from disk
// Or switch to a different config file
config.reload_from?;
Server Loop Example
use Config;
use thread;
use Duration;
Escape Sequences
Keys containing the path separator can be accessed using escape sequences.
Syntax
\/- Include literal separator in the key\\- Include literal backslash in the key- Works with any separator:
/,::,->, etc.
Example
Given this YAML with special characters in keys:
database:
"host/port": localhost:5432 # Key contains /
"user\name": admin\user # Key contains \
Access using escape sequences:
let config = load_yaml.unwrap;
// Access key containing separator (/)
let value = config.str; // -> "localhost:5432"
// Access key containing backslash (\)
let value = config.str; // -> "admin\user"
With custom separator:
let config = load_yaml.unwrap;
// Path: a::b\::c::d navigates to keys ["a", "b::c", "d"]
let value = config.str;
Input Validation
Trail Config validates inputs automatically and returns FormatError for invalid configurations:
| Input | Constraint | Error |
|---|---|---|
| Path Separator | Cannot be empty | Returns FormatError |
| File Paths | Empty filename treated as no file | Returns IoError |
| Paths | Empty paths safely handled | Returns None or empty |
| Separators (leading/trailing) | Handled gracefully | No error |
| Filename Templates | Must be valid format strings | Returns FormatError |
Examples:
// Empty separator - error
let result = new;
assert!; // FormatError
// Missing file with load_required - error
let result = load_required;
assert!; // IoError
// Missing file with default - ok, empty config
let config = default;
assert!; // Graceful fallback
Real-World Examples
Web Server Configuration
use Config;
let config = load_required?;
let host = config.str;
let port = config.get_int_strict?;
let ssl = config.get_bool.unwrap_or;
let workers = config.get_int.unwrap_or;
println!;
Environment-Specific Configuration
use Config;
use env;
let env = var.unwrap_or_else;
let config = load_required?;
let db_url = config.str_strict?;
let log_level = config.str;
println!;
Database Connection Pooling
use Config;
let config = default;
let db_config = DatabaseConfig ;
let pool = create_pool?;
Sample YAML:
db:
host: localhost
port: 5432
username: admin
password: secret
pool_size: 20
timeout: 60.0
Feature Flags and Feature Detection
use Config;
let config = default;
if config.get_bool.unwrap_or
if config.get_bool.unwrap_or
let beta_features = config.list;
for feature in beta_features
Sample Configuration File
app:
name: MyApp
port: 8080
timeout: 30.5
debug: false
database:
host: localhost
port: 5432
name: myapp_db
username: admin
password: secret
pool_size: 10
server:
bind: 127.0.0.1
workers: 4
log_level: info
features:
analytics: true
profiling: false
beta:
- new_ui
- advanced_search
License
This project is licensed under the MIT License - see the LICENSE file for details