use crate::types::NodeId;
const DEFAULT_ELECTION_MIN: u32 = 10;
const DEFAULT_ELECTION_MAX: u32 = 20;
const DEFAULT_HEARTBEAT: u32 = 3;
const DEFAULT_MAX_BATCH: usize = 64;
#[derive(Clone, Debug)]
pub struct RaftConfig {
pub(crate) id: NodeId,
pub(crate) peers: Vec<NodeId>,
pub(crate) election_timeout_min: u32,
pub(crate) election_timeout_max: u32,
pub(crate) heartbeat_interval: u32,
pub(crate) max_batch: usize,
pub(crate) seed: u64,
}
impl RaftConfig {
#[must_use]
pub fn new(id: NodeId, peers: impl IntoIterator<Item = NodeId>) -> Self {
let peers = peers.into_iter().filter(|&p| p != id).collect();
Self {
id,
peers,
election_timeout_min: DEFAULT_ELECTION_MIN,
election_timeout_max: DEFAULT_ELECTION_MAX,
heartbeat_interval: DEFAULT_HEARTBEAT,
max_batch: DEFAULT_MAX_BATCH,
seed: id,
}
}
#[must_use]
pub fn single(id: NodeId) -> Self {
Self::new(id, [])
}
#[must_use]
pub fn with_election_timeout(mut self, min: u32, max: u32) -> Self {
let min = min.max(1);
self.election_timeout_min = min;
self.election_timeout_max = max.max(min);
self
}
#[must_use]
pub fn with_heartbeat_interval(mut self, interval: u32) -> Self {
self.heartbeat_interval = interval.max(1);
self
}
#[must_use]
pub fn with_max_batch(mut self, max_batch: usize) -> Self {
self.max_batch = max_batch.max(1);
self
}
#[must_use]
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
#[inline]
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
#[inline]
#[must_use]
pub fn peers(&self) -> &[NodeId] {
&self.peers
}
#[inline]
#[must_use]
pub fn election_timeout(&self) -> (u32, u32) {
(self.election_timeout_min, self.election_timeout_max)
}
#[inline]
#[must_use]
pub fn heartbeat_interval(&self) -> u32 {
self.heartbeat_interval
}
#[inline]
#[must_use]
pub fn max_batch(&self) -> usize {
self.max_batch
}
#[inline]
#[must_use]
pub fn seed(&self) -> u64 {
self.seed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_filters_self_from_peers() {
let cfg = RaftConfig::new(1, [1, 2, 3]);
assert_eq!(cfg.peers(), &[2, 3]);
}
#[test]
fn test_defaults_are_applied() {
let cfg = RaftConfig::new(2, [1]);
assert_eq!(
cfg.election_timeout(),
(DEFAULT_ELECTION_MIN, DEFAULT_ELECTION_MAX)
);
assert_eq!(cfg.heartbeat_interval(), DEFAULT_HEARTBEAT);
assert_eq!(cfg.max_batch(), DEFAULT_MAX_BATCH);
assert_eq!(cfg.seed(), 2);
}
#[test]
fn test_max_batch_is_at_least_one() {
assert_eq!(RaftConfig::single(1).with_max_batch(0).max_batch(), 1);
assert_eq!(RaftConfig::single(1).with_max_batch(128).max_batch(), 128);
}
#[test]
fn test_single_has_no_peers() {
assert!(RaftConfig::single(9).peers().is_empty());
}
#[test]
fn test_election_timeout_normalises_bounds() {
let cfg = RaftConfig::single(1).with_election_timeout(0, 0);
assert_eq!(cfg.election_timeout(), (1, 1));
let swapped = RaftConfig::single(1).with_election_timeout(30, 10);
assert_eq!(swapped.election_timeout(), (30, 30));
}
#[test]
fn test_heartbeat_interval_is_at_least_one() {
assert_eq!(
RaftConfig::single(1)
.with_heartbeat_interval(0)
.heartbeat_interval(),
1
);
}
#[test]
fn test_builder_chains() {
let cfg = RaftConfig::new(1, [2, 3])
.with_election_timeout(15, 30)
.with_heartbeat_interval(5)
.with_seed(7);
assert_eq!(cfg.election_timeout(), (15, 30));
assert_eq!(cfg.heartbeat_interval(), 5);
assert_eq!(cfg.seed(), 7);
}
}