subx_cli/config/
validator.rs

1//! Configuration validation module providing validation rules and constraints.
2//!
3//! This module provides comprehensive validation functionality for configuration
4//! values, ensuring that all settings meet business requirements and system
5//! constraints before being used by the application.
6
7use crate::Result;
8use crate::config::Config;
9use crate::error::SubXError;
10
11/// Trait defining the validation interface for configuration sections.
12pub trait ConfigValidator {
13    /// Validate the configuration and return any errors found.
14    fn validate(&self, config: &Config) -> Result<()>;
15}
16
17/// AI configuration validator.
18pub struct AIValidator;
19
20impl ConfigValidator for AIValidator {
21    fn validate(&self, config: &Config) -> Result<()> {
22        // Check AI provider
23        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        // Check API key format for OpenAI
34        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        // Check temperature range
43        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        // Check retry attempts
50        if config.ai.retry_attempts > 10 {
51            return Err(SubXError::config("Retry count cannot exceed 10 times"));
52        }
53
54        Ok(())
55    }
56}
57
58/// Sync configuration validator.
59pub struct SyncValidator;
60
61impl ConfigValidator for SyncValidator {
62    fn validate(&self, config: &Config) -> Result<()> {
63        // Check max offset seconds
64        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        // Check correlation threshold
71        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
81/// Formats configuration validator.
82pub struct FormatsValidator;
83
84impl ConfigValidator for FormatsValidator {
85    fn validate(&self, config: &Config) -> Result<()> {
86        // Check default output format
87        if config.formats.default_output.is_empty() {
88            return Err(SubXError::config("Default output format cannot be empty"));
89        }
90
91        // Check default encoding
92        if config.formats.default_encoding.is_empty() {
93            return Err(SubXError::config("Default encoding cannot be empty"));
94        }
95
96        // Check encoding detection confidence
97        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
109/// Parallel configuration validator.
110pub struct ParallelValidator;
111
112impl ConfigValidator for ParallelValidator {
113    fn validate(&self, config: &Config) -> Result<()> {
114        // Check max workers
115        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
125/// Validate the complete configuration.
126///
127/// This function runs all configuration validators and returns the first
128/// error encountered, or Ok(()) if all validation passes.
129pub 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; // Too high
159        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; // Too high
166        assert!(validate_config(&config).is_err());
167    }
168}