subx_cli/config/
source.rs

1//! Configuration sources for loading partial configuration.
2
3use crate::config::manager::ConfigError;
4use crate::config::partial::PartialConfig;
5use log::debug;
6use std::path::PathBuf;
7
8/// Trait for configuration source.
9pub trait ConfigSource: Send + Sync {
10    /// Load partial configuration.
11    fn load(&self) -> Result<PartialConfig, ConfigError>;
12    /// Priority (lower = higher priority).
13    fn priority(&self) -> u8;
14    /// Source name for debugging.
15    fn source_name(&self) -> &'static str;
16    /// File system paths to watch for changes (only file-based sources need override).
17    fn watch_paths(&self) -> Vec<PathBuf> {
18        Vec::new()
19    }
20}
21
22/// File-based configuration source.
23pub struct FileSource {
24    path: PathBuf,
25}
26
27impl FileSource {
28    /// Create a new file source for the given path.
29    pub fn new(path: PathBuf) -> Self {
30        Self { path }
31    }
32}
33
34impl ConfigSource for FileSource {
35    fn load(&self) -> Result<PartialConfig, ConfigError> {
36        debug!("FileSource: Attempting to load from path: {:?}", self.path);
37        debug!("FileSource: Path exists: {}", self.path.exists());
38
39        if !self.path.exists() {
40            debug!("FileSource: Path does not exist, returning default config");
41            return Ok(PartialConfig::default());
42        }
43
44        let content = std::fs::read_to_string(&self.path).map_err(|e| {
45            debug!("FileSource: Failed to read file: {}", e);
46            e
47        })?;
48        debug!("FileSource: Read {} bytes from file", content.len());
49        debug!("FileSource: File content:\n{}", content);
50
51        let cfg: PartialConfig = toml::from_str(&content).map_err(|e| {
52            debug!("FileSource: TOML parsing failed: {}", e);
53            ConfigError::ParseError(e.to_string())
54        })?;
55
56        debug!("FileSource: Parsed successfully");
57        debug!(
58            "FileSource: cfg.ai.max_sample_length = {:?}",
59            cfg.ai.max_sample_length
60        );
61        debug!("FileSource: cfg.ai.model = {:?}", cfg.ai.model);
62        debug!("FileSource: cfg.ai.provider = {:?}", cfg.ai.provider);
63
64        Ok(cfg)
65    }
66
67    fn priority(&self) -> u8 {
68        10
69    }
70
71    fn source_name(&self) -> &'static str {
72        "file"
73    }
74    fn watch_paths(&self) -> Vec<PathBuf> {
75        vec![self.path.clone()]
76    }
77}
78
79/// Environment variable configuration source.
80pub struct EnvSource;
81
82impl EnvSource {
83    pub fn new() -> Self {
84        Self
85    }
86}
87
88impl ConfigSource for EnvSource {
89    fn load(&self) -> Result<PartialConfig, ConfigError> {
90        let mut config = PartialConfig::default();
91        if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
92            config.ai.api_key = Some(api_key);
93        }
94        if let Ok(model) = std::env::var("SUBX_AI_MODEL") {
95            config.ai.model = Some(model);
96        }
97        if let Ok(provider) = std::env::var("SUBX_AI_PROVIDER") {
98            config.ai.provider = Some(provider);
99        }
100        if let Ok(base_url) = std::env::var("OPENAI_BASE_URL") {
101            config.ai.base_url = Some(base_url);
102        }
103        if let Ok(backup) = std::env::var("SUBX_BACKUP_ENABLED") {
104            config.general.backup_enabled = Some(backup.parse().unwrap_or(false));
105        }
106        Ok(config)
107    }
108
109    fn priority(&self) -> u8 {
110        5 // 中等優先權,高於檔案但低於 CLI
111    }
112
113    fn source_name(&self) -> &'static str {
114        "environment"
115    }
116}
117
118/// Command line arguments configuration source.
119pub struct CliSource;
120
121impl CliSource {
122    pub fn new() -> Self {
123        Self {}
124    }
125}
126
127impl ConfigSource for CliSource {
128    fn load(&self) -> Result<PartialConfig, ConfigError> {
129        Ok(PartialConfig::default())
130    }
131
132    fn priority(&self) -> u8 {
133        1 // 最高優先權
134    }
135
136    fn source_name(&self) -> &'static str {
137        "cli"
138    }
139}
140
141impl Default for EnvSource {
142    fn default() -> Self {
143        EnvSource::new()
144    }
145}
146
147impl Default for CliSource {
148    fn default() -> Self {
149        CliSource::new()
150    }
151}