use crate::error::Error;
use crate::model::{Node, SUPPORTED_PROTOCOL_MAJOR, SUPPORTED_PROTOCOL_MINOR, Snapshot};
use crate::version;
use std::collections::HashSet;
pub fn validate(snapshot: &Snapshot) -> Result<(), Error> {
let (major, minor, _patch) = version::parse(&snapshot.protocol_version)?;
if major != SUPPORTED_PROTOCOL_MAJOR {
return Err(Error::UnsupportedMajor(major));
}
if major == 0 && minor != SUPPORTED_PROTOCOL_MINOR {
return Err(Error::UnsupportedMinor(minor));
}
let mut seen: HashSet<&Vec<String>> = HashSet::with_capacity(snapshot.tree.len());
for (i, node) in snapshot.tree.iter().enumerate() {
validate_node(node, i)?;
if !seen.insert(&node.path) {
return Err(Error::DuplicatePath(node.path.clone()));
}
}
Ok(())
}
pub fn validate_node(node: &Node, index: usize) -> Result<(), Error> {
if node.path.is_empty() {
return Err(Error::EmptyPath(index));
}
if node.path.iter().any(|seg| seg.is_empty()) {
return Err(Error::EmptyPathSegment(index));
}
for (name, values) in &node.content {
if name.is_empty() {
return Err(Error::EmptyAttrName(index));
}
if values.is_empty() {
return Err(Error::EmptyValueSet(index, name.clone()));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Node;
use std::collections::{BTreeMap, BTreeSet};
fn snap(version: &str, nodes: Vec<Node>) -> Snapshot {
Snapshot {
protocol_version: version.to_string(),
origin_id: "o".into(),
provider_name: "p".into(),
timestamp: 0,
fingerprint: None,
parent_origin_id: None,
labels: BTreeMap::new(),
tree: nodes,
}
}
fn node(path: &[&str]) -> Node {
Node {
path: path.iter().map(|s| s.to_string()).collect(),
content: BTreeMap::new(),
labels: BTreeMap::new(),
refs: Vec::new(),
}
}
#[test]
fn accepts_well_formed() {
let s = snap("0.1.0", vec![node(&["a"]), node(&["b"])]);
assert!(validate(&s).is_ok());
}
#[test]
fn rejects_unsupported_major() {
let s = snap("1.0.0", vec![node(&["a"])]);
assert_eq!(validate(&s), Err(Error::UnsupportedMajor(1)));
}
#[test]
fn rejects_bad_version() {
let s = snap("nope", vec![node(&["a"])]);
assert!(matches!(validate(&s), Err(Error::BadVersion(_))));
}
#[test]
fn rejects_empty_path() {
let s = snap("0.1.0", vec![node(&[])]);
assert_eq!(validate(&s), Err(Error::EmptyPath(0)));
}
#[test]
fn rejects_duplicate_path() {
let mut n = node(&["a"]);
n.content.insert("k".into(), BTreeSet::from(["v".to_string()]));
let s = snap("0.1.0", vec![node(&["a"]), n]);
assert!(matches!(validate(&s), Err(Error::DuplicatePath(_))));
}
#[test]
fn rejects_empty_path_segment() {
let s = snap("0.1.0", vec![node(&["a", ""])]);
assert_eq!(validate(&s), Err(Error::EmptyPathSegment(0)));
}
#[test]
fn rejects_empty_attr_name() {
let mut n = node(&["a"]);
n.content.insert(String::new(), BTreeSet::from(["v".to_string()]));
let s = snap("0.1.0", vec![n]);
assert_eq!(validate(&s), Err(Error::EmptyAttrName(0)));
}
#[test]
fn rejects_empty_value_set() {
let mut n = node(&["a"]);
n.content.insert("k".into(), BTreeSet::new());
let s = snap("0.1.0", vec![n]);
assert_eq!(validate(&s), Err(Error::EmptyValueSet(0, "k".to_string())));
}
#[test]
fn rejects_differing_minor_while_major_zero() {
let s = snap("0.2.0", vec![node(&["a"])]);
assert_eq!(validate(&s), Err(Error::UnsupportedMinor(2)));
}
#[test]
fn accepts_empty_tree() {
let s = snap("0.1.0", vec![]);
assert!(validate(&s).is_ok());
}
}