use std::collections::HashMap;
use std::fmt;
use std::time::Duration;
use super::divergence::Divergence;
#[derive(Debug, Clone, Default)]
pub struct RegistryDivergenceCount {
pub detected: usize,
pub repaired: usize,
}
#[derive(Debug, Clone)]
pub struct VerifyReport {
pub applied_index_ok: bool,
pub applied_index_gap: u64,
pub integrity_violations: Vec<Divergence>,
pub integrity_repaired: usize,
pub registry_divergences: HashMap<&'static str, RegistryDivergenceCount>,
pub all_repairs_ok: bool,
pub elapsed: Duration,
}
impl VerifyReport {
pub fn is_acceptable(&self) -> bool {
self.applied_index_ok && self.integrity_violations.is_empty() && self.all_repairs_ok
}
pub fn total_registry_divergences(&self) -> usize {
self.registry_divergences.values().map(|c| c.detected).sum()
}
pub fn total_registry_repairs(&self) -> usize {
self.registry_divergences.values().map(|c| c.repaired).sum()
}
}
impl fmt::Display for VerifyReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"catalog_sanity: applied_index_ok={} gap={} integrity_violations={} \
integrity_repaired={} registry_divergences={} repaired={} \
all_repairs_ok={} elapsed={:?}",
self.applied_index_ok,
self.applied_index_gap,
self.integrity_violations.len(),
self.integrity_repaired,
self.total_registry_divergences(),
self.total_registry_repairs(),
self.all_repairs_ok,
self.elapsed
)?;
for v in &self.integrity_violations {
write!(f, "\n integrity: {v}")?;
}
for (name, count) in &self.registry_divergences {
if count.detected > 0 {
write!(
f,
"\n registry {name}: {} detected, {} repaired",
count.detected, count.repaired
)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_report_is_acceptable() {
let r = VerifyReport {
applied_index_ok: true,
applied_index_gap: 0,
integrity_violations: vec![],
integrity_repaired: 0,
registry_divergences: HashMap::new(),
all_repairs_ok: true,
elapsed: Duration::from_millis(5),
};
assert!(r.is_acceptable());
assert_eq!(r.total_registry_divergences(), 0);
}
#[test]
fn integrity_violation_not_acceptable() {
let r = VerifyReport {
applied_index_ok: true,
applied_index_gap: 0,
integrity_violations: vec![Divergence::new(
super::super::divergence::DivergenceKind::OrphanRow {
kind: "collection",
key: "foo".into(),
expected_parent_kind: "owner",
},
)],
integrity_repaired: 0,
registry_divergences: HashMap::new(),
all_repairs_ok: true,
elapsed: Duration::from_millis(5),
};
assert!(!r.is_acceptable());
}
#[test]
fn applied_index_gap_not_acceptable() {
let r = VerifyReport {
applied_index_ok: false,
applied_index_gap: 42,
integrity_violations: vec![],
integrity_repaired: 0,
registry_divergences: HashMap::new(),
all_repairs_ok: true,
elapsed: Duration::from_millis(5),
};
assert!(!r.is_acceptable());
}
#[test]
fn unrepairable_divergence_not_acceptable() {
let mut d = HashMap::new();
d.insert(
"permissions",
RegistryDivergenceCount {
detected: 3,
repaired: 2,
},
);
let r = VerifyReport {
applied_index_ok: true,
applied_index_gap: 0,
integrity_violations: vec![],
integrity_repaired: 0,
registry_divergences: d,
all_repairs_ok: false,
elapsed: Duration::from_millis(5),
};
assert!(!r.is_acceptable());
assert_eq!(r.total_registry_divergences(), 3);
assert_eq!(r.total_registry_repairs(), 2);
}
#[test]
fn display_formats_all_fields() {
let r = VerifyReport {
applied_index_ok: true,
applied_index_gap: 0,
integrity_violations: vec![],
integrity_repaired: 0,
registry_divergences: HashMap::new(),
all_repairs_ok: true,
elapsed: Duration::from_millis(12),
};
let s = r.to_string();
assert!(s.contains("applied_index_ok=true"));
assert!(s.contains("integrity_violations=0"));
}
}