use std::fs;
use std::io::Write;
use std::path::Path;
use tempfile::NamedTempFile;
use crate::error::{AgitError, Result};
pub fn atomic_write(path: &Path, content: &[u8]) -> Result<()> {
let dir = path.parent().unwrap_or(Path::new("."));
fs::create_dir_all(dir)?;
let mut temp_file = NamedTempFile::new_in(dir)?;
temp_file.write_all(content)?;
temp_file.flush()?;
temp_file.as_file().sync_all()?;
temp_file
.persist(path)
.map_err(|e| AgitError::Io(e.error))?;
Ok(())
}
pub fn atomic_write_str(path: &Path, content: &str) -> Result<()> {
atomic_write(path, content.as_bytes())
}
pub fn atomic_write_json<T: serde::Serialize>(path: &Path, value: &T) -> Result<()> {
let json = serde_json::to_string_pretty(value)?;
atomic_write_str(path, &json)
}
pub fn read_optional(path: &Path) -> Result<Option<Vec<u8>>> {
if !path.exists() {
return Ok(None);
}
Ok(Some(fs::read(path)?))
}
pub fn read_optional_string(path: &Path) -> Result<Option<String>> {
if !path.exists() {
return Ok(None);
}
Ok(Some(fs::read_to_string(path)?))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_atomic_write() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
atomic_write(&path, b"hello world").unwrap();
let content = fs::read_to_string(&path).unwrap();
assert_eq!(content, "hello world");
}
#[test]
fn test_atomic_write_creates_parent_dirs() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("a").join("b").join("c").join("test.txt");
atomic_write(&path, b"nested").unwrap();
let content = fs::read_to_string(&path).unwrap();
assert_eq!(content, "nested");
}
#[test]
fn test_atomic_write_overwrites() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
atomic_write(&path, b"first").unwrap();
atomic_write(&path, b"second").unwrap();
let content = fs::read_to_string(&path).unwrap();
assert_eq!(content, "second");
}
#[test]
fn test_atomic_write_json() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.json");
#[derive(serde::Serialize)]
struct Config {
name: String,
value: i32,
}
let config = Config {
name: "test".to_string(),
value: 42,
};
atomic_write_json(&path, &config).unwrap();
let content = fs::read_to_string(&path).unwrap();
assert!(content.contains("\"name\": \"test\""));
assert!(content.contains("\"value\": 42"));
}
#[test]
fn test_read_optional() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("test.txt");
assert!(read_optional(&path).unwrap().is_none());
fs::write(&path, b"content").unwrap();
assert_eq!(read_optional(&path).unwrap(), Some(b"content".to_vec()));
}
}