use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DivergenceKind {
DanglingReference {
from_kind: &'static str,
from_key: String,
to_kind: &'static str,
to_key: String,
},
OrphanRow {
kind: &'static str,
key: String,
expected_parent_kind: &'static str,
},
MissingInRegistry { registry: &'static str, key: String },
ExtraInRegistry { registry: &'static str, key: String },
ValueMismatch {
registry: &'static str,
key: String,
detail: String,
},
TableLoadError { table: &'static str, detail: String },
}
impl DivergenceKind {
pub fn label(&self) -> &'static str {
match self {
Self::DanglingReference { .. } => "dangling_reference",
Self::OrphanRow { .. } => "orphan_row",
Self::MissingInRegistry { .. } => "missing_in_registry",
Self::ExtraInRegistry { .. } => "extra_in_registry",
Self::ValueMismatch { .. } => "value_mismatch",
Self::TableLoadError { .. } => "table_load_error",
}
}
pub fn is_integrity(&self) -> bool {
matches!(
self,
Self::DanglingReference { .. } | Self::OrphanRow { .. } | Self::TableLoadError { .. }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Divergence {
pub kind: DivergenceKind,
}
impl Divergence {
pub fn new(kind: DivergenceKind) -> Self {
Self { kind }
}
}
impl fmt::Display for Divergence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
DivergenceKind::DanglingReference {
from_kind,
from_key,
to_kind,
to_key,
} => write!(
f,
"dangling reference {from_kind}({from_key}) → {to_kind}({to_key}) not found"
),
DivergenceKind::OrphanRow {
kind,
key,
expected_parent_kind,
} => write!(
f,
"orphan row {kind}({key}) — no matching {expected_parent_kind}"
),
DivergenceKind::MissingInRegistry { registry, key } => {
write!(f, "registry {registry}: key {key} missing in memory")
}
DivergenceKind::ExtraInRegistry { registry, key } => {
write!(f, "registry {registry}: key {key} extra in memory")
}
DivergenceKind::ValueMismatch {
registry,
key,
detail,
} => write!(
f,
"registry {registry}: value mismatch for key {key} — {detail}"
),
DivergenceKind::TableLoadError { table, detail } => {
write!(f, "_system table {table} failed to load — {detail}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn labels_are_stable() {
let d = Divergence::new(DivergenceKind::MissingInRegistry {
registry: "permissions",
key: "alice".into(),
});
assert_eq!(d.kind.label(), "missing_in_registry");
assert!(!d.kind.is_integrity());
}
#[test]
fn table_load_error_is_integrity() {
let d = Divergence::new(DivergenceKind::TableLoadError {
table: "continuous_aggregates",
detail: "table does not exist".into(),
});
assert_eq!(d.kind.label(), "table_load_error");
assert!(d.kind.is_integrity());
assert!(d.to_string().contains("continuous_aggregates"));
}
#[test]
fn integrity_flag() {
let d = Divergence::new(DivergenceKind::DanglingReference {
from_kind: "owner",
from_key: "collection:1:foo".into(),
to_kind: "user",
to_key: "bob".into(),
});
assert!(d.kind.is_integrity());
assert!(d.to_string().contains("dangling reference"));
}
}