use std::collections::BTreeMap;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Capability {
ClaimForWorker,
ClaimFromReclaim,
SuspendResumeByCount,
CancelExecution,
CancelFlow,
CancelFlowWaitTimeout,
CancelFlowWaitIndefinite,
StreamRead,
StreamBestEffortLive,
StreamDurableSummary,
DeliverSignal,
ListPendingWaitpoints,
RotateWaitpointHmac,
SeedWaitpointHmac,
ReportUsage,
ReportUsageAdminPath,
ResetBudget,
CreateFlow,
CreateExecution,
StageDependencyEdge,
ApplyDependencyToChild,
PreparableBoot,
SubscribeLeaseHistory,
SubscribeCompletion,
SubscribeSignalDelivery,
Ping,
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CapabilityStatus {
Supported,
Unsupported,
Partial {
note: String,
},
Unknown,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl Version {
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct BackendIdentity {
pub family: &'static str,
pub version: Version,
pub rfc017_stage: &'static str,
}
impl BackendIdentity {
pub const fn new(
family: &'static str,
version: Version,
rfc017_stage: &'static str,
) -> Self {
Self {
family,
version,
rfc017_stage,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct CapabilityMatrix {
pub identity: BackendIdentity,
pub caps: BTreeMap<Capability, CapabilityStatus>,
}
impl CapabilityMatrix {
pub fn new(identity: BackendIdentity) -> Self {
Self {
identity,
caps: BTreeMap::new(),
}
}
pub fn set(&mut self, cap: Capability, status: CapabilityStatus) -> &mut Self {
self.caps.insert(cap, status);
self
}
pub fn get(&self, cap: Capability) -> CapabilityStatus {
self.caps
.get(&cap)
.cloned()
.unwrap_or(CapabilityStatus::Unknown)
}
pub fn supports(&self, cap: Capability) -> bool {
matches!(
self.get(cap),
CapabilityStatus::Supported | CapabilityStatus::Partial { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_new_is_const_and_ordered() {
const V: Version = Version::new(0, 9, 0);
assert_eq!(V.major, 0);
assert_eq!(V.minor, 9);
assert_eq!(V.patch, 0);
assert!(Version::new(0, 10, 0) > V);
assert!(Version::new(0, 9, 1) > V);
assert!(Version::new(1, 0, 0) > V);
}
#[test]
fn backend_identity_new_populates_fields() {
let id = BackendIdentity::new("valkey", Version::new(0, 9, 0), "E-shipped");
assert_eq!(id.family, "valkey");
assert_eq!(id.version, Version::new(0, 9, 0));
assert_eq!(id.rfc017_stage, "E-shipped");
}
#[test]
fn matrix_new_is_empty() {
let m = CapabilityMatrix::new(BackendIdentity::new(
"unknown",
Version::new(0, 0, 0),
"unknown",
));
assert!(m.caps.is_empty());
assert_eq!(m.get(Capability::Ping), CapabilityStatus::Unknown);
assert!(!m.supports(Capability::Ping));
}
#[test]
fn matrix_set_get_supports() {
let mut m = CapabilityMatrix::new(BackendIdentity::new(
"valkey",
Version::new(0, 9, 0),
"E-shipped",
));
m.set(Capability::Ping, CapabilityStatus::Supported)
.set(Capability::CancelExecution, CapabilityStatus::Unsupported)
.set(
Capability::ClaimForWorker,
CapabilityStatus::Partial {
note: "requires with_embedded_scheduler".to_string(),
},
);
assert_eq!(m.get(Capability::Ping), CapabilityStatus::Supported);
assert!(m.supports(Capability::Ping));
assert_eq!(
m.get(Capability::CancelExecution),
CapabilityStatus::Unsupported
);
assert!(!m.supports(Capability::CancelExecution));
assert!(m.supports(Capability::ClaimForWorker));
match m.get(Capability::ClaimForWorker) {
CapabilityStatus::Partial { note } => {
assert!(note.contains("with_embedded_scheduler"));
}
other => panic!("expected Partial, got {other:?}"),
}
let rows = m.caps.len();
assert_eq!(rows, 3);
}
#[test]
fn matrix_set_overwrites_existing() {
let mut m = CapabilityMatrix::new(BackendIdentity::new(
"valkey",
Version::new(0, 9, 0),
"E-shipped",
));
m.set(Capability::Ping, CapabilityStatus::Unsupported);
m.set(Capability::Ping, CapabilityStatus::Supported);
assert_eq!(m.get(Capability::Ping), CapabilityStatus::Supported);
assert_eq!(m.caps.len(), 1);
}
}