1use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use koi_common::paths;
8use koi_common::persist;
9
10#[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#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
21pub struct DnsState {
22 #[serde(default)]
23 pub entries: Vec<DnsEntry>,
24}
25
26pub fn dns_state_path() -> PathBuf {
28 paths::koi_state_dir().join("dns.json")
29}
30
31pub fn load_dns_state() -> Result<DnsState, std::io::Error> {
33 let path = dns_state_path();
34 persist::read_json_or_default(&path)
35}
36
37pub 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48pub struct TrustEntry {
49 pub name: String,
51 pub installed_at: String,
53 pub fingerprint: String,
55 pub source: String,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
61pub struct TrustState {
62 #[serde(default)]
63 pub roots: Vec<TrustEntry>,
64}
65
66pub fn trust_state_path() -> PathBuf {
68 paths::koi_state_dir().join("trust.json")
69}
70
71pub fn load_trust_state() -> Result<TrustState, std::io::Error> {
73 let path = trust_state_path();
74 persist::read_json_or_default(&path)
75}
76
77pub fn save_trust_state(state: &TrustState) -> Result<(), std::io::Error> {
79 let path = trust_state_path();
80 persist::write_json_pretty(&path, state)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn dns_state_round_trip() {
89 let state = DnsState {
90 entries: vec![DnsEntry {
91 name: "grafana.lan".to_string(),
92 ip: "192.168.1.50".to_string(),
93 ttl: Some(60),
94 }],
95 };
96 let json = serde_json::to_string(&state).unwrap();
97 let parsed: DnsState = serde_json::from_str(&json).unwrap();
98 assert_eq!(state, parsed);
99 }
100
101 #[test]
102 fn load_dns_state_missing_returns_default() {
103 let _ = koi_common::test::ensure_data_dir("koi-config-state-tests");
104 let state = load_dns_state().unwrap();
105 assert!(state.entries.is_empty());
106 }
107
108 #[test]
109 fn trust_state_round_trip() {
110 let state = TrustState {
111 roots: vec![TrustEntry {
112 name: "step-ca-root".to_string(),
113 installed_at: "2026-06-15T00:00:00Z".to_string(),
114 fingerprint: "abcd1234".to_string(),
115 source: "./root.pem".to_string(),
116 }],
117 };
118 let json = serde_json::to_string(&state).unwrap();
119 let parsed: TrustState = serde_json::from_str(&json).unwrap();
120 assert_eq!(state, parsed);
121 }
122
123 #[test]
124 fn trust_state_default_is_empty() {
125 assert!(TrustState::default().roots.is_empty());
126 }
127}