use std::collections::BTreeSet;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransferActorId(u64);
impl TransferActorId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransferRegionId(u64);
impl TransferRegionId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransferObligationId(u64);
impl TransferObligationId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TransferChildRole {
PathRace,
Writer,
Repair,
Relay,
Mailbox,
Swarm,
Finalizer,
}
impl TransferChildRole {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::PathRace => "path_race",
Self::Writer => "writer",
Self::Repair => "repair",
Self::Relay => "relay",
Self::Mailbox => "mailbox",
Self::Swarm => "swarm",
Self::Finalizer => "finalizer",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferChildRegion {
pub id: TransferRegionId,
pub parent: TransferRegionId,
pub role: TransferChildRole,
}
impl TransferChildRegion {
#[must_use]
pub const fn new(
id: TransferRegionId,
parent: TransferRegionId,
role: TransferChildRole,
) -> Self {
Self { id, parent, role }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferActorTopology {
pub supervisor_region: TransferRegionId,
pub actor_region: TransferRegionId,
pub child_regions: Vec<TransferChildRegion>,
}
impl TransferActorTopology {
#[must_use]
pub const fn new(supervisor_region: TransferRegionId, actor_region: TransferRegionId) -> Self {
Self {
supervisor_region,
actor_region,
child_regions: Vec::new(),
}
}
#[must_use]
pub fn with_child(mut self, id: TransferRegionId, role: TransferChildRole) -> Self {
self.child_regions
.push(TransferChildRegion::new(id, self.actor_region, role));
self
}
pub fn validate(&self) -> Result<(), TransferTopologyError> {
if self.supervisor_region == self.actor_region {
return Err(TransferTopologyError::ActorRegionEqualsSupervisor {
region: self.actor_region,
});
}
let mut seen = BTreeSet::new();
for child in &self.child_regions {
if child.id == self.supervisor_region || child.id == self.actor_region {
return Err(TransferTopologyError::ChildRegionAliasesOwner { child: child.id });
}
if child.parent != self.actor_region {
return Err(TransferTopologyError::DetachedChild {
child: child.id,
parent: child.parent,
expected_parent: self.actor_region,
});
}
if !seen.insert(child.id) {
return Err(TransferTopologyError::DuplicateChild { child: child.id });
}
}
Ok(())
}
#[must_use]
pub fn has_detached_child(&self) -> bool {
self.child_regions
.iter()
.any(|child| child.parent != self.actor_region)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransferTopologyError {
ActorRegionEqualsSupervisor {
region: TransferRegionId,
},
ChildRegionAliasesOwner {
child: TransferRegionId,
},
DetachedChild {
child: TransferRegionId,
parent: TransferRegionId,
expected_parent: TransferRegionId,
},
DuplicateChild {
child: TransferRegionId,
},
}
impl fmt::Display for TransferTopologyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ActorRegionEqualsSupervisor { region } => {
write!(f, "actor region {} equals supervisor region", region.get())
}
Self::ChildRegionAliasesOwner { child } => {
write!(f, "child region {} aliases a topology owner", child.get())
}
Self::DetachedChild {
child,
parent,
expected_parent,
} => write!(
f,
"child region {} is parented by {}, expected {}",
child.get(),
parent.get(),
expected_parent.get()
),
Self::DuplicateChild { child } => {
write!(f, "duplicate child region {}", child.get())
}
}
}
}
impl std::error::Error for TransferTopologyError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn topology_accepts_actor_owned_children() {
let topology =
TransferActorTopology::new(TransferRegionId::new(1), TransferRegionId::new(2))
.with_child(TransferRegionId::new(3), TransferChildRole::PathRace)
.with_child(TransferRegionId::new(4), TransferChildRole::Writer);
assert!(topology.validate().is_ok());
assert!(!topology.has_detached_child());
}
#[test]
fn topology_rejects_detached_children() {
let mut topology =
TransferActorTopology::new(TransferRegionId::new(1), TransferRegionId::new(2));
topology.child_regions.push(TransferChildRegion::new(
TransferRegionId::new(3),
TransferRegionId::new(99),
TransferChildRole::Relay,
));
assert!(matches!(
topology.validate(),
Err(TransferTopologyError::DetachedChild { .. })
));
assert!(topology.has_detached_child());
}
}