agentmux_srv\registry/
atomic.rs1use std::io::Write;
8use std::path::Path;
9
10pub fn write_atomic(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
15 let parent = path.parent().ok_or_else(|| {
16 std::io::Error::new(
17 std::io::ErrorKind::InvalidInput,
18 "write_atomic: path has no parent",
19 )
20 })?;
21 std::fs::create_dir_all(parent)?;
22 let mut tmp = tempfile::Builder::new()
23 .prefix(".registry-tmp-")
24 .suffix(".json")
25 .tempfile_in(parent)?;
26 tmp.as_file_mut().write_all(bytes)?;
27 tmp.as_file_mut().sync_all()?;
28 tmp.persist(path).map_err(|e| e.error)?;
29 Ok(())
30}
31
32pub fn rename_atomic(from: &Path, to: &Path) -> std::io::Result<()> {
34 if let Some(parent) = to.parent() {
35 std::fs::create_dir_all(parent)?;
36 }
37 std::fs::rename(from, to)
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn write_atomic_creates_parent_and_target() {
46 let tmp = tempfile::tempdir().unwrap();
47 let target = tmp.path().join("nested").join("file.json");
48 write_atomic(&target, b"hello").unwrap();
49 assert_eq!(std::fs::read(&target).unwrap(), b"hello");
50 }
51
52 #[test]
53 fn write_atomic_overwrites() {
54 let tmp = tempfile::tempdir().unwrap();
55 let target = tmp.path().join("a.json");
56 write_atomic(&target, b"v1").unwrap();
57 write_atomic(&target, b"v2-longer").unwrap();
58 assert_eq!(std::fs::read(&target).unwrap(), b"v2-longer");
59 }
60
61 #[test]
62 fn rename_atomic_moves_file() {
63 let tmp = tempfile::tempdir().unwrap();
64 let from = tmp.path().join("from.json");
65 let to = tmp.path().join("retired").join("from.json");
66 std::fs::write(&from, b"x").unwrap();
67 rename_atomic(&from, &to).unwrap();
68 assert!(!from.exists());
69 assert_eq!(std::fs::read(&to).unwrap(), b"x");
70 }
71
72 #[test]
73 fn rename_atomic_does_not_create_when_missing() {
74 let tmp = tempfile::tempdir().unwrap();
75 let from = tmp.path().join("missing.json");
76 let to = tmp.path().join("retired.json");
77 let err = rename_atomic(&from, &to).unwrap_err();
78 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
79 }
80}