use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SupervisorConfig {
pub supervisor: SupervisorRootConfig,
pub policy: PolicyConfig,
pub shutdown: ShutdownConfig,
pub observability: ObservabilityConfig,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConfigState {
pub supervisor: SupervisorRootConfig,
pub policy: PolicyConfig,
pub shutdown: ShutdownConfig,
pub observability: ObservabilityConfig,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SupervisorRootConfig {
pub strategy: crate::spec::supervisor::SupervisionStrategy,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PolicyConfig {
pub child_restart_limit: u32,
pub child_restart_window_ms: u64,
pub supervisor_failure_limit: u32,
pub supervisor_failure_window_ms: u64,
pub initial_backoff_ms: u64,
pub max_backoff_ms: u64,
pub jitter_ratio: f64,
pub heartbeat_interval_ms: u64,
pub stale_after_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShutdownConfig {
pub graceful_timeout_ms: u64,
pub abort_wait_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservabilityConfig {
pub event_journal_capacity: usize,
pub metrics_enabled: bool,
pub audit_enabled: bool,
}
impl TryFrom<SupervisorConfig> for ConfigState {
type Error = crate::error::types::SupervisorError;
fn try_from(config: SupervisorConfig) -> Result<Self, Self::Error> {
validate_policy(&config.policy)?;
validate_shutdown(&config.shutdown)?;
validate_observability(&config.observability)?;
Ok(Self {
supervisor: config.supervisor,
policy: config.policy,
shutdown: config.shutdown,
observability: config.observability,
})
}
}
impl ConfigState {
pub fn to_supervisor_spec(
&self,
) -> Result<crate::spec::supervisor::SupervisorSpec, crate::error::types::SupervisorError> {
let mut spec = crate::spec::supervisor::SupervisorSpec::root(Vec::new());
spec.strategy = self.supervisor.strategy;
spec.config_version = self.config_version();
spec.supervisor_failure_limit = self.policy.supervisor_failure_limit;
spec.control_channel_capacity = self.observability.event_journal_capacity;
spec.event_channel_capacity = self.observability.event_journal_capacity;
spec.default_backoff_policy = crate::spec::child::BackoffPolicy::new(
Duration::from_millis(self.policy.initial_backoff_ms),
Duration::from_millis(self.policy.max_backoff_ms),
self.policy.jitter_ratio,
);
spec.default_health_policy = crate::spec::child::HealthPolicy::new(
Duration::from_millis(self.policy.heartbeat_interval_ms),
Duration::from_millis(self.policy.stale_after_ms),
);
spec.default_shutdown_policy = crate::spec::child::ShutdownPolicy::new(
Duration::from_millis(self.shutdown.graceful_timeout_ms),
Duration::from_millis(self.shutdown.abort_wait_ms),
);
spec.validate()?;
Ok(spec)
}
fn config_version(&self) -> String {
format!(
"supervisor-{:?}-policy-{}-{}-shutdown-{}-observe-{}",
self.supervisor.strategy,
self.policy.child_restart_limit,
self.policy.supervisor_failure_limit,
self.shutdown.graceful_timeout_ms,
self.observability.event_journal_capacity
)
}
}
fn validate_policy(policy: &PolicyConfig) -> Result<(), crate::error::types::SupervisorError> {
validate_positive(policy.child_restart_limit, "child_restart_limit")?;
validate_positive(policy.supervisor_failure_limit, "supervisor_failure_limit")?;
validate_positive(policy.child_restart_window_ms, "child_restart_window_ms")?;
validate_positive(
policy.supervisor_failure_window_ms,
"supervisor_failure_window_ms",
)?;
validate_positive(policy.initial_backoff_ms, "initial_backoff_ms")?;
validate_positive(policy.max_backoff_ms, "max_backoff_ms")?;
validate_positive(policy.heartbeat_interval_ms, "heartbeat_interval_ms")?;
validate_positive(policy.stale_after_ms, "stale_after_ms")?;
if policy.initial_backoff_ms > policy.max_backoff_ms {
return Err(crate::error::types::SupervisorError::fatal_config(
"initial_backoff_ms must be less than or equal to max_backoff_ms",
));
}
if !(0.0..=1.0).contains(&policy.jitter_ratio) {
return Err(crate::error::types::SupervisorError::fatal_config(
"jitter_ratio must be between 0 and 1",
));
}
Ok(())
}
fn validate_shutdown(
shutdown: &ShutdownConfig,
) -> Result<(), crate::error::types::SupervisorError> {
validate_positive(shutdown.graceful_timeout_ms, "graceful_timeout_ms")?;
validate_positive(shutdown.abort_wait_ms, "abort_wait_ms")
}
fn validate_observability(
observability: &ObservabilityConfig,
) -> Result<(), crate::error::types::SupervisorError> {
validate_positive(
observability.event_journal_capacity as u64,
"event_journal_capacity",
)
}
fn validate_positive(
value: impl Into<u64>,
name: &str,
) -> Result<(), crate::error::types::SupervisorError> {
if value.into() == 0 {
Err(crate::error::types::SupervisorError::fatal_config(format!(
"{name} must be greater than zero"
)))
} else {
Ok(())
}
}