subx_cli/config/
validator.rs1use crate::Result;
8use crate::config::Config;
9use crate::config::{SyncConfig, VadConfig};
10use crate::error::SubXError;
11
12pub trait ConfigValidator {
14 fn validate(&self, config: &Config) -> Result<()>;
16}
17
18pub struct AIValidator;
20
21impl ConfigValidator for AIValidator {
22 fn validate(&self, config: &Config) -> Result<()> {
23 match config.ai.provider.as_str() {
25 "openai" | "anthropic" => {}
26 _ => {
27 return Err(SubXError::config(format!(
28 "Unsupported AI provider: {}",
29 config.ai.provider
30 )));
31 }
32 }
33
34 if config.ai.provider == "openai" {
36 if let Some(api_key) = config.ai.api_key.as_deref() {
37 if !api_key.starts_with("sk-") && !api_key.is_empty() {
38 return Err(SubXError::config("OpenAI API key must start with 'sk-'"));
39 }
40 }
41 }
42
43 if config.ai.temperature < 0.0 || config.ai.temperature > 2.0 {
45 return Err(SubXError::config(
46 "Temperature value must be between 0.0 and 2.0",
47 ));
48 }
49
50 if config.ai.retry_attempts > 10 {
52 return Err(SubXError::config("Retry count cannot exceed 10 times"));
53 }
54
55 if config.ai.request_timeout_seconds < 10 {
57 return Err(SubXError::config(
58 "Request timeout must be at least 10 seconds to allow for network latency",
59 ));
60 }
61
62 if config.ai.request_timeout_seconds > 600 {
63 return Err(SubXError::config(
64 "Request timeout should not exceed 600 seconds (10 minutes) to avoid excessive waiting",
65 ));
66 }
67
68 Ok(())
69 }
70}
71
72pub struct SyncValidator;
74
75impl ConfigValidator for SyncValidator {
76 fn validate(&self, config: &Config) -> Result<()> {
77 config.sync.validate()
79 }
80}
81
82impl SyncConfig {
83 pub fn validate(&self) -> Result<()> {
100 match self.default_method.as_str() {
102 "vad" | "auto" | "manual" => {}
103 _ => {
104 return Err(SubXError::config(
105 "sync.default_method must be one of: vad, auto, manual",
106 ));
107 }
108 }
109
110 if self.max_offset_seconds <= 0.0 {
112 return Err(SubXError::config(
113 "sync.max_offset_seconds must be greater than 0. Recommended range: 30.0 to 300.0 seconds.",
114 ));
115 }
116
117 if self.max_offset_seconds > 3600.0 {
118 return Err(SubXError::config(
119 "sync.max_offset_seconds should not exceed 3600 seconds (1 hour). If a larger value is needed, please verify the sync requirements are reasonable.",
120 ));
121 }
122
123 if self.max_offset_seconds < 5.0 {
125 log::warn!(
126 "sync.max_offset_seconds is set to {:.1}s which may be too small. Consider using 30.0-60.0 seconds.",
127 self.max_offset_seconds
128 );
129 } else if self.max_offset_seconds > 600.0 && self.max_offset_seconds <= 3600.0 {
130 log::warn!(
131 "sync.max_offset_seconds is set to {:.1}s which is quite large. Please confirm this meets your requirements.",
132 self.max_offset_seconds
133 );
134 }
135
136 self.vad.validate()?;
138
139 Ok(())
140 }
141}
142
143impl VadConfig {
144 pub fn validate(&self) -> Result<()> {
161 if self.sensitivity < 0.0 || self.sensitivity > 1.0 {
163 return Err(SubXError::config(
164 "sync.vad.sensitivity must be between 0.0 and 1.0",
165 ));
166 }
167
168 if self.chunk_size < 256 || self.chunk_size > 2048 || !self.chunk_size.is_power_of_two() {
170 return Err(SubXError::config(
171 "sync.vad.chunk_size must be a power of 2 between 256 and 2048",
172 ));
173 }
174
175 match self.sample_rate {
177 8000 | 16000 | 22050 | 32000 | 44100 | 48000 => {}
178 _ => {
179 return Err(SubXError::config(
180 "sync.vad.sample_rate must be one of: 8000, 16000, 22050, 32000, 44100, 48000",
181 ));
182 }
183 }
184
185 Ok(())
186 }
187}
188
189pub struct FormatsValidator;
191
192impl ConfigValidator for FormatsValidator {
193 fn validate(&self, config: &Config) -> Result<()> {
194 if config.formats.default_output.is_empty() {
196 return Err(SubXError::config("Default output format cannot be empty"));
197 }
198
199 if config.formats.default_encoding.is_empty() {
201 return Err(SubXError::config("Default encoding cannot be empty"));
202 }
203
204 if config.formats.encoding_detection_confidence < 0.0
206 || config.formats.encoding_detection_confidence > 1.0
207 {
208 return Err(SubXError::config(
209 "Encoding detection confidence must be between 0.0 and 1.0",
210 ));
211 }
212
213 Ok(())
214 }
215}
216
217pub struct ParallelValidator;
219
220impl ConfigValidator for ParallelValidator {
221 fn validate(&self, config: &Config) -> Result<()> {
222 if config.parallel.max_workers == 0 {
224 return Err(SubXError::config(
225 "Maximum concurrent workers must be greater than 0",
226 ));
227 }
228
229 Ok(())
230 }
231}
232
233pub fn validate_config(config: &Config) -> Result<()> {
238 AIValidator.validate(config)?;
239 SyncValidator.validate(config)?;
240 FormatsValidator.validate(config)?;
241 ParallelValidator.validate(config)?;
242 Ok(())
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::config::Config;
249
250 #[test]
251 fn test_validate_default_config() {
252 let config = Config::default();
253 assert!(validate_config(&config).is_ok());
254 }
255
256 #[test]
257 fn test_invalid_ai_provider() {
258 let mut config = Config::default();
259 config.ai.provider = "invalid".to_string();
260 assert!(validate_config(&config).is_err());
261 }
262
263 #[test]
264 fn test_invalid_temperature() {
265 let mut config = Config::default();
266 config.ai.temperature = 3.0; assert!(validate_config(&config).is_err());
268 }
269
270 #[test]
271 fn test_invalid_vad_sensitivity() {
272 let mut config = Config::default();
273 config.sync.vad.sensitivity = 1.5; assert!(validate_config(&config).is_err());
275 }
276}