subx_cli/config/
validator.rs1use crate::config::Config;
4use crate::config::manager::ConfigError;
5
6pub trait ConfigValidator: Send + Sync {
8 fn validate(&self, config: &Config) -> Result<(), ConfigError>;
10 fn validator_name(&self) -> &'static str;
12}
13
14pub struct AIConfigValidator;
16
17impl ConfigValidator for AIConfigValidator {
18 fn validate(&self, config: &Config) -> Result<(), ConfigError> {
19 match config.ai.provider.as_str() {
21 "openai" => {}
22 other => {
23 return Err(ConfigError::InvalidValue(
24 "ai.provider".to_string(),
25 format!("不支援的 AI 提供商: {}", other),
26 ));
27 }
28 }
29 if let Some(ref api_key) = config.ai.api_key {
30 if !api_key.starts_with("sk-") {
31 return Err(ConfigError::InvalidValue(
32 "ai.api_key".to_string(),
33 "OpenAI API 金鑰必須以 'sk-' 開頭".to_string(),
34 ));
35 }
36 }
37 let valid_models = [
38 "gpt-4",
39 "gpt-4-turbo",
40 "gpt-4o",
41 "gpt-4o-mini",
42 "gpt-3.5-turbo",
43 ];
44 if !valid_models.contains(&config.ai.model.as_str()) {
45 return Err(ConfigError::InvalidValue(
46 "ai.model".to_string(),
47 format!(
48 "不支援的模型: {},支援的模型: {:?}",
49 config.ai.model, valid_models
50 ),
51 ));
52 }
53 if config.ai.temperature < 0.0 || config.ai.temperature > 2.0 {
54 return Err(ConfigError::InvalidValue(
55 "ai.temperature".to_string(),
56 "溫度值必須在 0.0 到 2.0 之間".to_string(),
57 ));
58 }
59 if config.ai.retry_attempts > 10 {
60 return Err(ConfigError::InvalidValue(
61 "ai.retry_attempts".to_string(),
62 "重試次數不能超過 10 次".to_string(),
63 ));
64 }
65 if let Err(e) = validate_base_url(&config.ai.base_url) {
67 return Err(ConfigError::InvalidValue(
68 "ai.base_url".to_string(),
69 e.to_string(),
70 ));
71 }
72 Ok(())
73 }
74
75 fn validator_name(&self) -> &'static str {
76 "ai_config"
77 }
78}
79
80fn validate_base_url(url: &str) -> Result<(), String> {
81 use url::Url;
82 let parsed = Url::parse(url).map_err(|e| format!("無效的 URL 格式: {}", e))?;
83
84 if !matches!(parsed.scheme(), "http" | "https") {
85 return Err("base URL 必須使用 http 或 https 協定".to_string());
86 }
87
88 if parsed.host().is_none() {
89 return Err("base URL 必須包含有效的主機名稱".to_string());
90 }
91
92 Ok(())
93}
94
95#[cfg(test)]
96mod tests {
97 use super::validate_base_url;
98
99 #[test]
100 fn valid_base_url_https() {
101 assert!(validate_base_url("https://api.example.com/v1").is_ok());
102 }
103
104 #[test]
105 fn valid_base_url_http() {
106 assert!(validate_base_url("http://localhost:8000").is_ok());
107 }
108
109 #[test]
110 fn invalid_base_url_scheme() {
111 assert!(validate_base_url("ftp://example.com").is_err());
112 }
113
114 #[test]
115 fn invalid_base_url_no_host() {
116 assert!(validate_base_url("http://").is_err());
118 }
119}
120
121pub struct SyncConfigValidator;
123
124impl ConfigValidator for SyncConfigValidator {
125 fn validate(&self, config: &Config) -> Result<(), ConfigError> {
126 if config.sync.max_offset_seconds <= 0.0 || config.sync.max_offset_seconds > 300.0 {
127 return Err(ConfigError::InvalidValue(
128 "sync.max_offset_seconds".to_string(),
129 "最大偏移秒數必須在 0.0 到 300.0 之間".to_string(),
130 ));
131 }
132 if config.sync.correlation_threshold < 0.0 || config.sync.correlation_threshold > 1.0 {
133 return Err(ConfigError::InvalidValue(
134 "sync.correlation_threshold".to_string(),
135 "相關性閾值必須在 0.0 到 1.0 之間".to_string(),
136 ));
137 }
138 Ok(())
139 }
140
141 fn validator_name(&self) -> &'static str {
142 "sync_config"
143 }
144}
145
146pub struct FormatsConfigValidator;
148
149impl ConfigValidator for FormatsConfigValidator {
150 fn validate(&self, config: &Config) -> Result<(), ConfigError> {
151 if config.formats.default_output.trim().is_empty() {
152 return Err(ConfigError::InvalidValue(
153 "formats.default_output".to_string(),
154 "預設輸出格式不能為空".to_string(),
155 ));
156 }
157 if config.formats.default_encoding.trim().is_empty() {
158 return Err(ConfigError::InvalidValue(
159 "formats.default_encoding".to_string(),
160 "預設編碼不能為空".to_string(),
161 ));
162 }
163 if config.formats.encoding_detection_confidence < 0.0
164 || config.formats.encoding_detection_confidence > 1.0
165 {
166 return Err(ConfigError::InvalidValue(
167 "formats.encoding_detection_confidence".to_string(),
168 "編碼檢測信心度必須介於 0.0 和 1.0 之間".to_string(),
169 ));
170 }
171 Ok(())
172 }
173
174 fn validator_name(&self) -> &'static str {
175 "formats_config"
176 }
177}
178
179pub struct GeneralConfigValidator;
181
182impl ConfigValidator for GeneralConfigValidator {
183 fn validate(&self, config: &Config) -> Result<(), ConfigError> {
184 if config.general.max_concurrent_jobs == 0 {
185 return Err(ConfigError::InvalidValue(
186 "general.max_concurrent_jobs".to_string(),
187 "最大併發工作數必須大於 0".to_string(),
188 ));
189 }
190 Ok(())
191 }
192
193 fn validator_name(&self) -> &'static str {
194 "general_config"
195 }
196}