use std::fmt;
pub use melin_transport_core::durability_policy::{
Clause, CursorView, EvalStatus, Level, MAX_CLUSTER_SIZE, Policy, PolicyError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum DurabilityMode {
Local,
Hybrid,
DurablyReplicated,
}
impl DurabilityMode {
pub fn to_policy(self) -> Policy {
let clauses = match self {
DurabilityMode::Local => vec![Clause {
count: 1,
level: Level::Persisted,
}],
DurabilityMode::Hybrid => vec![
Clause {
count: 1,
level: Level::Persisted,
},
Clause {
count: 2,
level: Level::InMemory,
},
],
DurabilityMode::DurablyReplicated => vec![Clause {
count: 2,
level: Level::Persisted,
}],
};
Policy::new(clauses)
.expect("DurabilityMode::to_policy: hand-constructed clauses must validate")
}
pub fn as_str(self) -> &'static str {
match self {
DurabilityMode::Local => "local",
DurabilityMode::Hybrid => "hybrid",
DurabilityMode::DurablyReplicated => "durably-replicated",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"local" => Some(DurabilityMode::Local),
"hybrid" => Some(DurabilityMode::Hybrid),
"durably-replicated" => Some(DurabilityMode::DurablyReplicated),
_ => None,
}
}
pub fn as_u8(self) -> u8 {
match self {
DurabilityMode::Local => 0,
DurabilityMode::Hybrid => 1,
DurabilityMode::DurablyReplicated => 2,
}
}
pub fn from_u8(b: u8) -> Option<Self> {
match b {
0 => Some(DurabilityMode::Local),
1 => Some(DurabilityMode::Hybrid),
2 => Some(DurabilityMode::DurablyReplicated),
_ => None,
}
}
}
impl fmt::Display for DurabilityMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn durability_mode_u8_round_trip() {
for m in [
DurabilityMode::Local,
DurabilityMode::Hybrid,
DurabilityMode::DurablyReplicated,
] {
assert_eq!(DurabilityMode::from_u8(m.as_u8()), Some(m));
}
for b in [3, 4, 255] {
assert_eq!(DurabilityMode::from_u8(b), None);
}
}
#[test]
fn durability_mode_parse_matches_as_str() {
for m in [
DurabilityMode::Local,
DurabilityMode::Hybrid,
DurabilityMode::DurablyReplicated,
] {
assert_eq!(DurabilityMode::parse(m.as_str()), Some(m));
}
for bad in ["", "LOCAL", "hyb", "fast", "durably_replicated"] {
assert_eq!(DurabilityMode::parse(bad), None, "{bad:?} should not parse");
}
}
#[test]
fn mode_local_builds_persisted_ge_1() {
let p = DurabilityMode::Local.to_policy();
assert_eq!(p.clauses().len(), 1);
let c = p.clauses()[0];
assert_eq!(c.level, Level::Persisted);
assert_eq!(c.count, 1);
}
#[test]
fn mode_hybrid_builds_persisted_ge_1_and_in_memory_ge_2() {
let p = DurabilityMode::Hybrid.to_policy();
assert_eq!(p.clauses().len(), 2);
let persisted = p
.clauses()
.iter()
.find(|c| c.level == Level::Persisted)
.expect("persisted clause");
assert_eq!(persisted.count, 1);
let in_mem = p
.clauses()
.iter()
.find(|c| c.level == Level::InMemory)
.expect("in_memory clause");
assert_eq!(in_mem.count, 2);
}
#[test]
fn mode_durably_replicated_builds_persisted_ge_2() {
let p = DurabilityMode::DurablyReplicated.to_policy();
assert_eq!(p.clauses().len(), 1);
let c = p.clauses()[0];
assert_eq!(c.level, Level::Persisted);
assert_eq!(c.count, 2);
}
#[test]
fn mode_hybrid_fails_closed_on_single_node() {
let p = DurabilityMode::Hybrid.to_policy();
let nodes = [[100u64, 100u64]];
let v = CursorView::new(&nodes);
assert_eq!(p.evaluate(&v), 0, "hybrid stalls on single-node view");
}
#[test]
fn mode_hybrid_advances_with_two_nodes_acking_in_memory() {
let p = DurabilityMode::Hybrid.to_policy();
let nodes = [[100u64, 100u64], [80u64, 0u64]];
let v = CursorView::new(&nodes);
assert_eq!(p.evaluate(&v), 80);
}
}