use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)]
pub struct AgentConfig {
pub agent: AgentSection,
pub log: LogSection,
}
#[derive(Deserialize, Debug, Clone)]
pub struct AgentSection {
pub id: String,
pub nats_url: String,
#[serde(default)]
pub groups: Vec<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct LogSection {
pub path: String,
pub level: String,
#[serde(default = "default_keep_days")]
pub keep_days: usize,
}
fn default_keep_days() -> usize {
14
}
#[derive(Deserialize, Debug, Clone)]
pub struct BackendConfig {
pub server: ServerSection,
pub nats: NatsSection,
pub db: DbSection,
pub log: LogSection,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ServerSection {
pub bind: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct NatsSection {
pub url: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct DbSection {
pub sqlite_path: String,
}
fn load_typed<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
let mut engine = teravars::Engine::new();
let ctx = teravars::system_context();
let paths: Vec<PathBuf> = vec![path.to_path_buf()];
let merged = teravars::load_merged(&paths, &mut engine, &ctx)
.with_context(|| format!("teravars load_merged: {path:?}"))?;
let cfg: T = toml::Value::Table(merged.config)
.try_into()
.with_context(|| format!("decode config from {path:?}"))?;
Ok(cfg)
}
pub fn load_agent_config(path: &Path) -> Result<AgentConfig> {
load_typed(path)
}
pub fn load_backend_config(path: &Path) -> Result<BackendConfig> {
load_typed(path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agent_dev_toml_renders_pc_id_from_env_or_system_host() {
let cfg_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("configs")
.join("agent.dev.toml");
unsafe {
std::env::set_var("KANADE_DEV_AGENT_ID", "dev-pc-render-test");
}
let cfg = load_agent_config(&cfg_path).expect("load agent.dev.toml (env set)");
assert_eq!(cfg.agent.id, "dev-pc-render-test");
assert!(
cfg.log.path.contains("dev-pc-render-test"),
"log path should embed pc_id, got {}",
cfg.log.path,
);
unsafe {
std::env::remove_var("KANADE_DEV_AGENT_ID");
}
let cfg = load_agent_config(&cfg_path).expect("load agent.dev.toml (env unset)");
assert!(
!cfg.agent.id.is_empty(),
"pc_id should fall back to system.host"
);
assert_ne!(
cfg.agent.id, "{{ system.host }}",
"template should render, not leak"
);
}
}