use crate::common::config_file::{ConfigError, Settings};
use crate::common::config_schema::{RawConfig, SocketSetting};
pub(super) fn apply_file(raw: &RawConfig, settings: &mut Settings) -> Result<(), ConfigError> {
apply_file_general(raw, settings)?;
apply_file_local(raw, settings);
apply_file_view(raw, settings);
apply_file_api(raw, settings);
apply_file_alerts(raw, settings);
apply_file_energy(raw, settings)?;
apply_file_display(raw, settings);
apply_file_record(raw, settings)?;
apply_file_snapshot(raw, settings)?;
Ok(())
}
fn apply_file_general(raw: &RawConfig, settings: &mut Settings) -> Result<(), ConfigError> {
let Some(g) = &raw.general else {
return Ok(());
};
if let Some(m) = &g.default_mode {
match m.as_str() {
"local" | "view" | "api" => settings.general.default_mode = m.clone(),
_ => {
return Err(ConfigError::Semantic(format!(
"general.default_mode must be one of local/view/api, got `{m}`"
)));
}
}
}
if let Some(t) = &g.theme {
match t.as_str() {
"auto" | "light" | "dark" | "high-contrast" | "mono" => {
settings.general.theme = t.clone();
}
_ => {
return Err(ConfigError::Semantic(format!(
"general.theme must be auto/light/dark/high-contrast/mono, got `{t}`"
)));
}
}
}
if let Some(l) = &g.locale {
settings.general.locale = l.clone();
}
Ok(())
}
fn apply_file_local(raw: &RawConfig, settings: &mut Settings) {
if let Some(l) = &raw.local
&& l.interval_secs.is_some()
{
settings.local.interval_secs = l.interval_secs;
}
}
fn apply_file_view(raw: &RawConfig, settings: &mut Settings) {
let Some(v) = &raw.view else { return };
if v.hostfile.is_some() {
settings.view.hostfile = v.hostfile.clone();
}
if let Some(hosts) = &v.hosts {
settings.view.hosts = hosts.clone();
}
if v.interval_secs.is_some() {
settings.view.interval_secs = v.interval_secs;
}
if let Some(ssh) = &v.ssh {
settings.view.ssh = ssh.clone();
}
if v.ssh_hostfile.is_some() {
settings.view.ssh_hostfile = v.ssh_hostfile.clone();
}
if v.ssh_key.is_some() {
settings.view.ssh_key = v.ssh_key.clone();
}
if v.ssh_config.is_some() {
settings.view.ssh_config = v.ssh_config.clone();
}
if v.ssh_strict_host_key.is_some() {
settings.view.ssh_strict_host_key = v.ssh_strict_host_key.clone();
}
if v.ssh_timeout_secs.is_some() {
settings.view.ssh_timeout_secs = v.ssh_timeout_secs;
}
if v.ssh_fallback.is_some() {
settings.view.ssh_fallback = v.ssh_fallback.clone();
}
if v.ssh_known_hosts.is_some() {
settings.view.ssh_known_hosts = v.ssh_known_hosts.clone();
}
if v.ssh_concurrency.is_some() {
settings.view.ssh_concurrency = v.ssh_concurrency;
}
}
fn apply_file_api(raw: &RawConfig, settings: &mut Settings) {
let Some(a) = &raw.api else { return };
if let Some(p) = a.port {
settings.api.port = p;
}
match &a.socket {
SocketSetting::Unset => {}
s => settings.api.socket = s.clone(),
}
if let Some(p) = a.processes {
settings.api.processes = p;
}
if let Some(i) = a.interval_secs {
settings.api.interval_secs = i;
}
}
fn apply_file_alerts(raw: &RawConfig, settings: &mut Settings) {
let Some(al) = &raw.alerts else { return };
if let Some(t) = al.temp_warn_c {
settings.alerts.temp_warn_c = t;
}
if let Some(t) = al.temp_crit_c {
settings.alerts.temp_crit_c = t;
}
if let Some(v) = al.util_idle_pct {
settings.alerts.util_idle_pct = v;
}
if let Some(v) = al.util_idle_warn_mins {
settings.alerts.util_idle_warn_mins = v;
}
if let Some(v) = al.hysteresis_c {
settings.alerts.hysteresis_c = v;
}
if let Some(v) = al.bell_on_critical {
settings.alerts.bell_on_critical = v;
}
if let Some(url) = &al.webhook_url {
settings.alerts.webhook_url = url.clone();
}
if let Some(v) = al.power_crit_w {
settings.alerts.power_crit_w = v;
}
let _ = al.enabled;
}
fn apply_file_energy(raw: &RawConfig, settings: &mut Settings) -> Result<(), ConfigError> {
let Some(e) = &raw.energy else {
return Ok(());
};
if let Some(p) = e.price_per_kwh {
if !p.is_finite() || p < 0.0 {
return Err(ConfigError::Semantic(format!(
"energy.price_per_kwh must be a non-negative finite number, got {p}"
)));
}
settings.energy.price_per_kwh = p;
}
if let Some(c) = &e.currency {
settings.energy.currency = c.clone();
}
if let Some(s) = e.show_cost {
settings.energy.show_cost = s;
}
if let Some(w) = &e.wal_path
&& !w.trim().is_empty()
{
settings.energy.wal_path = Some(w.trim().to_string());
}
if let Some(g) = e.gap_interpolate_seconds {
if !(1..=3600).contains(&g) {
return Err(ConfigError::Semantic(format!(
"energy.gap_interpolate_seconds must be in [1, 3600], got {g}"
)));
}
settings.energy.gap_interpolate_seconds = g;
}
if let Some(w) = e.wal_enabled {
settings.energy.wal_enabled = w;
}
Ok(())
}
fn apply_file_display(raw: &RawConfig, settings: &mut Settings) {
let Some(d) = &raw.display else { return };
if let Some(c) = &d.color_scheme {
settings.display.color_scheme = c.clone();
}
if let Some(g) = &d.gauge_style {
settings.display.gauge_style = g.clone();
}
if let Some(v) = d.show_led_grid {
settings.display.show_led_grid = v;
}
}
fn apply_file_record(raw: &RawConfig, settings: &mut Settings) -> Result<(), ConfigError> {
let Some(r) = &raw.record else {
return Ok(());
};
if let Some(o) = &r.output_dir
&& !o.trim().is_empty()
{
settings.record.output_dir = Some(o.trim().to_string());
}
if let Some(c) = &r.compress {
match c.as_str() {
"zstd" | "gzip" | "none" => settings.record.compress = c.clone(),
_ => {
return Err(ConfigError::Semantic(format!(
"record.compress must be zstd/gzip/none, got `{c}`"
)));
}
}
}
Ok(())
}
fn apply_file_snapshot(raw: &RawConfig, settings: &mut Settings) -> Result<(), ConfigError> {
let Some(s) = &raw.snapshot else {
return Ok(());
};
if let Some(f) = &s.default_format {
match f.as_str() {
"json" | "csv" | "prometheus" => settings.snapshot.default_format = f.clone(),
_ => {
return Err(ConfigError::Semantic(format!(
"snapshot.default_format must be json/csv/prometheus, got `{f}`"
)));
}
}
}
if let Some(v) = s.default_pretty {
settings.snapshot.default_pretty = v;
}
Ok(())
}