Skip to main content

runner_manager_platform/
settings_file.rs

1//! Small operator settings kept as one JSON file each.
2
3use std::io;
4use std::path::Path;
5
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8
9/// The settings at `path`, or the defaults when there is no file.
10///
11/// # Errors
12///
13/// When the file cannot be read or does not parse.
14pub fn load<T: Default + DeserializeOwned>(path: &Path) -> io::Result<T> {
15    match std::fs::read(path) {
16        Ok(bytes) => serde_json::from_slice(&bytes)
17            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error)),
18        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(T::default()),
19        Err(error) => Err(error),
20    }
21}
22
23/// Writes `value` to `path` atomically, so a crash never leaves a truncated
24/// file that every later load would refuse.
25///
26/// # Errors
27///
28/// When the file cannot be written.
29pub fn save(path: &Path, value: &impl Serialize) -> io::Result<()> {
30    let bytes = serde_json::to_vec_pretty(value).map_err(io::Error::other)?;
31    crate::host_fitness::write_atomically(path, &bytes)
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[derive(Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
39    struct Example {
40        value: u8,
41    }
42
43    #[test]
44    fn a_missing_file_is_the_defaults_and_a_saved_one_reads_back() {
45        let directory = tempfile::tempdir().unwrap();
46        let path = directory.path().join("nested").join("example.json");
47        assert_eq!(load::<Example>(&path).unwrap(), Example::default());
48        save(&path, &Example { value: 7 }).unwrap();
49        assert_eq!(load::<Example>(&path).unwrap(), Example { value: 7 });
50        std::fs::write(&path, b"{").unwrap();
51        assert!(load::<Example>(&path).is_err());
52    }
53}