1 unstable release
Uses new Rust 2024
| 0.3.0 | Jul 13, 2025 |
|---|
#463 in Configuration
Used in revoke-cli
57KB
962 lines
revoke-config
Configuration management module for the Revoke microservices framework, supporting multiple backend stores and real-time configuration watching.
Features
- Multiple Backends: Memory, file, and Consul backends, enabled via feature flags
- Format Auto-detection: Automatically detects and parses JSON, YAML, and TOML files
- Real-time Watching: Watch configuration changes through streaming updates
- Version Tracking: Built-in configuration change version tracking
- Metadata Support: Attach metadata to configuration items (creation time, update time, tags, etc.)
- Type Safety: Strongly typed configuration values using serde serialization
- Async Support: Fully async API based on Tokio
Installation
Add to your Cargo.toml:
[dependencies]
revoke-config = { version = "0.1", features = ["memory", "file", "consul"] }
Feature Flags
memory(default): In-memory configuration providerfile: File-based configuration with auto-reload supportconsul: Consul KV store integrationetcd: etcd integration (requires protoc)full: Enable all features
Usage
Memory Provider
Simple in-memory configuration storage:
use revoke_config::MemoryConfigProvider;
use revoke_core::ConfigProvider;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = MemoryConfigProvider::new();
// Set configuration value
provider.set("app.name", "my-service").await?;
// Get configuration value
let name = provider.get("app.name").await?;
println!("App name: {}", name);
Ok(())
}
With initial values:
use std::collections::HashMap;
use serde_json::json;
let mut initial = HashMap::new();
initial.insert("app.port".to_string(), json!(8080));
initial.insert("app.debug".to_string(), json!(true));
let provider = MemoryConfigProvider::with_initial_values(initial);
File Provider
File-based configuration with automatic format detection:
use revoke_config::FileConfigProvider;
use revoke_core::ConfigProvider;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Auto-detect format from file extension
let provider = FileConfigProvider::new("config.yaml").await?;
// Or specify format explicitly
let provider = FileConfigProvider::with_format(
"config.conf",
ConfigFormat::Json
).await?;
// Read configuration
let db_url = provider.get("database.url").await?;
// Update configuration (saves to file)
provider.set("database.pool_size", "10").await?;
Ok(())
}
Enable file watching (requires notify feature):
#[cfg(feature = "notify")]
{
provider.start_file_watcher().await?;
// File changes will automatically reload configuration
}
Consul Provider
Integration with Consul KV store:
use revoke_config::{ConsulConfigProvider, ConsulConfigOptions};
use revoke_core::ConfigProvider;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ConsulConfigOptions {
address: "http://localhost:8500".to_string(),
namespace: Some("myapp".to_string()),
watch_interval: Duration::from_secs(5),
..Default::default()
};
let provider = ConsulConfigProvider::new(options);
// Start background watching
provider.start_watch_loop().await;
// Use configuration
provider.set("feature.enabled", "true").await?;
let enabled = provider.get("feature.enabled").await?;
Ok(())
}
Configuration Watching
Watch for real-time configuration changes:
use futures::StreamExt;
use revoke_core::ConfigProvider;
let mut stream = provider.watch("app.feature_flags").await?;
tokio::spawn(async move {
while let Some(value) = stream.next().await {
println!("Configuration changed: {}", value);
// React to configuration changes
}
});
Configuration Formats
The file provider supports multiple formats:
JSON:
{
"app": {
"name": "my-service",
"port": 8080
}
}
YAML:
app:
name: my-service
port: 8080
TOML:
[app]
name = "my-service"
port = 8080
Advanced Usage
Custom Configuration Listener
use revoke_config::ConfigWatcher;
use std::sync::Arc;
let watcher = ConfigWatcher::new();
// Subscribe to changes
watcher.subscribe(Arc::new(|change| {
println!("Config changed: {} -> {:?}", change.key, change.new_value);
}));
// Notify changes
watcher.notify(ConfigChange {
key: "app.version".to_string(),
old_value: Some(json!("1.0.0")),
new_value: Some(json!("1.1.0")),
change_type: ChangeType::Updated,
});
Configuration Metadata
use revoke_config::{ConfigValue, ConfigMetadata};
use chrono::Utc;
let value = ConfigValue {
key: "app.name".to_string(),
value: json!("my-service"),
version: 1,
metadata: ConfigMetadata {
created_at: Utc::now(),
updated_at: Utc::now(),
created_by: Some("admin".to_string()),
description: Some("Application name".to_string()),
tags: vec!["core".to_string(), "required".to_string()],
},
};
Configuration Validation
use serde::{Deserialize, Serialize};
use revoke_config::ConfigValidator;
#[derive(Debug, Deserialize, Serialize)]
struct AppConfig {
name: String,
port: u16,
#[serde(default)]
debug: bool,
}
// Validate configuration
let json_value = provider.get("app").await?;
let config: AppConfig = serde_json::from_str(&json_value)?;
if config.port < 1024 {
return Err("Port must be >= 1024".into());
}
Best Practices
- Namespace Configuration: Use dot notation for hierarchical configuration
- Watch Granularity: Watch specific keys rather than entire configuration
- Error Handling: Always handle configuration missing or parsing errors
- Caching: Providers include built-in caching for performance
- Atomic Updates: Use transactions when updating multiple related values
Performance Considerations
- Memory Provider: O(1) lookups, suitable for frequently accessed configuration
- File Provider: Cached in memory, file I/O only on changes
- Consul Provider: Network calls with local caching, configurable refresh interval
Error Handling
All providers return Result<T, RevokeError> with specific error types:
match provider.get("app.name").await {
Ok(value) => println!("Value: {}", value),
Err(RevokeError::ConfigError(msg)) => eprintln!("Config error: {}", msg),
Err(e) => eprintln!("Other error: {}", e),
}
Security
- Sensitive Data: Use encryption for sensitive configuration values
- Access Control: Consul provider supports ACL tokens
- Audit Trail: Metadata tracks who changed configuration
- Environment Variables: Support for environment variable substitution
Examples
See the examples directory for complete examples:
memory_provider.rs- Basic memory configurationfile_provider.rs- File-based configuration with watchingconsul_provider.rs- Consul integrationconfig_validation.rs- Configuration validation patterns
Dependencies
~7–25MB
~302K SLoC