ccsync_core/config/
validation.rs

1//! Configuration validation and error reporting
2
3use super::types::Config;
4use crate::error::Result;
5
6/// Configuration validator
7pub struct ConfigValidator;
8
9impl Default for ConfigValidator {
10    fn default() -> Self {
11        Self::new()
12    }
13}
14
15impl ConfigValidator {
16    /// Create a new config validator
17    #[must_use]
18    pub const fn new() -> Self {
19        Self
20    }
21
22    /// Validate a configuration
23    ///
24    /// # Errors
25    ///
26    /// Returns an error if the configuration is invalid.
27    pub fn validate(config: &Config) -> Result<()> {
28        // Check for conflicting settings
29        if config.follow_symlinks == Some(true) && config.preserve_symlinks == Some(true) {
30            anyhow::bail!(
31                "Conflicting configuration: both follow_symlinks and preserve_symlinks are enabled"
32            );
33        }
34
35        // Validate patterns are not empty strings
36        for pattern in &config.ignore {
37            if pattern.trim().is_empty() {
38                anyhow::bail!("Ignore pattern cannot be empty");
39            }
40        }
41
42        for pattern in &config.include {
43            if pattern.trim().is_empty() {
44                anyhow::bail!("Include pattern cannot be empty");
45            }
46        }
47
48        // Validate rules
49        for (idx, rule) in config.rules.iter().enumerate() {
50            if rule.patterns.is_empty() {
51                anyhow::bail!("Rule #{} has no patterns", idx + 1);
52            }
53
54            for pattern in &rule.patterns {
55                if pattern.trim().is_empty() {
56                    anyhow::bail!("Rule #{} has empty pattern", idx + 1);
57                }
58            }
59        }
60
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::config::types::{FileType, SyncDirection, SyncRule};
69
70    #[test]
71    fn test_validate_empty_config() {
72        let config = Config::default();
73        let _validator = ConfigValidator::new();
74
75        assert!(ConfigValidator::validate(&config).is_ok());
76    }
77
78    #[test]
79    fn test_validate_conflicting_symlink_settings() {
80        let mut config = Config::default();
81        config.follow_symlinks = Some(true);
82        config.preserve_symlinks = Some(true);
83
84        let _validator = ConfigValidator::new();
85        let result = ConfigValidator::validate(&config);
86
87        assert!(result.is_err());
88        assert!(
89            result
90                .unwrap_err()
91                .to_string()
92                .contains("follow_symlinks and preserve_symlinks")
93        );
94    }
95
96    #[test]
97    fn test_validate_empty_pattern() {
98        let mut config = Config::default();
99        config.ignore.push("   ".to_string());
100
101        let _validator = ConfigValidator::new();
102        let result = ConfigValidator::validate(&config);
103
104        assert!(result.is_err());
105        assert!(result.unwrap_err().to_string().contains("cannot be empty"));
106    }
107
108    #[test]
109    fn test_validate_rule_with_no_patterns() {
110        let mut config = Config::default();
111        config.rules.push(SyncRule {
112            patterns: vec![],
113            direction: Some(SyncDirection::ToLocal),
114            file_type: Some(FileType::Text),
115            include: true,
116        });
117
118        let _validator = ConfigValidator::new();
119        let result = ConfigValidator::validate(&config);
120
121        assert!(result.is_err());
122        assert!(result.unwrap_err().to_string().contains("has no patterns"));
123    }
124
125    #[test]
126    fn test_validate_valid_config() {
127        let mut config = Config::default();
128        config.ignore.push("*.tmp".to_string());
129        config.include.push("important.tmp".to_string());
130        config.follow_symlinks = Some(false);
131        config.preserve_symlinks = Some(false);
132
133        let _validator = ConfigValidator::new();
134        assert!(ConfigValidator::validate(&config).is_ok());
135    }
136}