opencode_cloud_core/host/
storage.rs1use std::fs::{self, File};
6use std::io::{Read, Write};
7
8use super::error::HostError;
9use super::schema::HostsFile;
10use crate::config::paths::get_hosts_path;
11
12pub fn load_hosts() -> Result<HostsFile, HostError> {
16 let hosts_path = get_hosts_path()
17 .ok_or_else(|| HostError::LoadFailed("Could not determine hosts file path".to_string()))?;
18
19 if !hosts_path.exists() {
20 tracing::debug!(
21 "Hosts file not found, returning empty: {}",
22 hosts_path.display()
23 );
24 return Ok(HostsFile::new());
25 }
26
27 let mut file = File::open(&hosts_path).map_err(|e| {
28 HostError::LoadFailed(format!("Failed to open {}: {}", hosts_path.display(), e))
29 })?;
30
31 let mut contents = String::new();
32 file.read_to_string(&mut contents).map_err(|e| {
33 HostError::LoadFailed(format!("Failed to read {}: {}", hosts_path.display(), e))
34 })?;
35
36 let hosts: HostsFile = serde_json::from_str(&contents).map_err(|e| {
37 HostError::LoadFailed(format!("Invalid JSON in {}: {}", hosts_path.display(), e))
38 })?;
39
40 tracing::debug!(
41 "Loaded {} hosts from {}",
42 hosts.hosts.len(),
43 hosts_path.display()
44 );
45 Ok(hosts)
46}
47
48pub fn save_hosts(hosts: &HostsFile) -> Result<(), HostError> {
53 let hosts_path = get_hosts_path()
54 .ok_or_else(|| HostError::SaveFailed("Could not determine hosts file path".to_string()))?;
55
56 if let Some(parent) = hosts_path.parent() {
58 if !parent.exists() {
59 fs::create_dir_all(parent)
60 .map_err(|e| HostError::SaveFailed(format!("Failed to create directory: {e}")))?;
61 }
62 }
63
64 if hosts_path.exists() {
66 let backup_path = hosts_path.with_extension("json.bak");
67 fs::copy(&hosts_path, &backup_path)
68 .map_err(|e| HostError::SaveFailed(format!("Failed to create backup: {e}")))?;
69 tracing::debug!("Created hosts backup: {}", backup_path.display());
70 }
71
72 let json = serde_json::to_string_pretty(hosts)
74 .map_err(|e| HostError::SaveFailed(format!("Failed to serialize: {e}")))?;
75
76 let mut file = File::create(&hosts_path).map_err(|e| {
78 HostError::SaveFailed(format!("Failed to create {}: {}", hosts_path.display(), e))
79 })?;
80
81 file.write_all(json.as_bytes()).map_err(|e| {
82 HostError::SaveFailed(format!("Failed to write {}: {}", hosts_path.display(), e))
83 })?;
84
85 tracing::debug!(
86 "Saved {} hosts to {}",
87 hosts.hosts.len(),
88 hosts_path.display()
89 );
90 Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::host::schema::HostConfig;
97
98 #[test]
99 fn test_load_nonexistent_returns_empty() {
100 let result = load_hosts();
103 assert!(result.is_ok());
105 }
106
107 #[test]
108 fn test_serialize_format() {
109 let mut hosts = HostsFile::new();
110 hosts.add_host("test", HostConfig::new("test.example.com"));
111
112 let json = serde_json::to_string_pretty(&hosts).unwrap();
113
114 let parsed: HostsFile = serde_json::from_str(&json).unwrap();
116 assert!(parsed.has_host("test"));
117 }
118}