#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TokenConsumeOutcome {
Proceed,
Replay,
Invalid,
}
#[must_use]
pub fn classify_token_consume(
changed: usize,
found: bool,
already_consumed: bool,
binding_ok: bool,
) -> TokenConsumeOutcome {
if changed == 1 {
return TokenConsumeOutcome::Proceed;
}
if !found || !binding_ok {
return TokenConsumeOutcome::Invalid;
}
if already_consumed {
return TokenConsumeOutcome::Replay;
}
TokenConsumeOutcome::Invalid
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn consume_winner_proceeds() {
assert_eq!(
classify_token_consume(1, true, false, true),
TokenConsumeOutcome::Proceed
);
}
#[test]
fn consume_loser_of_race_sees_replay() {
assert_eq!(
classify_token_consume(0, true, true, true),
TokenConsumeOutcome::Replay
);
}
#[test]
fn consume_unknown_token_is_invalid() {
assert_eq!(
classify_token_consume(0, false, false, false),
TokenConsumeOutcome::Invalid
);
}
#[test]
fn consume_binding_mismatch_is_invalid() {
assert_eq!(
classify_token_consume(0, true, false, false),
TokenConsumeOutcome::Invalid
);
}
#[test]
fn consume_expired_unconsumed_is_invalid() {
assert_eq!(
classify_token_consume(0, true, false, true),
TokenConsumeOutcome::Invalid
);
}
#[test]
fn consume_never_double_proceeds() {
for found in [false, true] {
for consumed in [false, true] {
for binding in [false, true] {
assert_ne!(
classify_token_consume(0, found, consumed, binding),
TokenConsumeOutcome::Proceed,
"changed==0 must never proceed \
(found={found} consumed={consumed} binding={binding})"
);
}
}
}
}
#[test]
fn changed_greater_than_one_is_conservatively_invalid() {
for bad in [2usize, 100] {
assert_ne!(
classify_token_consume(bad, true, false, true),
TokenConsumeOutcome::Proceed,
"changed={bad} must not Proceed"
);
}
}
#[test]
fn not_found_always_invalid_regardless_of_other_flags() {
for consumed in [false, true] {
for binding in [false, true] {
assert_eq!(
classify_token_consume(0, false, consumed, binding),
TokenConsumeOutcome::Invalid,
"not found must always be Invalid"
);
}
}
}
}