revoke-config

Crates.iorevoke-config
lib.rsrevoke-config
version0.3.0
created_at2025-07-13 06:01:53.436744+00
updated_at2025-07-13 06:01:53.436744+00
descriptionConfiguration management with hot-reloading for Revoke framework
homepage
repositoryhttps://github.com/revoke/revoke
max_upload_size
id1750020
size116,074
LioRael (LioRael)

documentation

README

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 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:

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

  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:

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 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
Commit count: 0

cargo fmt