exocore_core/simple_store/
json_disk_store.rs

1use std::{
2    fs::OpenOptions,
3    io::{Error, ErrorKind},
4    path::{Path, PathBuf},
5};
6
7use super::*;
8
9pub struct JsonDiskStore<T: Serialize + DeserializeOwned> {
10    path: PathBuf,
11    phantom: std::marker::PhantomData<T>,
12}
13
14impl<T: Serialize + DeserializeOwned> JsonDiskStore<T> {
15    pub fn new<P: AsRef<Path>>(path: P) -> Result<JsonDiskStore<T>> {
16        let meta = std::fs::metadata(path.as_ref());
17        if meta.map(|m| m.is_dir()).unwrap_or(false) {
18            error!("Path {:?} is a directory, not a file.", path.as_ref());
19            return Err(Error::new(
20                ErrorKind::InvalidData,
21                "Path was a directory, not a file",
22            ));
23        }
24
25        Ok(JsonDiskStore {
26            path: path.as_ref().to_path_buf(),
27            phantom: std::marker::PhantomData,
28        })
29    }
30}
31
32impl<T: Serialize + DeserializeOwned> SimpleStore<T> for JsonDiskStore<T> {
33    fn read(&self) -> Result<Option<T>> {
34        let meta = std::fs::metadata(&self.path);
35        if meta.is_err() {
36            return Ok(None);
37        }
38
39        let file = OpenOptions::new()
40            .read(true)
41            .write(false)
42            .create(false)
43            .open(&self.path)?;
44
45        serde_json::from_reader(file).map_err(|err| {
46            error!("Couldn't decode stored JSON file: {}", err);
47            Error::new(ErrorKind::InvalidData, "Invalid json data")
48        })
49    }
50
51    fn write(&self, value: &T) -> Result<()> {
52        let tmp_file = self.path.with_extension("tmp");
53        let file = OpenOptions::new()
54            .read(false)
55            .write(true)
56            .create(true)
57            .truncate(true)
58            .open(&tmp_file)?;
59        serde_json::to_writer(file, value)?;
60
61        std::fs::rename(&tmp_file, &self.path)
62    }
63}
64
65#[cfg(test)]
66mod test {
67    use serde_derive::{Deserialize, Serialize};
68    use tempfile::tempdir;
69
70    use super::*;
71
72    #[test]
73    fn test_not_dir() {
74        let dir = tempdir().unwrap();
75        let store = JsonDiskStore::<String>::new(dir.path());
76        assert!(store.is_err());
77
78        let file = dir.path().join("file");
79        let store = JsonDiskStore::<String>::new(file);
80        assert!(store.is_ok());
81    }
82
83    #[test]
84    fn test_write_read() {
85        let dir = tempdir().unwrap();
86        let file = dir.path().join("store");
87        let store = JsonDiskStore::<TestStruct>::new(file).unwrap();
88
89        let data = TestStruct {
90            data: "hello".to_string(),
91        };
92        store.write(&data).unwrap();
93
94        let read_data = store.read().unwrap();
95        assert_eq!(read_data, Some(data));
96    }
97
98    #[derive(PartialEq, Debug, Serialize, Deserialize)]
99    struct TestStruct {
100        data: String,
101    }
102}