Skip to main content

claude_agent/config/
mod.rs

1//! Pluggable configuration provider system.
2//!
3//! ```rust,no_run
4//! use claude_agent::config::ConfigBuilder;
5//!
6//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
7//! let config = ConfigBuilder::new()
8//!     .env()
9//!     .file(".claude/settings.json")
10//!     .build()
11//!     .await?;
12//! # Ok(())
13//! # }
14//! ```
15
16pub mod cloud;
17pub mod composite;
18pub mod env;
19pub mod file;
20pub mod memory;
21pub mod provider;
22pub mod settings;
23pub mod validator;
24
25pub use cloud::{BedrockConfig, CloudConfig, FoundryConfig, TokenLimits, VertexConfig};
26pub use composite::CompositeConfigProvider;
27pub use env::EnvConfigProvider;
28pub use file::FileConfigProvider;
29pub use memory::MemoryConfigProvider;
30pub use provider::{ConfigProvider, ConfigProviderExt};
31pub use settings::{
32    HookConfig, HooksSettings, NetworkSandboxSettings, PermissionSettings, SandboxSettings,
33    Settings, SettingsLoader, SettingsSource, ToolSearchSettings,
34};
35pub use validator::{ConfigValidator, ValueType};
36
37use thiserror::Error;
38
39#[derive(Error, Debug)]
40pub enum ConfigError {
41    #[error("Key not found: {key}")]
42    NotFound { key: String },
43
44    #[error("Invalid value for {key}: {message}")]
45    InvalidValue { key: String, message: String },
46
47    #[error("Serialization error: {0}")]
48    Serialization(#[from] serde_json::Error),
49
50    #[error("IO error: {0}")]
51    Io(#[from] std::io::Error),
52
53    #[error("Environment error: {0}")]
54    Env(#[from] std::env::VarError),
55
56    #[error("Provider error: {message}")]
57    Provider { message: String },
58
59    #[error("{0}")]
60    ValidationErrors(ValidationErrors),
61}
62
63#[derive(Debug)]
64pub struct ValidationErrors(pub Vec<ConfigError>);
65
66impl std::fmt::Display for ValidationErrors {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "Validation failed: ")?;
69        let msgs: Vec<String> = self.0.iter().map(|e| e.to_string()).collect();
70        write!(f, "{}", msgs.join("; "))
71    }
72}
73
74pub type ConfigResult<T> = std::result::Result<T, ConfigError>;
75
76pub struct ConfigBuilder {
77    providers: Vec<Box<dyn ConfigProvider>>,
78}
79
80impl ConfigBuilder {
81    pub fn new() -> Self {
82        Self {
83            providers: Vec::new(),
84        }
85    }
86
87    pub fn env(mut self) -> Self {
88        self.providers.push(Box::new(EnvConfigProvider::new()));
89        self
90    }
91
92    pub fn env_with_prefix(mut self, prefix: &str) -> Self {
93        self.providers
94            .push(Box::new(EnvConfigProvider::prefixed(prefix)));
95        self
96    }
97
98    pub fn file(mut self, path: impl AsRef<std::path::Path>) -> Self {
99        self.providers.push(Box::new(FileConfigProvider::new(
100            path.as_ref().to_path_buf(),
101        )));
102        self
103    }
104
105    pub fn memory(mut self, provider: MemoryConfigProvider) -> Self {
106        self.providers.push(Box::new(provider));
107        self
108    }
109
110    pub fn provider(mut self, provider: Box<dyn ConfigProvider>) -> Self {
111        self.providers.push(provider);
112        self
113    }
114
115    pub async fn build(self) -> ConfigResult<CompositeConfigProvider> {
116        let mut composite = CompositeConfigProvider::new();
117        for provider in self.providers {
118            composite.add_provider(provider);
119        }
120        Ok(composite)
121    }
122}
123
124impl Default for ConfigBuilder {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_config_error_display() {
136        let err = ConfigError::NotFound {
137            key: "api_key".to_string(),
138        };
139        assert!(err.to_string().contains("api_key"));
140    }
141
142    #[test]
143    fn test_config_builder() {
144        let builder = ConfigBuilder::new().env().env_with_prefix("CLAUDE_");
145        assert!(!builder.providers.is_empty());
146    }
147}