use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use crate::error::CliError;
pub fn write_atomic(dest: &Path, content: &[u8]) -> Result<(), CliError> {
let parent = dest.parent().ok_or_else(|| {
CliError::io(
dest.to_string_lossy().into_owned(),
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"path has no parent directory",
),
)
})?;
fs::create_dir_all(parent)
.map_err(|e| CliError::io(parent.to_string_lossy().into_owned(), e))?;
let tmp_path = dest.with_extension("tmp");
let write_result = (|| -> std::io::Result<()> {
let mut file = File::create(&tmp_path)?;
file.write_all(content)?;
file.flush()?;
file.sync_all()?;
Ok(())
})();
if let Err(e) = write_result {
let _ = fs::remove_file(&tmp_path);
return Err(CliError::io(tmp_path.to_string_lossy().into_owned(), e));
}
fs::rename(&tmp_path, dest)
.map_err(|e| CliError::io(dest.to_string_lossy().into_owned(), e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn atomic_write_creates_file() {
let dir = TempDir::new().unwrap();
let dest = dir.path().join("file.txt");
write_atomic(&dest, b"test content").unwrap();
let read_back = std::fs::read(&dest).unwrap();
assert_eq!(read_back, b"test content");
}
#[test]
fn atomic_write_creates_directories() {
let dir = TempDir::new().unwrap();
let dest = dir.path().join("subdir").join("other").join("file.txt");
write_atomic(&dest, b"test").unwrap();
assert!(dest.exists());
}
#[test]
fn atomic_write_overwrites_existing() {
let dir = TempDir::new().unwrap();
let dest = dir.path().join("file.txt");
write_atomic(&dest, b"first version").unwrap();
write_atomic(&dest, b"second version").unwrap();
let read_back = std::fs::read(&dest).unwrap();
assert_eq!(read_back, b"second version");
}
}