Skip to main content

agent_teams/util/
atomic_write.rs

1//! Atomic file writes via tempfile + rename.
2//!
3//! Mirrors the Python pattern of `os.replace(tmp, target)` to ensure
4//! readers never see a partially-written file.
5
6use std::path::Path;
7
8use crate::error::{Error, Result};
9
10/// Atomically write `data` to `path`.
11///
12/// Creates a temporary file in the same directory, writes `data`,
13/// then renames it to `path`. On Unix this is atomic because
14/// rename(2) on the same filesystem is guaranteed atomic.
15pub fn atomic_write(path: &Path, data: &[u8]) -> Result<()> {
16    let dir = path.parent().ok_or_else(|| Error::AtomicWriteFailed {
17        path: path.to_path_buf(),
18        reason: "no parent directory".into(),
19    })?;
20
21    // Ensure parent directory exists
22    std::fs::create_dir_all(dir)?;
23
24    let mut tmp = tempfile::NamedTempFile::new_in(dir).map_err(|e| Error::AtomicWriteFailed {
25        path: path.to_path_buf(),
26        reason: e.to_string(),
27    })?;
28
29    std::io::Write::write_all(&mut tmp, data).map_err(|e| Error::AtomicWriteFailed {
30        path: path.to_path_buf(),
31        reason: e.to_string(),
32    })?;
33
34    tmp.persist(path).map_err(|e| Error::AtomicWriteFailed {
35        path: path.to_path_buf(),
36        reason: e.to_string(),
37    })?;
38
39    Ok(())
40}
41
42/// Atomically write a serde-serializable value as pretty-printed JSON.
43pub fn atomic_write_json<T: serde::Serialize>(path: &Path, value: &T) -> Result<()> {
44    let data = serde_json::to_vec_pretty(value)?;
45    atomic_write(path, &data)
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn atomic_write_creates_file() {
54        let dir = tempfile::tempdir().unwrap();
55        let path = dir.path().join("test.json");
56
57        atomic_write(&path, b"hello world").unwrap();
58        assert_eq!(std::fs::read_to_string(&path).unwrap(), "hello world");
59    }
60
61    #[test]
62    fn atomic_write_json_round_trip() {
63        let dir = tempfile::tempdir().unwrap();
64        let path = dir.path().join("data.json");
65
66        let data = serde_json::json!({"key": "value", "num": 42});
67        atomic_write_json(&path, &data).unwrap();
68
69        let read: serde_json::Value =
70            serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
71        assert_eq!(read["key"], "value");
72        assert_eq!(read["num"], 42);
73    }
74}