rust_supervisor/config/
state.rs1use crate::config::configurable::{
7 DashboardIpcConfig, ObservabilityConfig, PolicyConfig, ShutdownConfig, SupervisorConfig,
8 SupervisorRootConfig,
9};
10use serde::{Deserialize, Serialize};
11use std::time::Duration;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct ConfigState {
16 pub supervisor: SupervisorRootConfig,
18 pub policy: PolicyConfig,
20 pub shutdown: ShutdownConfig,
22 pub observability: ObservabilityConfig,
24 pub ipc: Option<DashboardIpcConfig>,
26}
27
28impl TryFrom<SupervisorConfig> for ConfigState {
29 type Error = crate::error::types::SupervisorError;
30
31 fn try_from(config: SupervisorConfig) -> Result<Self, Self::Error> {
33 validate_policy(&config.policy)?;
34 validate_shutdown(&config.shutdown)?;
35 validate_observability(&config.observability)?;
36 validate_ipc(config.ipc.as_ref())?;
37 Ok(Self {
38 supervisor: config.supervisor,
39 policy: config.policy,
40 shutdown: config.shutdown,
41 observability: config.observability,
42 ipc: config.ipc,
43 })
44 }
45}
46
47impl ConfigState {
48 pub fn to_supervisor_spec(
89 &self,
90 ) -> Result<crate::spec::supervisor::SupervisorSpec, crate::error::types::SupervisorError> {
91 let mut spec = crate::spec::supervisor::SupervisorSpec::root(Vec::new());
92 spec.strategy = self.supervisor.strategy;
93 spec.config_version = self.config_version();
94 spec.supervisor_failure_limit = self.policy.supervisor_failure_limit;
95 spec.control_channel_capacity = self.observability.event_journal_capacity;
96 spec.event_channel_capacity = self.observability.event_journal_capacity;
97 spec.default_backoff_policy = crate::spec::child::BackoffPolicy::new(
98 Duration::from_millis(self.policy.initial_backoff_ms),
99 Duration::from_millis(self.policy.max_backoff_ms),
100 self.policy.jitter_ratio,
101 );
102 spec.default_health_policy = crate::spec::child::HealthPolicy::new(
103 Duration::from_millis(self.policy.heartbeat_interval_ms),
104 Duration::from_millis(self.policy.stale_after_ms),
105 );
106 spec.default_shutdown_policy = crate::spec::child::ShutdownPolicy::new(
107 Duration::from_millis(self.shutdown.graceful_timeout_ms),
108 Duration::from_millis(self.shutdown.abort_wait_ms),
109 );
110 spec.validate()?;
111 Ok(spec)
112 }
113
114 fn config_version(&self) -> String {
124 format!(
125 "supervisor-{:?}-policy-{}-{}-shutdown-{}-observe-{}",
126 self.supervisor.strategy,
127 self.policy.child_restart_limit,
128 self.policy.supervisor_failure_limit,
129 self.shutdown.graceful_timeout_ms,
130 self.observability.event_journal_capacity
131 )
132 }
133}
134
135fn validate_policy(policy: &PolicyConfig) -> Result<(), crate::error::types::SupervisorError> {
145 validate_positive(policy.child_restart_limit, "policy.child_restart_limit")?;
146 validate_positive(
147 policy.supervisor_failure_limit,
148 "policy.supervisor_failure_limit",
149 )?;
150 validate_positive(
151 policy.child_restart_window_ms,
152 "policy.child_restart_window_ms",
153 )?;
154 validate_positive(
155 policy.supervisor_failure_window_ms,
156 "policy.supervisor_failure_window_ms",
157 )?;
158 validate_positive(policy.initial_backoff_ms, "policy.initial_backoff_ms")?;
159 validate_positive(policy.max_backoff_ms, "policy.max_backoff_ms")?;
160 validate_positive(policy.heartbeat_interval_ms, "policy.heartbeat_interval_ms")?;
161 validate_positive(policy.stale_after_ms, "policy.stale_after_ms")?;
162 if policy.initial_backoff_ms > policy.max_backoff_ms {
163 return Err(crate::error::types::SupervisorError::fatal_config(
164 "policy.initial_backoff_ms must be less than or equal to policy.max_backoff_ms",
165 ));
166 }
167 if !(0.0..=1.0).contains(&policy.jitter_ratio) {
168 return Err(crate::error::types::SupervisorError::fatal_config(
169 "policy.jitter_ratio must be between 0 and 1",
170 ));
171 }
172 Ok(())
173}
174
175fn validate_shutdown(
185 shutdown: &ShutdownConfig,
186) -> Result<(), crate::error::types::SupervisorError> {
187 validate_positive(shutdown.graceful_timeout_ms, "shutdown.graceful_timeout_ms")?;
188 validate_positive(shutdown.abort_wait_ms, "shutdown.abort_wait_ms")
189}
190
191fn validate_observability(
201 observability: &ObservabilityConfig,
202) -> Result<(), crate::error::types::SupervisorError> {
203 validate_positive(
204 observability.event_journal_capacity as u64,
205 "observability.event_journal_capacity",
206 )
207}
208
209fn validate_ipc(
219 ipc: Option<&DashboardIpcConfig>,
220) -> Result<(), crate::error::types::SupervisorError> {
221 crate::dashboard::config::validate_dashboard_ipc_config(ipc)
222 .map(|_| ())
223 .map_err(|error| crate::error::types::SupervisorError::fatal_config(error.to_string()))
224}
225
226fn validate_positive(
237 value: impl Into<u64>,
238 name: &str,
239) -> Result<(), crate::error::types::SupervisorError> {
240 if value.into() == 0 {
241 Err(crate::error::types::SupervisorError::fatal_config(format!(
242 "{name} must be greater than zero"
243 )))
244 } else {
245 Ok(())
246 }
247}