1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4use serde::Deserialize;
5
6#[derive(Deserialize, Debug, Clone)]
9pub struct AgentConfig {
10 pub agent: AgentSection,
11 pub log: LogSection,
12}
13
14#[derive(Deserialize, Debug, Clone)]
15pub struct AgentSection {
16 pub id: String,
17 pub nats_url: String,
18 #[serde(default)]
25 pub groups: Vec<String>,
26}
27
28#[derive(Deserialize, Debug, Clone)]
29pub struct LogSection {
30 pub path: String,
31 pub level: String,
32 #[serde(default = "default_keep_days")]
37 pub keep_days: usize,
38}
39
40fn default_keep_days() -> usize {
41 14
42}
43
44#[derive(Deserialize, Debug, Clone)]
47pub struct BackendConfig {
48 pub server: ServerSection,
49 pub nats: NatsSection,
50 pub db: DbSection,
51 pub log: LogSection,
52}
53
54#[derive(Deserialize, Debug, Clone)]
55pub struct ServerSection {
56 pub bind: String,
57}
58
59#[derive(Deserialize, Debug, Clone)]
60pub struct NatsSection {
61 pub url: String,
62}
63
64#[derive(Deserialize, Debug, Clone)]
65pub struct DbSection {
66 pub sqlite_path: String,
67}
68
69fn load_typed<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
72 let mut engine = teravars::Engine::new();
73 let ctx = teravars::system_context();
74 let paths: Vec<PathBuf> = vec![path.to_path_buf()];
75 let merged = teravars::load_merged(&paths, &mut engine, &ctx)
76 .with_context(|| format!("teravars load_merged: {path:?}"))?;
77 let cfg: T = toml::Value::Table(merged.config)
78 .try_into()
79 .with_context(|| format!("decode config from {path:?}"))?;
80 Ok(cfg)
81}
82
83pub fn load_agent_config(path: &Path) -> Result<AgentConfig> {
84 load_typed(path)
85}
86
87pub fn load_backend_config(path: &Path) -> Result<BackendConfig> {
88 load_typed(path)
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
110 fn agent_dev_toml_renders_pc_id_from_env_or_system_host() {
111 let cfg_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
114 .join("..")
115 .join("..")
116 .join("configs")
117 .join("agent.dev.toml");
118
119 unsafe {
123 std::env::set_var("KANADE_DEV_AGENT_ID", "dev-pc-render-test");
124 }
125 let cfg = load_agent_config(&cfg_path).expect("load agent.dev.toml (env set)");
126 assert_eq!(cfg.agent.id, "dev-pc-render-test");
127 assert!(
128 cfg.log.path.contains("dev-pc-render-test"),
129 "log path should embed pc_id, got {}",
130 cfg.log.path,
131 );
132
133 unsafe {
138 std::env::remove_var("KANADE_DEV_AGENT_ID");
139 }
140 let cfg = load_agent_config(&cfg_path).expect("load agent.dev.toml (env unset)");
141 assert!(
142 !cfg.agent.id.is_empty(),
143 "pc_id should fall back to system.host"
144 );
145 assert_ne!(
146 cfg.agent.id, "{{ system.host }}",
147 "template should render, not leak"
148 );
149 }
150}