use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum DiscoveryConfig {
Etcd(EtcdDiscoveryConfig),
P2p(P2pDiscoveryConfig),
Filesystem(FilesystemDiscoveryConfig),
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct EtcdDiscoveryConfig {
pub cluster_id: String,
#[serde(default = "default_etcd_endpoints")]
pub endpoints: Vec<String>,
#[serde(default = "default_etcd_ttl")]
#[validate(range(min = 10, max = 600))]
pub ttl_secs: u64,
#[serde(default = "default_operation_timeout")]
pub operation_timeout_secs: u64,
#[serde(default = "default_max_retries")]
#[validate(range(min = 0, max = 10))]
pub max_retries: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct P2pDiscoveryConfig {
pub cluster_id: String,
#[serde(default)]
pub listen_port: u16,
#[serde(default)]
pub bootstrap_peers: Vec<String>,
#[serde(default = "default_replication_factor")]
pub replication_factor: usize,
#[serde(default)]
pub enable_mdns: bool,
#[serde(default = "default_record_ttl")]
pub record_ttl_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilesystemDiscoveryConfig {
pub path: PathBuf,
}
fn default_etcd_endpoints() -> Vec<String> {
vec!["http://localhost:2379".to_string()]
}
fn default_etcd_ttl() -> u64 {
60
}
fn default_operation_timeout() -> u64 {
30
}
fn default_max_retries() -> u32 {
3
}
fn default_replication_factor() -> usize {
3
}
fn default_record_ttl() -> u64 {
600
}
impl Default for EtcdDiscoveryConfig {
fn default() -> Self {
Self {
cluster_id: String::new(),
endpoints: default_etcd_endpoints(),
ttl_secs: default_etcd_ttl(),
operation_timeout_secs: default_operation_timeout(),
max_retries: default_max_retries(),
}
}
}
impl Default for P2pDiscoveryConfig {
fn default() -> Self {
Self {
cluster_id: String::new(),
listen_port: 0,
bootstrap_peers: Vec::new(),
replication_factor: default_replication_factor(),
enable_mdns: false,
record_ttl_secs: default_record_ttl(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_etcd_config() {
let json = r#"{
"type": "etcd",
"cluster_id": "test-cluster",
"endpoints": ["http://etcd1:2379"],
"ttl_secs": 120
}"#;
let config: DiscoveryConfig = serde_json::from_str(json).unwrap();
match config {
DiscoveryConfig::Etcd(etcd) => {
assert_eq!(etcd.cluster_id, "test-cluster");
assert_eq!(etcd.endpoints, vec!["http://etcd1:2379"]);
assert_eq!(etcd.ttl_secs, 120);
assert_eq!(etcd.operation_timeout_secs, 30); assert_eq!(etcd.max_retries, 3); }
_ => panic!("Expected Etcd config"),
}
}
#[test]
fn test_deserialize_p2p_config() {
let json = r#"{
"type": "p2p",
"cluster_id": "test-cluster",
"listen_port": 4001,
"bootstrap_peers": ["192.168.1.10:4001"],
"enable_mdns": true
}"#;
let config: DiscoveryConfig = serde_json::from_str(json).unwrap();
match config {
DiscoveryConfig::P2p(p2p) => {
assert_eq!(p2p.cluster_id, "test-cluster");
assert_eq!(p2p.listen_port, 4001);
assert_eq!(p2p.bootstrap_peers, vec!["192.168.1.10:4001"]);
assert!(p2p.enable_mdns);
assert_eq!(p2p.replication_factor, 3); assert_eq!(p2p.record_ttl_secs, 600); }
_ => panic!("Expected P2p config"),
}
}
#[test]
fn test_deserialize_filesystem_config() {
let json = r#"{
"type": "filesystem",
"path": "/tmp/discovery.json"
}"#;
let config: DiscoveryConfig = serde_json::from_str(json).unwrap();
match config {
DiscoveryConfig::Filesystem(fs) => {
assert_eq!(fs.path, PathBuf::from("/tmp/discovery.json"));
}
_ => panic!("Expected Filesystem config"),
}
}
#[test]
fn test_serialize_etcd_config() {
let config = DiscoveryConfig::Etcd(EtcdDiscoveryConfig {
cluster_id: "my-cluster".to_string(),
endpoints: vec!["http://localhost:2379".to_string()],
ttl_secs: 60,
operation_timeout_secs: 30,
max_retries: 3,
});
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains(r#""type":"etcd""#));
assert!(json.contains(r#""cluster_id":"my-cluster""#));
}
#[test]
fn test_etcd_default() {
let config = EtcdDiscoveryConfig::default();
assert!(config.cluster_id.is_empty());
assert_eq!(config.endpoints, vec!["http://localhost:2379"]);
assert_eq!(config.ttl_secs, 60);
assert_eq!(config.operation_timeout_secs, 30);
assert_eq!(config.max_retries, 3);
}
#[test]
fn test_p2p_default() {
let config = P2pDiscoveryConfig::default();
assert!(config.cluster_id.is_empty());
assert_eq!(config.listen_port, 0);
assert!(config.bootstrap_peers.is_empty());
assert_eq!(config.replication_factor, 3);
assert!(!config.enable_mdns);
assert_eq!(config.record_ttl_secs, 600);
}
}