distributed_config/sources/
mod.rs

1//! Configuration sources for loading configuration from various locations
2
3use crate::error::Result;
4use crate::value::ConfigValue;
5use async_trait::async_trait;
6use std::collections::HashMap;
7
8pub mod env;
9pub mod file;
10pub mod remote;
11
12pub use env::EnvSource;
13pub use file::FileSource;
14pub use remote::RemoteSource;
15
16/// Trait for configuration sources
17#[async_trait]
18pub trait ConfigSource: Send + Sync {
19    /// Load configuration from this source
20    async fn load(&self) -> Result<ConfigValue>;
21
22    /// Get the name of this source (for debugging/logging)
23    fn name(&self) -> &str;
24
25    /// Check if this source supports watching for changes
26    fn supports_watching(&self) -> bool {
27        false
28    }
29
30    /// Start watching for changes (if supported)
31    async fn start_watching(&self) -> Result<tokio::sync::mpsc::Receiver<ConfigValue>> {
32        Err(crate::error::ConfigError::Other(
33            "Watching not supported by this source".to_string(),
34        ))
35    }
36}
37
38/// A source that combines multiple configuration sources
39pub struct CompositeSource {
40    sources: Vec<(Box<dyn ConfigSource>, u32)>, // (source, priority)
41    name: String,
42}
43
44impl CompositeSource {
45    /// Create a new composite source
46    pub fn new(name: String) -> Self {
47        Self {
48            sources: Vec::new(),
49            name,
50        }
51    }
52
53    /// Add a source with priority (higher number = higher priority)
54    pub fn add_source(mut self, source: Box<dyn ConfigSource>, priority: u32) -> Self {
55        self.sources.push((source, priority));
56        self.sources.sort_by(|a, b| a.1.cmp(&b.1)); // Sort by priority
57        self
58    }
59}
60
61#[async_trait]
62impl ConfigSource for CompositeSource {
63    async fn load(&self) -> Result<ConfigValue> {
64        let mut merged_config = ConfigValue::Object(HashMap::new());
65
66        // Load from all sources in priority order (lowest to highest)
67        for (source, _priority) in &self.sources {
68            let config = source.load().await?;
69            merged_config.merge(config);
70        }
71
72        Ok(merged_config)
73    }
74
75    fn name(&self) -> &str {
76        &self.name
77    }
78
79    fn supports_watching(&self) -> bool {
80        self.sources
81            .iter()
82            .any(|(source, _)| source.supports_watching())
83    }
84}
85
86/// Helper function to merge two ConfigValue objects
87pub fn merge_config_values(mut base: ConfigValue, overlay: ConfigValue) -> ConfigValue {
88    base.merge(overlay);
89    base
90}