use crate::config::configurable::{
DashboardIpcConfig, ObservabilityConfig, PolicyConfig, ShutdownConfig, SupervisorConfig,
SupervisorRootConfig,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConfigState {
pub supervisor: SupervisorRootConfig,
pub policy: PolicyConfig,
pub shutdown: ShutdownConfig,
pub observability: ObservabilityConfig,
pub ipc: Option<DashboardIpcConfig>,
}
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)?;
validate_ipc(config.ipc.as_ref())?;
Ok(Self {
supervisor: config.supervisor,
policy: config.policy,
shutdown: config.shutdown,
observability: config.observability,
ipc: config.ipc,
})
}
}
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, "policy.child_restart_limit")?;
validate_positive(
policy.supervisor_failure_limit,
"policy.supervisor_failure_limit",
)?;
validate_positive(
policy.child_restart_window_ms,
"policy.child_restart_window_ms",
)?;
validate_positive(
policy.supervisor_failure_window_ms,
"policy.supervisor_failure_window_ms",
)?;
validate_positive(policy.initial_backoff_ms, "policy.initial_backoff_ms")?;
validate_positive(policy.max_backoff_ms, "policy.max_backoff_ms")?;
validate_positive(policy.heartbeat_interval_ms, "policy.heartbeat_interval_ms")?;
validate_positive(policy.stale_after_ms, "policy.stale_after_ms")?;
if policy.initial_backoff_ms > policy.max_backoff_ms {
return Err(crate::error::types::SupervisorError::fatal_config(
"policy.initial_backoff_ms must be less than or equal to policy.max_backoff_ms",
));
}
if !(0.0..=1.0).contains(&policy.jitter_ratio) {
return Err(crate::error::types::SupervisorError::fatal_config(
"policy.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, "shutdown.graceful_timeout_ms")?;
validate_positive(shutdown.abort_wait_ms, "shutdown.abort_wait_ms")
}
fn validate_observability(
observability: &ObservabilityConfig,
) -> Result<(), crate::error::types::SupervisorError> {
validate_positive(
observability.event_journal_capacity as u64,
"observability.event_journal_capacity",
)
}
fn validate_ipc(
ipc: Option<&DashboardIpcConfig>,
) -> Result<(), crate::error::types::SupervisorError> {
crate::dashboard::config::validate_dashboard_ipc_config(ipc)
.map(|_| ())
.map_err(|error| crate::error::types::SupervisorError::fatal_config(error.to_string()))
}
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(())
}
}