claude_agent/config/
composite.rs

1//! Composite Configuration Provider
2//!
3//! Chains multiple configuration providers with priority ordering.
4//! Earlier providers have higher priority.
5
6use super::ConfigResult;
7use super::provider::ConfigProvider;
8
9/// Composite configuration provider that chains multiple providers
10pub struct CompositeConfigProvider {
11    providers: Vec<Box<dyn ConfigProvider>>,
12}
13
14impl CompositeConfigProvider {
15    /// Create a new empty composite provider
16    pub fn new() -> Self {
17        Self {
18            providers: Vec::new(),
19        }
20    }
21
22    /// Add a provider (first added = highest priority)
23    pub fn add_provider(&mut self, provider: Box<dyn ConfigProvider>) {
24        self.providers.push(provider);
25    }
26
27    /// Add a provider and return self (for chaining)
28    pub fn with_provider(mut self, provider: Box<dyn ConfigProvider>) -> Self {
29        self.providers.push(provider);
30        self
31    }
32
33    /// Get the number of providers
34    pub fn provider_count(&self) -> usize {
35        self.providers.len()
36    }
37
38    /// Get provider names
39    pub fn provider_names(&self) -> Vec<&str> {
40        self.providers.iter().map(|p| p.name()).collect()
41    }
42}
43
44impl Default for CompositeConfigProvider {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50#[async_trait::async_trait]
51impl ConfigProvider for CompositeConfigProvider {
52    fn name(&self) -> &str {
53        "composite"
54    }
55
56    async fn get_raw(&self, key: &str) -> ConfigResult<Option<String>> {
57        // Try each provider in order (first match wins)
58        for provider in &self.providers {
59            if let Some(value) = provider.get_raw(key).await? {
60                return Ok(Some(value));
61            }
62        }
63        Ok(None)
64    }
65
66    async fn set_raw(&self, key: &str, value: &str) -> ConfigResult<()> {
67        // Set in the first provider (highest priority)
68        if let Some(provider) = self.providers.first() {
69            provider.set_raw(key, value).await?;
70        }
71        Ok(())
72    }
73
74    async fn delete(&self, key: &str) -> ConfigResult<bool> {
75        // Delete from all providers that have the key
76        let mut deleted = false;
77        for provider in &self.providers {
78            if provider.delete(key).await? {
79                deleted = true;
80            }
81        }
82        Ok(deleted)
83    }
84
85    async fn list_keys(&self, prefix: &str) -> ConfigResult<Vec<String>> {
86        // Collect unique keys from all providers
87        let mut all_keys = std::collections::HashSet::new();
88        for provider in &self.providers {
89            for key in provider.list_keys(prefix).await? {
90                all_keys.insert(key);
91            }
92        }
93        Ok(all_keys.into_iter().collect())
94    }
95}
96
97impl std::fmt::Debug for CompositeConfigProvider {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("CompositeConfigProvider")
100            .field("provider_count", &self.providers.len())
101            .field("provider_names", &self.provider_names())
102            .finish()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::config::memory::MemoryConfigProvider;
110
111    #[tokio::test]
112    async fn test_composite_provider_priority() {
113        let high_priority = MemoryConfigProvider::with_name("high");
114        high_priority.set_raw("key", "high_value").await.unwrap();
115
116        let low_priority = MemoryConfigProvider::with_name("low");
117        low_priority.set_raw("key", "low_value").await.unwrap();
118        low_priority.set_raw("only_low", "from_low").await.unwrap();
119
120        let composite = CompositeConfigProvider::new()
121            .with_provider(Box::new(high_priority))
122            .with_provider(Box::new(low_priority));
123
124        // High priority wins
125        assert_eq!(
126            composite.get_raw("key").await.unwrap(),
127            Some("high_value".to_string())
128        );
129
130        // Falls through to low priority
131        assert_eq!(
132            composite.get_raw("only_low").await.unwrap(),
133            Some("from_low".to_string())
134        );
135    }
136
137    #[tokio::test]
138    async fn test_composite_provider_list_keys() {
139        let p1 = MemoryConfigProvider::new();
140        p1.set_raw("app.name", "value1").await.unwrap();
141        p1.set_raw("app.version", "value2").await.unwrap();
142
143        let p2 = MemoryConfigProvider::new();
144        p2.set_raw("app.config", "value3").await.unwrap();
145        p2.set_raw("other", "value4").await.unwrap();
146
147        let composite = CompositeConfigProvider::new()
148            .with_provider(Box::new(p1))
149            .with_provider(Box::new(p2));
150
151        let keys = composite.list_keys("app.").await.unwrap();
152        assert_eq!(keys.len(), 3); // name, version, config
153    }
154
155    #[tokio::test]
156    async fn test_composite_provider_set() {
157        let composite = CompositeConfigProvider::new()
158            .with_provider(Box::new(MemoryConfigProvider::new()))
159            .with_provider(Box::new(MemoryConfigProvider::new()));
160
161        composite.set_raw("new_key", "new_value").await.unwrap();
162
163        // Should be readable
164        assert_eq!(
165            composite.get_raw("new_key").await.unwrap(),
166            Some("new_value".to_string())
167        );
168    }
169
170    #[tokio::test]
171    async fn test_composite_provider_delete() {
172        let p1 = MemoryConfigProvider::new();
173        p1.set_raw("shared", "from_p1").await.unwrap();
174
175        let p2 = MemoryConfigProvider::new();
176        p2.set_raw("shared", "from_p2").await.unwrap();
177
178        let composite = CompositeConfigProvider::new()
179            .with_provider(Box::new(p1))
180            .with_provider(Box::new(p2));
181
182        // Delete from all
183        assert!(composite.delete("shared").await.unwrap());
184        assert_eq!(composite.get_raw("shared").await.unwrap(), None);
185    }
186
187    #[tokio::test]
188    async fn test_composite_provider_names() {
189        let composite = CompositeConfigProvider::new()
190            .with_provider(Box::new(MemoryConfigProvider::with_name("first")))
191            .with_provider(Box::new(MemoryConfigProvider::with_name("second")));
192
193        let names = composite.provider_names();
194        assert_eq!(names, vec!["first", "second"]);
195    }
196}