sqlite_graphrag/
atomic_io.rs1use crate::errors::AppError;
7use std::io::Write;
8use std::path::Path;
9
10pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), AppError> {
19 let parent = path.parent().filter(|p| !p.as_os_str().is_empty());
20 let dir = match parent {
21 Some(p) => {
22 std::fs::create_dir_all(p)?;
23 p
24 }
25 None => Path::new("."),
26 };
27
28 let mut tmp = tempfile::NamedTempFile::new_in(dir).map_err(AppError::Io)?;
29 tmp.write_all(bytes).map_err(AppError::Io)?;
30 tmp.as_file().sync_all().map_err(AppError::Io)?;
31 tmp.persist(path).map_err(|e| {
32 AppError::Io(std::io::Error::other(format!(
33 "atomic persist failed for {}: {e}",
34 path.display()
35 )))
36 })?;
37
38 #[cfg(unix)]
39 {
40 if let Ok(dir_file) = std::fs::File::open(dir) {
41 let _ = dir_file.sync_all();
42 }
43 }
44
45 Ok(())
46}
47
48pub fn write_json_atomic<T: serde::Serialize>(path: &Path, value: &T) -> Result<(), AppError> {
53 let mut json = serde_json::to_string_pretty(value)?;
54 json.push('\n');
55 write_atomic(path, json.as_bytes())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use serde_json::json;
62 use tempfile::TempDir;
63
64 #[test]
65 fn write_atomic_round_trip() {
66 let tmp = TempDir::new().unwrap();
67 let path = tmp.path().join("out.json");
68 write_atomic(&path, b"{\"ok\":true}\n").unwrap();
69 let got = std::fs::read_to_string(&path).unwrap();
70 assert_eq!(got, "{\"ok\":true}\n");
71 }
72
73 #[test]
74 fn write_json_atomic_is_valid_json() {
75 let tmp = TempDir::new().unwrap();
76 let path = tmp.path().join("envelope.json");
77 write_json_atomic(&path, &json!({"a": 1, "b": "x"})).unwrap();
78 let raw = std::fs::read_to_string(&path).unwrap();
79 let v: serde_json::Value = serde_json::from_str(&raw).expect("must parse");
80 assert_eq!(v["a"], 1);
81 }
82
83 #[test]
84 fn write_atomic_creates_parent_dirs() {
85 let tmp = TempDir::new().unwrap();
86 let path = tmp.path().join("nested").join("dir").join("f.txt");
87 write_atomic(&path, b"hi\n").unwrap();
88 assert_eq!(std::fs::read_to_string(&path).unwrap(), "hi\n");
89 }
90}