Skip to main content

cfgd_core/generate/
validate.rs

1use crate::config::{CfgdConfig, ModuleDocument, ProfileDocument};
2use crate::generate::{SchemaKind, ValidationResult};
3
4pub fn validate_yaml(content: &str, kind: SchemaKind) -> ValidationResult {
5    // Step 1: Parse as generic YAML to check syntax
6    let value: serde_yaml::Value = match serde_yaml::from_str(content) {
7        Ok(v) => v,
8        Err(e) => {
9            return ValidationResult {
10                valid: false,
11                errors: vec![format!("YAML syntax error: {}", e)],
12            };
13        }
14    };
15
16    // Step 2: Check kind field matches expected
17    if let Some(doc_kind) = value.get("kind").and_then(|v| v.as_str())
18        && doc_kind != kind.as_str()
19    {
20        return ValidationResult {
21            valid: false,
22            errors: vec![format!(
23                "Expected kind '{}', found '{}'",
24                kind.as_str(),
25                doc_kind
26            )],
27        };
28    }
29
30    // Step 3: Attempt deserialization into concrete type
31    let deser_result = match kind {
32        SchemaKind::Module => serde_yaml::from_str::<ModuleDocument>(content).map(|_| ()),
33        SchemaKind::Profile => serde_yaml::from_str::<ProfileDocument>(content).map(|_| ()),
34        SchemaKind::Config => serde_yaml::from_str::<CfgdConfig>(content).map(|_| ()),
35    };
36
37    match deser_result {
38        Ok(()) => ValidationResult {
39            valid: true,
40            errors: vec![],
41        },
42        Err(e) => ValidationResult {
43            valid: false,
44            errors: vec![format!("Deserialization error: {}", e)],
45        },
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_validate_valid_module() {
55        let yaml = r#"
56apiVersion: cfgd.io/v1alpha1
57kind: Module
58metadata:
59  name: nvim
60spec:
61  packages:
62    - name: neovim
63"#;
64        let result = validate_yaml(yaml, SchemaKind::Module);
65        assert!(
66            result.valid,
67            "Expected valid, got errors: {:?}",
68            result.errors
69        );
70    }
71
72    #[test]
73    fn test_validate_valid_profile() {
74        let yaml = r#"
75apiVersion: cfgd.io/v1alpha1
76kind: Profile
77metadata:
78  name: base
79spec:
80  modules: [nvim, tmux]
81"#;
82        let result = validate_yaml(yaml, SchemaKind::Profile);
83        assert!(
84            result.valid,
85            "Expected valid, got errors: {:?}",
86            result.errors
87        );
88    }
89
90    #[test]
91    fn test_validate_invalid_yaml_syntax() {
92        let yaml = "not: [valid: yaml: {{";
93        let result = validate_yaml(yaml, SchemaKind::Module);
94        assert!(!result.valid);
95        assert!(!result.errors.is_empty());
96    }
97
98    #[test]
99    fn test_validate_wrong_kind() {
100        let yaml = r#"
101apiVersion: cfgd.io/v1alpha1
102kind: Profile
103metadata:
104  name: test
105spec: {}
106"#;
107        let result = validate_yaml(yaml, SchemaKind::Module);
108        assert!(!result.valid);
109        assert!(result.errors.iter().any(|e| e.contains("kind")));
110    }
111
112    #[test]
113    fn test_validate_missing_api_version() {
114        let yaml = r#"
115kind: Module
116metadata:
117  name: test
118spec: {}
119"#;
120        let result = validate_yaml(yaml, SchemaKind::Module);
121        assert!(!result.valid);
122        assert!(result.errors.iter().any(|e| e.contains("apiVersion")));
123    }
124}