use crate::config::configurable::{DashboardIpcBindMode, DashboardIpcConfig};
use crate::dashboard::error::DashboardError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidatedDashboardIpcConfig {
pub target_id: String,
pub path: std::path::PathBuf,
pub permissions: String,
pub bind_mode: DashboardIpcBindMode,
pub registration: Option<ValidatedDashboardRegistrationConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidatedDashboardRegistrationConfig {
pub relay_registration_path: std::path::PathBuf,
pub display_name: String,
pub lease_seconds: u64,
pub registration_heartbeat_interval_seconds: u64,
}
pub fn validate_dashboard_ipc_config(
config: Option<&DashboardIpcConfig>,
) -> Result<Option<ValidatedDashboardIpcConfig>, DashboardError> {
let Some(config) = config else {
return Ok(None);
};
if !config.enabled {
return Ok(None);
}
let target_id = required_text(config.target_id.as_deref(), "ipc.target_id")?;
let path = config.path.clone().ok_or_else(|| {
DashboardError::validation("config", None, "ipc.path is required when IPC is enabled")
})?;
if !path.is_absolute() {
return Err(DashboardError::validation(
"config",
Some(target_id.clone()),
"ipc.path must be absolute",
));
}
let registration = validate_registration(config, &target_id)?;
Ok(Some(ValidatedDashboardIpcConfig {
target_id,
path,
permissions: config
.permissions
.clone()
.unwrap_or_else(|| "0600".to_owned()),
bind_mode: config.bind_mode.unwrap_or(DashboardIpcBindMode::CreateNew),
registration,
}))
}
fn validate_registration(
config: &DashboardIpcConfig,
target_id: &str,
) -> Result<Option<ValidatedDashboardRegistrationConfig>, DashboardError> {
let Some(registration) = config.registration.as_ref() else {
return Ok(None);
};
if !registration.enabled {
return Ok(None);
}
let relay_registration_path =
registration
.relay_registration_path
.clone()
.ok_or_else(|| {
DashboardError::validation(
"config",
Some(target_id.to_owned()),
"ipc.registration.relay_registration_path is required",
)
})?;
if !relay_registration_path.is_absolute() {
return Err(DashboardError::validation(
"config",
Some(target_id.to_owned()),
"ipc.registration.relay_registration_path must be absolute",
));
}
let lease_seconds = registration.lease_seconds.ok_or_else(|| {
DashboardError::validation(
"config",
Some(target_id.to_owned()),
"ipc.registration.lease_seconds is required",
)
})?;
if lease_seconds == 0 {
return Err(DashboardError::validation(
"config",
Some(target_id.to_owned()),
"ipc.registration.lease_seconds must be greater than zero",
));
}
let heartbeat_seconds = registration
.registration_heartbeat_interval_seconds
.unwrap_or(15);
if heartbeat_seconds == 0 || heartbeat_seconds >= lease_seconds {
return Err(DashboardError::validation(
"config",
Some(target_id.to_owned()),
"ipc.registration.registration_heartbeat_interval_seconds must be positive and less than lease_seconds",
));
}
Ok(Some(ValidatedDashboardRegistrationConfig {
relay_registration_path,
display_name: registration
.display_name
.clone()
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| target_id.to_owned()),
lease_seconds,
registration_heartbeat_interval_seconds: heartbeat_seconds,
}))
}
fn required_text(value: Option<&str>, field: &str) -> Result<String, DashboardError> {
let text = value.unwrap_or_default().trim();
if text.is_empty() {
Err(DashboardError::validation(
"config",
None,
format!("{field} must not be empty"),
))
} else {
Ok(text.to_owned())
}
}