xerv_core/flow/
settings.rs

1//! Flow runtime settings from YAML.
2
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6/// Runtime settings for a flow.
7///
8/// These settings control execution behavior like concurrency limits,
9/// timeouts, and circuit breaker configuration.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(default)]
12pub struct FlowSettings {
13    /// Maximum concurrent trace executions.
14    #[serde(default = "default_max_concurrent")]
15    pub max_concurrent_executions: u32,
16
17    /// Execution timeout in milliseconds.
18    #[serde(default = "default_timeout_ms")]
19    pub execution_timeout_ms: u64,
20
21    /// Error rate threshold for circuit breaker (0.0 to 1.0).
22    #[serde(default = "default_circuit_breaker_threshold")]
23    pub circuit_breaker_threshold: f64,
24
25    /// Window for measuring error rate in milliseconds.
26    #[serde(default = "default_circuit_breaker_window_ms")]
27    pub circuit_breaker_window_ms: u64,
28
29    /// Maximum concurrent versions during deployment.
30    #[serde(default = "default_max_concurrent_versions")]
31    pub max_concurrent_versions: u32,
32
33    /// Drain timeout in milliseconds.
34    #[serde(default = "default_drain_timeout_ms")]
35    pub drain_timeout_ms: u64,
36
37    /// Grace period before hard drain in milliseconds.
38    #[serde(default = "default_drain_grace_period_ms")]
39    pub drain_grace_period_ms: u64,
40
41    /// Enable debug tracing for this flow.
42    #[serde(default)]
43    pub debug: bool,
44
45    /// Custom environment variables for this flow.
46    #[serde(default)]
47    pub env: std::collections::HashMap<String, String>,
48}
49
50fn default_max_concurrent() -> u32 {
51    100
52}
53fn default_timeout_ms() -> u64 {
54    60_000
55}
56fn default_circuit_breaker_threshold() -> f64 {
57    0.05
58}
59fn default_circuit_breaker_window_ms() -> u64 {
60    60_000
61}
62fn default_max_concurrent_versions() -> u32 {
63    5
64}
65fn default_drain_timeout_ms() -> u64 {
66    30 * 60 * 1000
67}
68fn default_drain_grace_period_ms() -> u64 {
69    5 * 60 * 1000
70}
71
72impl Default for FlowSettings {
73    fn default() -> Self {
74        Self {
75            max_concurrent_executions: default_max_concurrent(),
76            execution_timeout_ms: default_timeout_ms(),
77            circuit_breaker_threshold: default_circuit_breaker_threshold(),
78            circuit_breaker_window_ms: default_circuit_breaker_window_ms(),
79            max_concurrent_versions: default_max_concurrent_versions(),
80            drain_timeout_ms: default_drain_timeout_ms(),
81            drain_grace_period_ms: default_drain_grace_period_ms(),
82            debug: false,
83            env: std::collections::HashMap::new(),
84        }
85    }
86}
87
88impl FlowSettings {
89    /// Get execution timeout as Duration.
90    pub fn execution_timeout(&self) -> Duration {
91        Duration::from_millis(self.execution_timeout_ms)
92    }
93
94    /// Get circuit breaker window as Duration.
95    pub fn circuit_breaker_window(&self) -> Duration {
96        Duration::from_millis(self.circuit_breaker_window_ms)
97    }
98
99    /// Get drain timeout as Duration.
100    pub fn drain_timeout(&self) -> Duration {
101        Duration::from_millis(self.drain_timeout_ms)
102    }
103
104    /// Get drain grace period as Duration.
105    pub fn drain_grace_period(&self) -> Duration {
106        Duration::from_millis(self.drain_grace_period_ms)
107    }
108
109    /// Convert to PipelineSettings.
110    pub fn to_pipeline_settings(&self) -> crate::traits::PipelineSettings {
111        crate::traits::PipelineSettings {
112            max_concurrent_executions: self.max_concurrent_executions,
113            execution_timeout: self.execution_timeout(),
114            circuit_breaker_threshold: self.circuit_breaker_threshold,
115            circuit_breaker_window: self.circuit_breaker_window(),
116            max_concurrent_versions: self.max_concurrent_versions,
117            drain_timeout: self.drain_timeout(),
118            drain_grace_period: self.drain_grace_period(),
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn default_settings() {
129        let settings = FlowSettings::default();
130        assert_eq!(settings.max_concurrent_executions, 100);
131        assert_eq!(settings.execution_timeout_ms, 60_000);
132        assert_eq!(settings.circuit_breaker_threshold, 0.05);
133    }
134
135    #[test]
136    fn deserialize_settings() {
137        let yaml = r#"
138max_concurrent_executions: 50
139execution_timeout_ms: 30000
140debug: true
141env:
142  API_KEY: test123
143"#;
144        let settings: FlowSettings = serde_yaml::from_str(yaml).unwrap();
145        assert_eq!(settings.max_concurrent_executions, 50);
146        assert_eq!(settings.execution_timeout_ms, 30_000);
147        assert!(settings.debug);
148        assert_eq!(settings.env.get("API_KEY"), Some(&"test123".to_string()));
149    }
150
151    #[test]
152    fn to_duration() {
153        let settings = FlowSettings {
154            execution_timeout_ms: 5000,
155            ..Default::default()
156        };
157        assert_eq!(settings.execution_timeout(), Duration::from_millis(5000));
158    }
159}