1use fs_err as fs;
26use std::io::{self, Write};
27use std::path::Path;
28
29use fs::OpenOptions;
30
31pub fn safe_write(path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> io::Result<()> {
44 let path = path.as_ref();
45 let content = content.as_ref();
46 let parent = path.parent().unwrap_or_else(|| Path::new("."));
47
48 fs::create_dir_all(parent)?;
50
51 let temp_path = path.with_extension("tmp");
53
54 let mut temp_file = OpenOptions::new()
55 .write(true)
56 .create_new(true)
57 .open(&temp_path)?;
58
59 temp_file.write_all(content)?;
61 temp_file.flush()?;
63 temp_file.sync_all()?;
64 drop(temp_file);
66
67 #[cfg(windows)]
68 {
69 if path.exists() {
70 fs::remove_file(path)?;
71 }
72 }
73 fs::rename(&temp_path, path)?;
74 Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use tempfile::TempDir;
81
82 #[test]
83 fn test_basic_write() -> io::Result<()> {
84 let temp_dir = TempDir::new()?;
85 let test_path = temp_dir.path().join("test.txt");
86
87 let content = b"Hello, World!";
88 safe_write(&test_path, content)?;
89
90 let read_content = fs::read(&test_path)?;
92 assert_eq!(content, read_content.as_slice());
93
94 Ok(())
95 }
96
97 #[test]
98 fn test_nested_directory_creation() -> io::Result<()> {
99 let temp_dir = TempDir::new()?;
100 let test_path = temp_dir.path().join("nested/dirs/test.txt");
101
102 let content = b"Nested content";
103 safe_write(&test_path, content)?;
104
105 assert!(test_path.exists());
106 let read_content = fs::read(&test_path)?;
107 assert_eq!(content, read_content.as_slice());
108
109 Ok(())
110 }
111
112 #[test]
113 fn test_overwrite_existing() -> io::Result<()> {
114 let temp_dir = TempDir::new()?;
115 let test_path = temp_dir.path().join("overwrite.txt");
116
117 safe_write(&test_path, b"Initial content")?;
119
120 let new_content = b"New content";
122 safe_write(&test_path, new_content)?;
123
124 let read_content = fs::read(&test_path)?;
125 assert_eq!(new_content, read_content.as_slice());
126
127 Ok(())
128 }
129}