Skip to main content

forge_core/config/
cluster.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5use super::types::DurationStr;
6
7/// Cluster configuration.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[non_exhaustive]
10pub struct ClusterConfig {
11    #[serde(default = "default_cluster_name")]
12    pub name: String,
13
14    #[serde(default)]
15    pub discovery: DiscoveryMethod,
16
17    /// Heartbeat interval duration (e.g. "5s", "10s").
18    #[serde(default = "default_heartbeat_interval")]
19    pub heartbeat_interval: DurationStr,
20
21    /// Threshold duration for marking nodes as dead (e.g. "15s", "30s").
22    #[serde(default = "default_dead_threshold")]
23    pub dead_threshold: DurationStr,
24
25    /// Static seed nodes (for static discovery).
26    #[serde(default)]
27    pub seed_nodes: Vec<String>,
28
29    /// DNS name for discovery (for DNS discovery).
30    pub dns_name: Option<String>,
31}
32
33impl Default for ClusterConfig {
34    fn default() -> Self {
35        Self {
36            name: default_cluster_name(),
37            discovery: DiscoveryMethod::default(),
38            heartbeat_interval: default_heartbeat_interval(),
39            dead_threshold: default_dead_threshold(),
40            seed_nodes: Vec::new(),
41            dns_name: None,
42        }
43    }
44}
45
46impl ClusterConfig {
47    /// Return a copy with `dns_name` set.
48    pub fn with_dns_name(mut self, dns_name: String) -> Self {
49        self.dns_name = Some(dns_name);
50        self
51    }
52}
53
54fn default_cluster_name() -> String {
55    "default".to_string()
56}
57
58fn default_heartbeat_interval() -> DurationStr {
59    DurationStr::new(Duration::from_secs(5))
60}
61
62fn default_dead_threshold() -> DurationStr {
63    DurationStr::new(Duration::from_secs(15))
64}
65
66/// Cluster discovery method.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
68#[serde(rename_all = "lowercase")]
69pub enum DiscoveryMethod {
70    /// Use PostgreSQL table for discovery.
71    #[default]
72    Postgres,
73
74    /// Use DNS for discovery.
75    Dns,
76
77    /// Use Kubernetes for discovery.
78    Kubernetes,
79
80    /// Use static seed nodes.
81    Static,
82}
83
84#[cfg(test)]
85#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_default_cluster_config() {
91        let config = ClusterConfig::default();
92        assert_eq!(config.name, "default");
93        assert_eq!(config.discovery, DiscoveryMethod::Postgres);
94        assert_eq!(config.heartbeat_interval.as_secs(), 5);
95    }
96
97    #[test]
98    fn test_parse_cluster_config() {
99        let toml = r#"
100            name = "production"
101            discovery = "kubernetes"
102            heartbeat_interval = "10s"
103        "#;
104
105        let config: ClusterConfig = toml::from_str(toml).unwrap();
106        assert_eq!(config.name, "production");
107        assert_eq!(config.discovery, DiscoveryMethod::Kubernetes);
108    }
109}