Skip to main content

camel_master/
config.rs

1use camel_api::CamelError;
2use camel_component_api::NetworkRetryPolicy;
3
4#[derive(Debug, Clone)]
5pub struct MasterUriConfig {
6    pub lock_name: String,
7    pub delegate_uri: String,
8}
9
10impl MasterUriConfig {
11    pub fn parse(uri: &str) -> Result<Self, CamelError> {
12        let mut parts = uri.splitn(3, ':');
13        let scheme = parts.next().unwrap_or_default();
14        let lock_name = parts.next().unwrap_or_default();
15        let delegate_uri = parts.next().unwrap_or_default();
16        if scheme != "master" || lock_name.is_empty() || delegate_uri.is_empty() {
17            return Err(CamelError::InvalidUri(format!(
18                "{uri}: expected master:<lockname>:<delegate-uri>"
19            )));
20        }
21        Ok(Self {
22            lock_name: lock_name.to_string(),
23            delegate_uri: delegate_uri.to_string(),
24        })
25    }
26}
27
28/// Per-component reconnect default: unlimited retries (max_attempts=0),
29/// preserving the previous `None = unlimited` behavior. Operators can opt
30/// into bounded retry via TOML `[components.master.reconnect]`.
31fn master_reconnect_default() -> NetworkRetryPolicy {
32    NetworkRetryPolicy {
33        max_attempts: 0, // unlimited
34        ..NetworkRetryPolicy::default()
35    }
36}
37
38/// Configuration for the master/leader-election component.
39///
40/// Controls drain timeout for graceful delegate shutdown and reconnection policy.
41///
42/// ## Backward compatibility
43///
44/// The `delegate_retry_max_attempts` field is retained as a backward-compat alias.
45/// When set (not `None`), it bridges into `reconnect.max_attempts` during construction
46/// in `MasterComponent::new()`. If `reconnect` is also explicitly configured, the
47/// explicit `reconnect` value wins.
48#[derive(Debug, Clone)]
49pub struct MasterComponentConfig {
50    /// Timeout in milliseconds for draining a delegate consumer on leadership loss.
51    /// The drain runs after leadership is lost and may overlap a successor's
52    /// lease; see the README "How It Works" section.
53    pub drain_timeout_ms: u64,
54    /// Structured reconnection policy, replacing the flat `delegate_retry_max_attempts`
55    /// field for new configs. Default: unlimited (`max_attempts=0`).
56    pub reconnect: NetworkRetryPolicy,
57    /// Backward-compat alias for `reconnect.max_attempts`. `None` means unlimited.
58    /// Bridged into `reconnect` during `MasterComponent::new()`.
59    pub delegate_retry_max_attempts: Option<u32>,
60}
61
62impl MasterComponentConfig {
63    /// Create a new config with the given drain timeout and retry limit.
64    pub fn new(drain_timeout_ms: u64, delegate_retry_max_attempts: Option<u32>) -> Self {
65        Self {
66            drain_timeout_ms,
67            reconnect: master_reconnect_default(),
68            delegate_retry_max_attempts,
69        }
70    }
71}
72
73impl Default for MasterComponentConfig {
74    fn default() -> Self {
75        Self {
76            drain_timeout_ms: 5000,
77            reconnect: master_reconnect_default(),
78            delegate_retry_max_attempts: None,
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn master_config_has_reconnect_policy() {
89        let cfg = MasterComponentConfig::default();
90        // Default should be unlimited (max_attempts=0) to preserve old None behavior
91        assert_eq!(cfg.reconnect.max_attempts, 0);
92        assert!(cfg.reconnect.enabled);
93        // Backward-compat field defaults to None
94        assert_eq!(cfg.delegate_retry_max_attempts, None);
95    }
96
97    #[test]
98    fn master_config_default_reconnect_is_unlimited() {
99        let policy = master_reconnect_default();
100        assert_eq!(policy.max_attempts, 0);
101        assert!(policy.enabled);
102        // Unlimited means should_retry always returns true
103        assert!(policy.should_retry(0));
104        assert!(policy.should_retry(100));
105        assert!(policy.should_retry(10_000));
106    }
107
108    #[test]
109    fn master_config_new_preserves_drain_timeout() {
110        let cfg = MasterComponentConfig::new(10_000, Some(5));
111        assert_eq!(cfg.drain_timeout_ms, 10_000);
112    }
113}