#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ClusterSection {
pub enabled: bool,
pub port_base: u16,
pub node_id: String,
pub elect_port_base: u16,
pub peers: Vec<PeerEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PeerEntry {
pub node_id: String,
pub host: String,
pub port: u16,
}
impl PeerEntry {
pub fn parse_one(token: &str) -> Option<Self> {
let (node_id, rest) = token.split_once('@')?;
if node_id.is_empty() {
return None;
}
let colon = rest.rfind(':')?;
let host = &rest[..colon];
if host.is_empty() {
return None;
}
let port: u16 = rest[colon + 1..].parse().ok()?;
Some(PeerEntry {
node_id: node_id.to_string(),
host: host.to_string(),
port,
})
}
pub fn parse_list(s: &str) -> Result<Vec<PeerEntry>, String> {
let mut out = Vec::new();
for raw in s.split(',') {
let token = raw.trim();
if token.is_empty() {
continue;
}
match Self::parse_one(token) {
Some(p) => out.push(p),
None => return Err(token.to_string()),
}
}
Ok(out)
}
}
#[cfg(test)]
mod peer_entry_tests {
use super::*;
#[test]
fn parse_one_basic() {
let p = PeerEntry::parse_one("node-1@10.0.0.1:6004").unwrap();
assert_eq!(p.node_id, "node-1");
assert_eq!(p.host, "10.0.0.1");
assert_eq!(p.port, 6004);
}
#[test]
fn parse_one_dns_host() {
let p = PeerEntry::parse_one("primary@db-east.local:6105").unwrap();
assert_eq!(p.host, "db-east.local");
assert_eq!(p.port, 6105);
}
#[test]
fn parse_one_rejects_empty_id_host_or_bad_port() {
assert!(PeerEntry::parse_one("@host:6004").is_none());
assert!(PeerEntry::parse_one("id@:6004").is_none());
assert!(PeerEntry::parse_one("id@host:NaN").is_none());
assert!(PeerEntry::parse_one("id@host:99999").is_none()); assert!(PeerEntry::parse_one("no-at-or-colon").is_none());
}
#[test]
fn parse_list_three_peers_trim_tolerated() {
let s = "a@1.1.1.1:6004, b@1.1.1.2:6004 ,c@1.1.1.3:6004";
let peers = PeerEntry::parse_list(s).unwrap();
assert_eq!(peers.len(), 3);
assert_eq!(peers[1].node_id, "b");
}
#[test]
fn parse_list_trailing_comma_ok() {
let peers = PeerEntry::parse_list("a@h:1,b@h:2,").unwrap();
assert_eq!(peers.len(), 2);
}
#[test]
fn parse_list_first_bad_token_errs() {
let err = PeerEntry::parse_list("a@h:1,bad-token,c@h:3").unwrap_err();
assert_eq!(err, "bad-token");
}
#[test]
fn parse_list_empty_is_empty() {
assert_eq!(PeerEntry::parse_list("").unwrap(), Vec::<PeerEntry>::new());
assert_eq!(PeerEntry::parse_list(" ").unwrap(), Vec::<PeerEntry>::new());
}
}