ledvar_core/
wellformed.rs1use crate::error::Error;
4use crate::model::{Node, SUPPORTED_PROTOCOL_MAJOR, SUPPORTED_PROTOCOL_MINOR, Snapshot};
5use crate::version;
6use std::collections::HashSet;
7
8pub fn validate(snapshot: &Snapshot) -> Result<(), Error> {
19 let (major, minor, _patch) = version::parse(&snapshot.protocol_version)?;
20 if major != SUPPORTED_PROTOCOL_MAJOR {
21 return Err(Error::UnsupportedMajor(major));
22 }
23 if major == 0 && minor != SUPPORTED_PROTOCOL_MINOR {
26 return Err(Error::UnsupportedMinor(minor));
27 }
28
29 let mut seen: HashSet<&Vec<String>> = HashSet::with_capacity(snapshot.tree.len());
33 for (i, node) in snapshot.tree.iter().enumerate() {
34 validate_node(node, i)?;
35 if !seen.insert(&node.path) {
36 return Err(Error::DuplicatePath(node.path.clone()));
37 }
38 }
39 Ok(())
40}
41
42pub fn validate_node(node: &Node, index: usize) -> Result<(), Error> {
49 if node.path.is_empty() {
50 return Err(Error::EmptyPath(index));
51 }
52 if node.path.iter().any(|seg| seg.is_empty()) {
53 return Err(Error::EmptyPathSegment(index));
54 }
55 for (name, values) in &node.content {
56 if name.is_empty() {
57 return Err(Error::EmptyAttrName(index));
58 }
59 if values.is_empty() {
60 return Err(Error::EmptyValueSet(index, name.clone()));
61 }
62 }
63 Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::model::Node;
70 use std::collections::{BTreeMap, BTreeSet};
71
72 fn snap(version: &str, nodes: Vec<Node>) -> Snapshot {
73 Snapshot {
74 protocol_version: version.to_string(),
75 origin_id: "o".into(),
76 provider_name: "p".into(),
77 timestamp: 0,
78 fingerprint: None,
79 parent_origin_id: None,
80 labels: BTreeMap::new(),
81 tree: nodes,
82 }
83 }
84
85 fn node(path: &[&str]) -> Node {
86 Node {
87 path: path.iter().map(|s| s.to_string()).collect(),
88 content: BTreeMap::new(),
89 labels: BTreeMap::new(),
90 refs: Vec::new(),
91 }
92 }
93
94 #[test]
95 fn accepts_well_formed() {
96 let s = snap("0.1.0", vec![node(&["a"]), node(&["b"])]);
97 assert!(validate(&s).is_ok());
98 }
99
100 #[test]
101 fn rejects_unsupported_major() {
102 let s = snap("1.0.0", vec![node(&["a"])]);
103 assert_eq!(validate(&s), Err(Error::UnsupportedMajor(1)));
104 }
105
106 #[test]
107 fn rejects_bad_version() {
108 let s = snap("nope", vec![node(&["a"])]);
109 assert!(matches!(validate(&s), Err(Error::BadVersion(_))));
110 }
111
112 #[test]
113 fn rejects_empty_path() {
114 let s = snap("0.1.0", vec![node(&[])]);
115 assert_eq!(validate(&s), Err(Error::EmptyPath(0)));
116 }
117
118 #[test]
119 fn rejects_duplicate_path() {
120 let mut n = node(&["a"]);
121 n.content.insert("k".into(), BTreeSet::from(["v".to_string()]));
122 let s = snap("0.1.0", vec![node(&["a"]), n]);
124 assert!(matches!(validate(&s), Err(Error::DuplicatePath(_))));
125 }
126
127 #[test]
128 fn rejects_empty_path_segment() {
129 let s = snap("0.1.0", vec![node(&["a", ""])]);
130 assert_eq!(validate(&s), Err(Error::EmptyPathSegment(0)));
131 }
132
133 #[test]
134 fn rejects_empty_attr_name() {
135 let mut n = node(&["a"]);
136 n.content.insert(String::new(), BTreeSet::from(["v".to_string()]));
137 let s = snap("0.1.0", vec![n]);
138 assert_eq!(validate(&s), Err(Error::EmptyAttrName(0)));
139 }
140
141 #[test]
142 fn rejects_empty_value_set() {
143 let mut n = node(&["a"]);
144 n.content.insert("k".into(), BTreeSet::new());
145 let s = snap("0.1.0", vec![n]);
146 assert_eq!(validate(&s), Err(Error::EmptyValueSet(0, "k".to_string())));
147 }
148
149 #[test]
150 fn rejects_differing_minor_while_major_zero() {
151 let s = snap("0.2.0", vec![node(&["a"])]);
153 assert_eq!(validate(&s), Err(Error::UnsupportedMinor(2)));
154 }
155
156 #[test]
157 fn accepts_empty_tree() {
158 let s = snap("0.1.0", vec![]);
160 assert!(validate(&s).is_ok());
161 }
162}