subx_cli/config/
validator.rs1use crate::Result;
8use crate::config::Config;
9use crate::error::SubXError;
10
11pub trait ConfigValidator {
13 fn validate(&self, config: &Config) -> Result<()>;
15}
16
17pub struct AIValidator;
19
20impl ConfigValidator for AIValidator {
21 fn validate(&self, config: &Config) -> Result<()> {
22 match config.ai.provider.as_str() {
24 "openai" | "anthropic" => {}
25 _ => {
26 return Err(SubXError::config(format!(
27 "Unsupported AI provider: {}",
28 config.ai.provider
29 )));
30 }
31 }
32
33 if config.ai.provider == "openai" {
35 if let Some(api_key) = config.ai.api_key.as_deref() {
36 if !api_key.starts_with("sk-") && !api_key.is_empty() {
37 return Err(SubXError::config("OpenAI API key must start with 'sk-'"));
38 }
39 }
40 }
41
42 if config.ai.temperature < 0.0 || config.ai.temperature > 2.0 {
44 return Err(SubXError::config(
45 "Temperature value must be between 0.0 and 2.0",
46 ));
47 }
48
49 if config.ai.retry_attempts > 10 {
51 return Err(SubXError::config("Retry count cannot exceed 10 times"));
52 }
53
54 Ok(())
55 }
56}
57
58pub struct SyncValidator;
60
61impl ConfigValidator for SyncValidator {
62 fn validate(&self, config: &Config) -> Result<()> {
63 if config.sync.max_offset_seconds < 0.0 || config.sync.max_offset_seconds > 300.0 {
65 return Err(SubXError::config(
66 "Maximum offset seconds must be between 0.0 and 300.0",
67 ));
68 }
69
70 if config.sync.correlation_threshold < 0.0 || config.sync.correlation_threshold > 1.0 {
72 return Err(SubXError::config(
73 "Correlation threshold must be between 0.0 and 1.0",
74 ));
75 }
76
77 Ok(())
78 }
79}
80
81pub struct FormatsValidator;
83
84impl ConfigValidator for FormatsValidator {
85 fn validate(&self, config: &Config) -> Result<()> {
86 if config.formats.default_output.is_empty() {
88 return Err(SubXError::config("Default output format cannot be empty"));
89 }
90
91 if config.formats.default_encoding.is_empty() {
93 return Err(SubXError::config("Default encoding cannot be empty"));
94 }
95
96 if config.formats.encoding_detection_confidence < 0.0
98 || config.formats.encoding_detection_confidence > 1.0
99 {
100 return Err(SubXError::config(
101 "Encoding detection confidence must be between 0.0 and 1.0",
102 ));
103 }
104
105 Ok(())
106 }
107}
108
109pub struct ParallelValidator;
111
112impl ConfigValidator for ParallelValidator {
113 fn validate(&self, config: &Config) -> Result<()> {
114 if config.parallel.max_workers == 0 {
116 return Err(SubXError::config(
117 "Maximum concurrent workers must be greater than 0",
118 ));
119 }
120
121 Ok(())
122 }
123}
124
125pub fn validate_config(config: &Config) -> Result<()> {
130 AIValidator.validate(config)?;
131 SyncValidator.validate(config)?;
132 FormatsValidator.validate(config)?;
133 ParallelValidator.validate(config)?;
134 Ok(())
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::config::Config;
141
142 #[test]
143 fn test_validate_default_config() {
144 let config = Config::default();
145 assert!(validate_config(&config).is_ok());
146 }
147
148 #[test]
149 fn test_invalid_ai_provider() {
150 let mut config = Config::default();
151 config.ai.provider = "invalid".to_string();
152 assert!(validate_config(&config).is_err());
153 }
154
155 #[test]
156 fn test_invalid_temperature() {
157 let mut config = Config::default();
158 config.ai.temperature = 3.0; assert!(validate_config(&config).is_err());
160 }
161
162 #[test]
163 fn test_invalid_correlation_threshold() {
164 let mut config = Config::default();
165 config.sync.correlation_threshold = 1.5; assert!(validate_config(&config).is_err());
167 }
168}