# 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`:
```toml
[dependencies]
revoke-config = { version = "0.1", features = ["memory", "file", "consul"] }
```
## Feature Flags
- `memory` (default): In-memory configuration provider
- `file`: File-based configuration with auto-reload support
- `consul`: Consul KV store integration
- `etcd`: etcd integration (requires protoc)
- `full`: Enable all features
## Usage
### Memory Provider
Simple in-memory configuration storage:
```rust
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:
```rust
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:
```rust
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):
```rust
#[cfg(feature = "notify")]
{
provider.start_file_watcher().await?;
// File changes will automatically reload configuration
}
```
### Consul Provider
Integration with Consul KV store:
```rust
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:
```rust
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:**
```json
{
"app": {
"name": "my-service",
"port": 8080
}
}
```
**YAML:**
```yaml
app:
name: my-service
port: 8080
```
**TOML:**
```toml
[app]
name = "my-service"
port = 8080
```
## Advanced Usage
### Custom Configuration Listener
```rust
use revoke_config::ConfigWatcher;
use std::sync::Arc;
let watcher = ConfigWatcher::new();
// Subscribe to changes
}));
// 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
```rust
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
```rust
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
1. **Namespace Configuration**: Use dot notation for hierarchical configuration
2. **Watch Granularity**: Watch specific keys rather than entire configuration
3. **Error Handling**: Always handle configuration missing or parsing errors
4. **Caching**: Providers include built-in caching for performance
5. **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:
```rust
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
1. **Sensitive Data**: Use encryption for sensitive configuration values
2. **Access Control**: Consul provider supports ACL tokens
3. **Audit Trail**: Metadata tracks who changed configuration
4. **Environment Variables**: Support for environment variable substitution
## Examples
See the [examples](examples/) directory for complete examples:
- `memory_provider.rs` - Basic memory configuration
- `file_provider.rs` - File-based configuration with watching
- `consul_provider.rs` - Consul integration
- `config_validation.rs` - Configuration validation patterns