pacenotes_server/store/
manifest_file.rs1pub fn read_manifest(path: &std::path::Path) -> anyhow::Result<pacenotes_shared::Manifest> {
6 let bytes = match std::fs::read(path) {
7 Ok(bytes) => bytes,
8 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
9 return Ok(pacenotes_shared::Manifest::default())
10 }
11 Err(e) => anyhow::bail!("reading {}: {e}", path.display()),
12 };
13 let manifest: pacenotes_shared::Manifest = serde_json::from_slice(&bytes)
14 .map_err(|e| anyhow::anyhow!("parsing {}: {e}", path.display()))?;
15
16 for package in &manifest.packages {
17 if !pacenotes_shared::valid_id(&package.game) {
18 anyhow::bail!("invalid game id {:?} in manifest", package.game);
19 }
20 if !pacenotes_shared::valid_id(&package.directory) {
21 anyhow::bail!("invalid directory name {:?} in manifest", package.directory);
22 }
23 if !pacenotes_shared::valid_name(&package.name) {
24 anyhow::bail!("invalid package name {:?} in manifest", package.name);
25 }
26 }
27 Ok(manifest)
28}
29
30pub fn write_manifest(
34 data_dir: &std::path::Path,
35 manifest: &pacenotes_shared::Manifest,
36) -> anyhow::Result<std::path::PathBuf> {
37 let path = data_dir.join(crate::store::MANIFEST_FILE);
38 let temp = data_dir.join(format!(".{}.tmp", crate::store::MANIFEST_FILE));
39 let json = serde_json::to_string_pretty(manifest)?;
40 std::fs::write(&temp, format!("{json}\n"))
41 .map_err(|e| anyhow::anyhow!("writing {}: {e}", temp.display()))?;
42 std::fs::rename(&temp, &path).map_err(|e| {
43 let _ = std::fs::remove_file(&temp);
44 anyhow::anyhow!("replacing {}: {e}", path.display())
45 })?;
46 Ok(path)
47}
48
49#[cfg(test)]
50mod tests {
51 #[test]
52 fn the_manifest_is_replaced_without_leaving_a_temp_file() {
53 let tmp = tempfile::tempdir().unwrap();
54 let dir = tmp.path();
55 std::fs::write(dir.join(crate::store::MANIFEST_FILE), b"old").unwrap();
56
57 let path = super::write_manifest(dir, &pacenotes_shared::Manifest::default()).unwrap();
58 assert_eq!(path, dir.join(crate::store::MANIFEST_FILE));
59 assert!(super::read_manifest(&path).unwrap().packages.is_empty());
60 let leftovers: Vec<_> = std::fs::read_dir(dir).unwrap().collect();
61 assert_eq!(leftovers.len(), 1, "{leftovers:?}");
62 }
63}