///|
/// Checks an invariant at the initial state and after every accepted event.
///
/// The invariant returns `None` when the state is valid or a stable diagnostic
/// message when it is violated. The first violation is therefore the shortest
/// replay prefix that reproduces the failure.
pub fn[P, S] find_first_invariant_failure(
journal : Journal[P],
initial_state : S,
payload_fingerprint : (P) -> String,
state_fingerprint : (S) -> String,
reducer : (S, JournalEvent[P]) -> Transition[S],
invariant : (S) -> String?,
) -> InvariantResult {
match journal.validate(payload_fingerprint) {
Invalid(issue) => return ReplayFailed(InvalidJournal(issue))
Valid(_, _) => ()
}
match invariant(initial_state) {
Some(reason) =>
return Violated(
0,
fingerprint_text(state_fingerprint(initial_state)),
reason,
)
None => ()
}
let mut state = initial_state
for index = 0; index < journal.length(); index = index + 1 {
let event = match journal.get(index) {
Some(event) => event
None => abort("validated journal index disappeared")
}
match reducer(state, event) {
Rejected(reason) => return ReplayFailed(Rejected(event.sequence, reason))
Accepted(next_state) => {
state = next_state
match invariant(state) {
Some(reason) =>
return Violated(
event.sequence,
fingerprint_text(state_fingerprint(state)),
reason,
)
None => ()
}
}
}
}
Holds(journal.length() + 1)
}
///|
/// Returns the shortest journal prefix that reproduces an invariant failure.
pub fn[P, S] minimal_failing_prefix(
journal : Journal[P],
initial_state : S,
payload_fingerprint : (P) -> String,
state_fingerprint : (S) -> String,
reducer : (S, JournalEvent[P]) -> Transition[S],
invariant : (S) -> String?,
) -> Journal[P]? {
match
find_first_invariant_failure(
journal, initial_state, payload_fingerprint, state_fingerprint, reducer, invariant,
) {
Violated(sequence, _, _) => Some(journal.prefix(sequence))
_ => None
}
}