use serde::{Deserialize, Serialize};
use shikumi::TieredConfig;
use crate::error::ConfigError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RevoadaConfig {
pub topology: TopologyConfig,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TopologyConfig {
pub strategy: TopologyStrategyKind,
pub min_nodes: u32,
pub grace_period_seconds: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TopologyStrategyKind {
Solo,
Pair,
Quorum3M,
Cluster3MNW,
MeshAllPeers,
Phalanx,
}
impl TieredConfig for RevoadaConfig {
fn bare() -> Self {
Self {
topology: TopologyConfig {
strategy: TopologyStrategyKind::Solo,
min_nodes: 0,
grace_period_seconds: 0,
},
}
}
fn prescribed_default() -> Self {
Self {
topology: TopologyConfig {
strategy: TopologyStrategyKind::Phalanx,
min_nodes: 1,
grace_period_seconds: 10,
},
}
}
fn extend(self, _base: &Self) -> Self {
self
}
}
impl RevoadaConfig {
pub fn validate(&self) -> Result<(), ConfigError> {
if matches!(
self.topology.strategy,
TopologyStrategyKind::Solo | TopologyStrategyKind::MeshAllPeers
) {
Ok(())
} else if self.topology.min_nodes < 1 {
Err(ConfigError::InvalidField {
field: "revoada.topology.min_nodes".into(),
reason: format!(
"strategy {:?} requires min_nodes >= 1",
self.topology.strategy
),
})
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prescribed_default_uses_phalanx() {
let cfg = RevoadaConfig::prescribed_default();
assert_eq!(cfg.topology.strategy, TopologyStrategyKind::Phalanx);
assert_eq!(cfg.topology.min_nodes, 1);
assert_eq!(cfg.topology.grace_period_seconds, 10);
cfg.validate().unwrap();
}
#[test]
fn bare_uses_solo_with_zero_min_nodes() {
let cfg = RevoadaConfig::bare();
assert_eq!(cfg.topology.strategy, TopologyStrategyKind::Solo);
assert_eq!(cfg.topology.min_nodes, 0);
cfg.validate().unwrap(); }
#[test]
fn quorum_with_zero_min_nodes_fails_field_validation() {
let cfg = RevoadaConfig {
topology: TopologyConfig {
strategy: TopologyStrategyKind::Quorum3M,
min_nodes: 0,
grace_period_seconds: 10,
},
};
assert!(cfg.validate().is_err());
}
#[test]
fn strategy_kind_serializes_snake_case() {
let json = serde_json::to_string(&TopologyStrategyKind::Cluster3MNW).unwrap();
assert_eq!(json, "\"cluster3_m_n_w\"");
}
}