use crate::Schema;
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InventoryGap {
pub missing: BTreeSet<String>,
pub unknown: BTreeSet<String>,
}
impl InventoryGap {
#[must_use]
pub fn is_empty(&self) -> bool {
self.missing.is_empty() && self.unknown.is_empty()
}
}
impl std::fmt::Display for InventoryGap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.missing.is_empty() {
writeln!(f, "declared by the schema but absent from the inventory:")?;
for name in &self.missing {
writeln!(f, " {name}")?;
}
}
if !self.unknown.is_empty() {
writeln!(f, "named by the inventory but not declared under any root:")?;
for name in &self.unknown {
writeln!(f, " {name}")?;
}
}
Ok(())
}
}
#[must_use]
pub fn descendants_of(schema: &Schema, roots: &[&str]) -> BTreeSet<String> {
let wanted: BTreeSet<String> = roots.iter().map(|r| r.to_ascii_uppercase()).collect();
let mut found = BTreeSet::new();
for name in schema.entity_names() {
let upper = name.to_ascii_uppercase();
if wanted.contains(&upper) {
found.insert(upper);
continue;
}
if schema
.supertypes(name)
.iter()
.any(|s| wanted.contains(&s.to_ascii_uppercase()))
{
found.insert(upper);
}
}
found
}
#[must_use]
pub fn audit_inventory(schema: &Schema, roots: &[&str], inventory: &[&str]) -> InventoryGap {
let expected = descendants_of(schema, roots);
let actual: BTreeSet<String> = inventory.iter().map(|e| e.to_ascii_uppercase()).collect();
InventoryGap {
missing: expected.difference(&actual).cloned().collect(),
unknown: actual.difference(&expected).cloned().collect(),
}
}