use std::fmt;
use std::sync::OnceLock;
use crate::identity::{Compat, Identity, Run};
#[derive(Debug)]
pub struct Allegiance {
ours: Compat,
owner: OnceLock<Run>,
}
impl Allegiance {
#[must_use]
pub const fn new(ours: Compat) -> Self {
Self {
ours,
owner: OnceLock::new(),
}
}
#[must_use]
pub fn to(ours: Compat, owner: Run) -> Self {
let allegiance = Self::new(ours);
let _ = allegiance.owner.set(owner);
allegiance
}
#[must_use]
pub fn observe(&self, owner: Identity) -> Standing {
if owner.compat != self.ours {
return Standing::Superseded(Because::Incompatible {
ours: self.ours,
owner: owner.compat,
});
}
match self.owner.get() {
Some(&sworn) if sworn != owner.run => Standing::Superseded(Because::NewRun {
sworn,
owner: owner.run,
}),
Some(_) => Standing::Current,
None => {
let _ = self.owner.set(owner.run);
Standing::Current
}
}
}
#[must_use]
pub fn owner(&self) -> Option<Run> {
self.owner.get().copied()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Standing {
Current,
Superseded(Because),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Because {
Incompatible {
ours: Compat,
owner: Compat,
},
NewRun {
sworn: Run,
owner: Run,
},
}
impl fmt::Display for Because {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Incompatible { ours, owner } => write!(
f,
"owner speaks {} and this helper speaks {}",
owner.get(),
ours.get()
),
Self::NewRun { sworn, owner } => write!(
f,
"owner is run {} and this helper serves run {}",
owner.get(),
sworn.get()
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const OURS: Compat = Compat::from_raw(18);
fn owner(run: u64, compat: u64) -> Identity {
Identity::new(Run::from_raw(run), Compat::from_raw(compat))
}
#[test]
fn the_first_owner_seen_is_adopted_not_refused() {
let allegiance = Allegiance::new(OURS);
assert_eq!(allegiance.observe(owner(7, 18)), Standing::Current);
assert_eq!(allegiance.owner(), Some(Run::from_raw(7)));
}
#[test]
fn the_same_owner_keeps_the_helper() {
let allegiance = Allegiance::new(OURS);
assert_eq!(allegiance.observe(owner(7, 18)), Standing::Current);
assert_eq!(allegiance.observe(owner(7, 18)), Standing::Current);
}
#[test]
fn a_restarted_owner_supersedes_the_helper() {
let allegiance = Allegiance::to(OURS, Run::from_raw(7));
assert_eq!(
allegiance.observe(owner(9, 18)),
Standing::Superseded(Because::NewRun {
sworn: Run::from_raw(7),
owner: Run::from_raw(9),
})
);
}
#[test]
fn a_spawn_time_hint_catches_an_orphan_on_its_very_first_handshake() {
let allegiance = Allegiance::to(OURS, Run::from_raw(7));
assert!(matches!(
allegiance.observe(owner(9, 18)),
Standing::Superseded(_)
));
}
#[test]
fn incompatibility_outranks_the_run_check() {
let allegiance = Allegiance::to(OURS, Run::from_raw(7));
assert_eq!(
allegiance.observe(owner(7, 17)),
Standing::Superseded(Because::Incompatible {
ours: OURS,
owner: Compat::from_raw(17),
})
);
}
}