use std::fs::{self, File};
use std::io::{Read, Write};
use super::error::HostError;
use super::schema::HostsFile;
use crate::config::paths::get_hosts_path;
pub fn load_hosts() -> Result<HostsFile, HostError> {
let hosts_path = get_hosts_path()
.ok_or_else(|| HostError::LoadFailed("Could not determine hosts file path".to_string()))?;
if !hosts_path.exists() {
tracing::debug!(
"Hosts file not found, returning empty: {}",
hosts_path.display()
);
return Ok(HostsFile::new());
}
let mut file = File::open(&hosts_path).map_err(|e| {
HostError::LoadFailed(format!("Failed to open {}: {}", hosts_path.display(), e))
})?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|e| {
HostError::LoadFailed(format!("Failed to read {}: {}", hosts_path.display(), e))
})?;
let hosts: HostsFile = serde_json::from_str(&contents).map_err(|e| {
HostError::LoadFailed(format!("Invalid JSON in {}: {}", hosts_path.display(), e))
})?;
tracing::debug!(
"Loaded {} hosts from {}",
hosts.hosts.len(),
hosts_path.display()
);
Ok(hosts)
}
pub fn save_hosts(hosts: &HostsFile) -> Result<(), HostError> {
let hosts_path = get_hosts_path()
.ok_or_else(|| HostError::SaveFailed("Could not determine hosts file path".to_string()))?;
if let Some(parent) = hosts_path.parent()
&& !parent.exists()
{
fs::create_dir_all(parent)
.map_err(|e| HostError::SaveFailed(format!("Failed to create directory: {e}")))?;
}
if hosts_path.exists() {
let backup_path = hosts_path.with_extension("json.bak");
fs::copy(&hosts_path, &backup_path)
.map_err(|e| HostError::SaveFailed(format!("Failed to create backup: {e}")))?;
tracing::debug!("Created hosts backup: {}", backup_path.display());
}
let json = serde_json::to_string_pretty(hosts)
.map_err(|e| HostError::SaveFailed(format!("Failed to serialize: {e}")))?;
let mut file = File::create(&hosts_path).map_err(|e| {
HostError::SaveFailed(format!("Failed to create {}: {}", hosts_path.display(), e))
})?;
file.write_all(json.as_bytes()).map_err(|e| {
HostError::SaveFailed(format!("Failed to write {}: {}", hosts_path.display(), e))
})?;
tracing::debug!(
"Saved {} hosts to {}",
hosts.hosts.len(),
hosts_path.display()
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::host::schema::HostConfig;
#[test]
fn test_load_nonexistent_returns_empty() {
let result = load_hosts();
assert!(result.is_ok());
}
#[test]
fn test_serialize_format() {
let mut hosts = HostsFile::new();
hosts.add_host("test", HostConfig::new("test.example.com"));
let json = serde_json::to_string_pretty(&hosts).unwrap();
let parsed: HostsFile = serde_json::from_str(&json).unwrap();
assert!(parsed.has_host("test"));
}
}