1use serde::{Deserialize, Serialize};
7use std::time::Duration;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct SupervisorConfig {
12 pub supervisor: SupervisorRootConfig,
14 pub policy: PolicyConfig,
16 pub shutdown: ShutdownConfig,
18 pub observability: ObservabilityConfig,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct ConfigState {
25 pub supervisor: SupervisorRootConfig,
27 pub policy: PolicyConfig,
29 pub shutdown: ShutdownConfig,
31 pub observability: ObservabilityConfig,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct SupervisorRootConfig {
38 pub strategy: crate::spec::supervisor::SupervisionStrategy,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct PolicyConfig {
45 pub child_restart_limit: u32,
47 pub child_restart_window_ms: u64,
49 pub supervisor_failure_limit: u32,
51 pub supervisor_failure_window_ms: u64,
53 pub initial_backoff_ms: u64,
55 pub max_backoff_ms: u64,
57 pub jitter_ratio: f64,
59 pub heartbeat_interval_ms: u64,
61 pub stale_after_ms: u64,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct ShutdownConfig {
68 pub graceful_timeout_ms: u64,
70 pub abort_wait_ms: u64,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct ObservabilityConfig {
77 pub event_journal_capacity: usize,
79 pub metrics_enabled: bool,
81 pub audit_enabled: bool,
83}
84
85impl TryFrom<SupervisorConfig> for ConfigState {
86 type Error = crate::error::types::SupervisorError;
87
88 fn try_from(config: SupervisorConfig) -> Result<Self, Self::Error> {
90 validate_policy(&config.policy)?;
91 validate_shutdown(&config.shutdown)?;
92 validate_observability(&config.observability)?;
93 Ok(Self {
94 supervisor: config.supervisor,
95 policy: config.policy,
96 shutdown: config.shutdown,
97 observability: config.observability,
98 })
99 }
100}
101
102impl ConfigState {
103 pub fn to_supervisor_spec(
144 &self,
145 ) -> Result<crate::spec::supervisor::SupervisorSpec, crate::error::types::SupervisorError> {
146 let mut spec = crate::spec::supervisor::SupervisorSpec::root(Vec::new());
147 spec.strategy = self.supervisor.strategy;
148 spec.config_version = self.config_version();
149 spec.supervisor_failure_limit = self.policy.supervisor_failure_limit;
150 spec.control_channel_capacity = self.observability.event_journal_capacity;
151 spec.event_channel_capacity = self.observability.event_journal_capacity;
152 spec.default_backoff_policy = crate::spec::child::BackoffPolicy::new(
153 Duration::from_millis(self.policy.initial_backoff_ms),
154 Duration::from_millis(self.policy.max_backoff_ms),
155 self.policy.jitter_ratio,
156 );
157 spec.default_health_policy = crate::spec::child::HealthPolicy::new(
158 Duration::from_millis(self.policy.heartbeat_interval_ms),
159 Duration::from_millis(self.policy.stale_after_ms),
160 );
161 spec.default_shutdown_policy = crate::spec::child::ShutdownPolicy::new(
162 Duration::from_millis(self.shutdown.graceful_timeout_ms),
163 Duration::from_millis(self.shutdown.abort_wait_ms),
164 );
165 spec.validate()?;
166 Ok(spec)
167 }
168
169 fn config_version(&self) -> String {
179 format!(
180 "supervisor-{:?}-policy-{}-{}-shutdown-{}-observe-{}",
181 self.supervisor.strategy,
182 self.policy.child_restart_limit,
183 self.policy.supervisor_failure_limit,
184 self.shutdown.graceful_timeout_ms,
185 self.observability.event_journal_capacity
186 )
187 }
188}
189
190fn validate_policy(policy: &PolicyConfig) -> Result<(), crate::error::types::SupervisorError> {
200 validate_positive(policy.child_restart_limit, "child_restart_limit")?;
201 validate_positive(policy.supervisor_failure_limit, "supervisor_failure_limit")?;
202 validate_positive(policy.child_restart_window_ms, "child_restart_window_ms")?;
203 validate_positive(
204 policy.supervisor_failure_window_ms,
205 "supervisor_failure_window_ms",
206 )?;
207 validate_positive(policy.initial_backoff_ms, "initial_backoff_ms")?;
208 validate_positive(policy.max_backoff_ms, "max_backoff_ms")?;
209 validate_positive(policy.heartbeat_interval_ms, "heartbeat_interval_ms")?;
210 validate_positive(policy.stale_after_ms, "stale_after_ms")?;
211 if policy.initial_backoff_ms > policy.max_backoff_ms {
212 return Err(crate::error::types::SupervisorError::fatal_config(
213 "initial_backoff_ms must be less than or equal to max_backoff_ms",
214 ));
215 }
216 if !(0.0..=1.0).contains(&policy.jitter_ratio) {
217 return Err(crate::error::types::SupervisorError::fatal_config(
218 "jitter_ratio must be between 0 and 1",
219 ));
220 }
221 Ok(())
222}
223
224fn validate_shutdown(
234 shutdown: &ShutdownConfig,
235) -> Result<(), crate::error::types::SupervisorError> {
236 validate_positive(shutdown.graceful_timeout_ms, "graceful_timeout_ms")?;
237 validate_positive(shutdown.abort_wait_ms, "abort_wait_ms")
238}
239
240fn validate_observability(
250 observability: &ObservabilityConfig,
251) -> Result<(), crate::error::types::SupervisorError> {
252 validate_positive(
253 observability.event_journal_capacity as u64,
254 "event_journal_capacity",
255 )
256}
257
258fn validate_positive(
269 value: impl Into<u64>,
270 name: &str,
271) -> Result<(), crate::error::types::SupervisorError> {
272 if value.into() == 0 {
273 Err(crate::error::types::SupervisorError::fatal_config(format!(
274 "{name} must be greater than zero"
275 )))
276 } else {
277 Ok(())
278 }
279}