use serde::{Deserialize, Serialize};
use super::AuditSequence;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "defect")]
pub enum AuditChainDefect {
DoesNotStartAtTheBeginning { found: AuditSequence },
DigestAltered { at: AuditSequence },
UnexpectedRoot { at: AuditSequence },
SequenceBroken {
expected: AuditSequence,
found: AuditSequence,
},
LinkBroken { at: AuditSequence },
ForeignCeremony { at: AuditSequence },
}
impl AuditChainDefect {
#[must_use]
pub fn explain(self) -> String {
match self {
Self::DoesNotStartAtTheBeginning { found } => format!(
"the journal opens at position {}, so records were removed from the front",
found.value()
),
Self::DigestAltered { at } => format!(
"the record at position {} no longer produces its own digest",
at.value()
),
Self::UnexpectedRoot { at } => format!(
"the record at position {} disagrees with the rest of the journal about \
whether it has a predecessor",
at.value()
),
Self::SequenceBroken { expected, found } => format!(
"position {} was expected and position {} was found, so records were \
removed from the middle or reordered",
expected.value(),
found.value()
),
Self::LinkBroken { at } => format!(
"the record at position {} names a predecessor digest that is not the \
digest of the record before it",
at.value()
),
Self::ForeignCeremony { at } => format!(
"the record at position {} belongs to another ceremony",
at.value()
),
}
}
#[must_use]
pub fn at(self) -> AuditSequence {
match self {
Self::DoesNotStartAtTheBeginning { found } | Self::SequenceBroken { found, .. } => {
found
}
Self::DigestAltered { at }
| Self::UnexpectedRoot { at }
| Self::LinkBroken { at }
| Self::ForeignCeremony { at } => at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn at(value: u64) -> AuditSequence {
AuditSequence::new(value).unwrap()
}
#[test]
fn every_defect_names_the_position_it_was_found_at() {
for defect in [
AuditChainDefect::DoesNotStartAtTheBeginning { found: at(3) },
AuditChainDefect::DigestAltered { at: at(3) },
AuditChainDefect::UnexpectedRoot { at: at(3) },
AuditChainDefect::SequenceBroken {
expected: at(2),
found: at(3),
},
AuditChainDefect::LinkBroken { at: at(3) },
AuditChainDefect::ForeignCeremony { at: at(3) },
] {
assert_eq!(defect.at(), at(3), "{defect:?}");
assert!(
defect.explain().contains('3'),
"{defect:?} explains itself without naming where: {}",
defect.explain()
);
}
}
#[test]
fn a_broken_sequence_names_both_positions() {
let explanation = AuditChainDefect::SequenceBroken {
expected: at(2),
found: at(5),
}
.explain();
assert!(explanation.contains('2'), "{explanation}");
assert!(explanation.contains('5'), "{explanation}");
}
}