claude_agent/config/
env.rs

1//! Environment Variable Configuration Provider
2//!
3//! Provides read-only access to configuration via environment variables.
4//! Environment variables are immutable at runtime for thread-safety.
5
6use super::provider::ConfigProvider;
7use super::{ConfigError, ConfigResult};
8
9/// Read-only environment variable configuration provider.
10///
11/// Environment variables are treated as immutable at runtime because
12/// modifying them is not thread-safe (requires unsafe in Rust 1.80+).
13#[derive(Debug, Clone)]
14pub struct EnvConfigProvider {
15    prefix: Option<String>,
16}
17
18impl EnvConfigProvider {
19    /// Create a new environment provider with no prefix
20    pub fn new() -> Self {
21        Self { prefix: None }
22    }
23
24    /// Create an environment provider with a prefix
25    pub fn with_prefix(prefix: impl Into<String>) -> Self {
26        Self {
27            prefix: Some(prefix.into()),
28        }
29    }
30
31    /// Get the full environment variable name
32    fn env_key(&self, key: &str) -> String {
33        match &self.prefix {
34            Some(prefix) => format!("{}{}", prefix, key.to_uppercase().replace('.', "_")),
35            None => key.to_uppercase().replace('.', "_"),
36        }
37    }
38
39    /// Reverse: extract key from environment variable name
40    fn key_from_env(&self, env_name: &str) -> Option<String> {
41        match &self.prefix {
42            Some(prefix) => {
43                if env_name.starts_with(prefix) {
44                    Some(env_name[prefix.len()..].to_lowercase().replace('_', "."))
45                } else {
46                    None
47                }
48            }
49            None => Some(env_name.to_lowercase().replace('_', ".")),
50        }
51    }
52}
53
54impl Default for EnvConfigProvider {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60#[async_trait::async_trait]
61impl ConfigProvider for EnvConfigProvider {
62    fn name(&self) -> &str {
63        "env"
64    }
65
66    async fn get_raw(&self, key: &str) -> ConfigResult<Option<String>> {
67        let env_key = self.env_key(key);
68        match std::env::var(&env_key) {
69            Ok(value) => Ok(Some(value)),
70            Err(std::env::VarError::NotPresent) => Ok(None),
71            Err(e) => Err(ConfigError::Env(e)),
72        }
73    }
74
75    async fn set_raw(&self, _key: &str, _value: &str) -> ConfigResult<()> {
76        Err(ConfigError::Provider {
77            message: "Environment variables are read-only at runtime".into(),
78        })
79    }
80
81    async fn delete(&self, _key: &str) -> ConfigResult<bool> {
82        Err(ConfigError::Provider {
83            message: "Environment variables are read-only at runtime".into(),
84        })
85    }
86
87    async fn list_keys(&self, prefix: &str) -> ConfigResult<Vec<String>> {
88        let env_prefix = self.env_key(prefix);
89        let keys: Vec<String> = std::env::vars()
90            .filter_map(|(k, _)| {
91                if k.starts_with(&env_prefix) {
92                    self.key_from_env(&k)
93                } else {
94                    None
95                }
96            })
97            .collect();
98        Ok(keys)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[tokio::test]
107    async fn test_env_key_conversion() {
108        let provider = EnvConfigProvider::new();
109        assert_eq!(provider.env_key("api.key"), "API_KEY");
110        assert_eq!(provider.env_key("model.name"), "MODEL_NAME");
111
112        let provider = EnvConfigProvider::with_prefix("CLAUDE_");
113        assert_eq!(provider.env_key("api.key"), "CLAUDE_API_KEY");
114    }
115
116    #[tokio::test]
117    async fn test_env_provider_get() {
118        let provider = EnvConfigProvider::with_prefix("TEST_CONFIG_");
119
120        // SAFETY: Test-only environment setup
121        unsafe { std::env::set_var("TEST_CONFIG_MY_KEY", "my_value") };
122        let value = provider.get_raw("my.key").await.unwrap();
123        assert_eq!(value, Some("my_value".to_string()));
124        unsafe { std::env::remove_var("TEST_CONFIG_MY_KEY") };
125    }
126
127    #[tokio::test]
128    async fn test_env_provider_read_only() {
129        let provider = EnvConfigProvider::new();
130
131        // set_raw should fail (read-only)
132        assert!(provider.set_raw("key", "value").await.is_err());
133
134        // delete should fail (read-only)
135        assert!(provider.delete("key").await.is_err());
136    }
137
138    #[tokio::test]
139    async fn test_env_provider_not_found() {
140        let provider = EnvConfigProvider::with_prefix("NONEXISTENT_PREFIX_");
141        let value = provider.get_raw("some.key").await.unwrap();
142        assert_eq!(value, None);
143    }
144}