use std::fmt;
use crate::tier::tier_trait::BackendKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CapabilityFlags(u16);
impl CapabilityFlags {
pub const EMPTY: CapabilityFlags = CapabilityFlags(0);
pub const IN_MEMORY: CapabilityFlags = CapabilityFlags(1 << 0);
pub const VOLATILE: CapabilityFlags = CapabilityFlags(1 << 1);
pub const PERSISTENT: CapabilityFlags = CapabilityFlags(1 << 2);
pub const SHARED: CapabilityFlags = CapabilityFlags(1 << 3);
pub const AUTHORITATIVE: CapabilityFlags = CapabilityFlags(1 << 4);
pub const BLOCKING_IO: CapabilityFlags = CapabilityFlags(1 << 5);
pub const ATOMIC_WRITE_OR_ERROR: CapabilityFlags = CapabilityFlags(1 << 6);
pub const ALL: [CapabilityFlags; 7] = [
Self::IN_MEMORY,
Self::VOLATILE,
Self::PERSISTENT,
Self::SHARED,
Self::AUTHORITATIVE,
Self::BLOCKING_IO,
Self::ATOMIC_WRITE_OR_ERROR,
];
#[must_use]
pub const fn bits(self) -> u16 {
self.0
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[must_use]
pub const fn intersects(self, other: Self) -> bool {
self.0 & other.0 != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
CapabilityFlags(self.0 | other.0)
}
#[must_use]
pub const fn without(self, other: Self) -> Self {
CapabilityFlags(self.0 & !other.0)
}
pub fn iter(self) -> impl Iterator<Item = CapabilityFlags> {
Self::ALL.into_iter().filter(move |f| self.contains(*f))
}
#[must_use]
pub fn names(self) -> Vec<&'static str> {
self.iter().map(Self::name).collect()
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::IN_MEMORY => "in_memory",
Self::VOLATILE => "volatile",
Self::PERSISTENT => "persistent",
Self::SHARED => "shared",
Self::AUTHORITATIVE => "authoritative",
Self::BLOCKING_IO => "blocking_io",
Self::ATOMIC_WRITE_OR_ERROR => "atomic_write_or_error",
_ => "unknown",
}
}
}
impl std::ops::BitOr for CapabilityFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl std::ops::BitOrAssign for CapabilityFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl fmt::Display for CapabilityFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names = self.names();
if names.is_empty() {
f.write_str("none")
} else {
f.write_str(&names.join("|"))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OperationalState {
Unbound,
Unavailable,
Healthy,
Degraded,
Recovering,
Reconciling,
}
impl OperationalState {
pub const ALL: [OperationalState; 6] = [
Self::Unbound,
Self::Unavailable,
Self::Healthy,
Self::Degraded,
Self::Recovering,
Self::Reconciling,
];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Unbound => "unbound",
Self::Unavailable => "unavailable",
Self::Healthy => "healthy",
Self::Degraded => "degraded",
Self::Recovering => "recovering",
Self::Reconciling => "reconciling",
}
}
#[must_use]
pub fn is_serving(self) -> bool {
matches!(self, Self::Healthy | Self::Degraded)
}
}
impl fmt::Display for OperationalState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DurabilityClass {
Volatile,
Delegated,
Verified,
}
impl DurabilityClass {
pub const ALL: [DurabilityClass; 3] = [Self::Volatile, Self::Delegated, Self::Verified];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Volatile => "volatile",
Self::Delegated => "delegated",
Self::Verified => "verified",
}
}
#[must_use]
pub fn survives_restart(self) -> bool {
matches!(self, Self::Delegated | Self::Verified)
}
}
impl fmt::Display for DurabilityClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TierCapability {
pub backend: BackendKind,
pub flags: CapabilityFlags,
pub state: OperationalState,
pub durability: DurabilityClass,
}
impl TierCapability {
#[must_use]
pub const fn unbound() -> Self {
Self {
backend: BackendKind::Unavailable,
flags: CapabilityFlags::EMPTY,
state: OperationalState::Unbound,
durability: DurabilityClass::Volatile,
}
}
#[must_use]
pub const fn new(
backend: BackendKind,
flags: CapabilityFlags,
state: OperationalState,
durability: DurabilityClass,
) -> Self {
Self {
backend,
flags,
state,
durability,
}
}
#[must_use]
pub const fn is_bound(&self) -> bool {
!matches!(self.state, OperationalState::Unbound)
}
#[must_use]
pub const fn is_authoritative(&self) -> bool {
self.flags.contains(CapabilityFlags::AUTHORITATIVE)
}
#[must_use]
pub const fn is_blocking_io(&self) -> bool {
self.flags.contains(CapabilityFlags::BLOCKING_IO)
}
#[must_use]
pub const fn survives_restart(&self) -> bool {
matches!(
self.durability,
DurabilityClass::Delegated | DurabilityClass::Verified
)
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"{}[{}] state={} durability={}",
self.backend, self.flags, self.state, self.durability
)
}
}
#[must_use]
pub fn all_state_names() -> Vec<&'static str> {
let mut names: Vec<&'static str> = Vec::new();
names.extend(CapabilityFlags::ALL.iter().map(|f| f.name()));
names.extend(OperationalState::ALL.iter().map(|s| s.name()));
names.extend(DurabilityClass::ALL.iter().map(|d| d.name()));
names.sort_unstable();
names.dedup();
names
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flags_compose() {
let c = CapabilityFlags::IN_MEMORY | CapabilityFlags::VOLATILE;
assert!(c.contains(CapabilityFlags::IN_MEMORY));
assert!(c.contains(CapabilityFlags::VOLATILE));
assert!(!c.contains(CapabilityFlags::PERSISTENT));
let authority =
CapabilityFlags::PERSISTENT | CapabilityFlags::SHARED | CapabilityFlags::AUTHORITATIVE;
assert!(authority.contains(CapabilityFlags::PERSISTENT));
assert!(authority.contains(CapabilityFlags::AUTHORITATIVE));
}
#[test]
fn without_removes_only_the_named_flag() {
let c = CapabilityFlags::IN_MEMORY | CapabilityFlags::VOLATILE;
let c = c.without(CapabilityFlags::VOLATILE);
assert!(c.contains(CapabilityFlags::IN_MEMORY));
assert!(!c.contains(CapabilityFlags::VOLATILE));
}
#[test]
fn every_contract_capability_name_is_representable() {
let have = all_state_names();
for required in [
"unbound",
"unavailable",
"volatile",
"persistent",
"authoritative",
"shared",
"degraded",
"recovering",
"in_memory",
] {
assert!(
have.contains(&required),
"{required:?} is required by the contract but no axis can report it"
);
}
}
#[test]
fn unbound_is_distinct_from_unavailable() {
let unbound = TierCapability::unbound();
let unavailable = TierCapability::new(
BackendKind::InMemory,
CapabilityFlags::IN_MEMORY,
OperationalState::Unavailable,
DurabilityClass::Volatile,
);
assert!(!unbound.is_bound());
assert!(unavailable.is_bound());
assert_ne!(unbound.state, unavailable.state);
}
#[test]
fn delegating_durability_is_not_claiming_verified() {
let d = TierCapability::new(
BackendKind::Sled,
CapabilityFlags::PERSISTENT,
OperationalState::Healthy,
DurabilityClass::Delegated,
);
assert!(d.survives_restart());
assert_ne!(d.durability, DurabilityClass::Verified);
}
#[test]
fn capability_summary_carries_no_key_material() {
let c = TierCapability::new(
BackendKind::Sled,
CapabilityFlags::PERSISTENT,
OperationalState::Healthy,
DurabilityClass::Verified,
);
let s = c.summary();
assert!(s.contains("sled"));
assert!(s.contains("persistent"));
assert!(s.contains("verified"));
}
}