Skip to main content

koi_config/
state.rs

1//! Runtime state file management (Phase 1+).
2
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use koi_common::paths;
8use koi_common::persist;
9
10/// DNS static entry stored in the local state file.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
12pub struct DnsEntry {
13    pub name: String,
14    pub ip: String,
15    #[serde(default)]
16    pub ttl: Option<u32>,
17}
18
19/// DNS state persisted on disk.
20#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
21pub struct DnsState {
22    #[serde(default)]
23    pub entries: Vec<DnsEntry>,
24}
25
26/// Path to the DNS state file.
27pub fn dns_state_path() -> PathBuf {
28    paths::koi_state_dir().join("dns.json")
29}
30
31/// Load DNS state from disk. Returns default state if missing.
32pub fn load_dns_state() -> Result<DnsState, std::io::Error> {
33    let path = dns_state_path();
34    persist::read_json_or_default(&path)
35}
36
37/// Save DNS state to disk, creating the state directory if needed.
38pub fn save_dns_state(state: &DnsState) -> Result<(), std::io::Error> {
39    let path = dns_state_path();
40    persist::write_json_pretty(&path, state)
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn dns_state_round_trip() {
49        let state = DnsState {
50            entries: vec![DnsEntry {
51                name: "grafana.lan".to_string(),
52                ip: "192.168.1.50".to_string(),
53                ttl: Some(60),
54            }],
55        };
56        let json = serde_json::to_string(&state).unwrap();
57        let parsed: DnsState = serde_json::from_str(&json).unwrap();
58        assert_eq!(state, parsed);
59    }
60
61    #[test]
62    fn load_dns_state_missing_returns_default() {
63        let _ = koi_common::test::ensure_data_dir("koi-config-state-tests");
64        let state = load_dns_state().unwrap();
65        assert!(state.entries.is_empty());
66    }
67}