1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use std::{
    fs::OpenOptions,
    io::{Error, ErrorKind},
    path::{Path, PathBuf},
};

use super::*;

pub struct JsonDiskStore<T: Serialize + DeserializeOwned> {
    path: PathBuf,
    phantom: std::marker::PhantomData<T>,
}

impl<T: Serialize + DeserializeOwned> JsonDiskStore<T> {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<JsonDiskStore<T>> {
        let meta = std::fs::metadata(path.as_ref());
        if meta.map(|m| m.is_dir()).unwrap_or(false) {
            error!("Path {:?} is a directory, not a file.", path.as_ref());
            return Err(Error::new(
                ErrorKind::InvalidData,
                "Path was a directory, not a file",
            ));
        }

        Ok(JsonDiskStore {
            path: path.as_ref().to_path_buf(),
            phantom: std::marker::PhantomData,
        })
    }
}

impl<T: Serialize + DeserializeOwned> SimpleStore<T> for JsonDiskStore<T> {
    fn read(&self) -> Result<Option<T>> {
        let meta = std::fs::metadata(&self.path);
        if meta.is_err() {
            return Ok(None);
        }

        let file = OpenOptions::new()
            .read(true)
            .write(false)
            .create(false)
            .open(&self.path)?;

        serde_json::from_reader(file).map_err(|err| {
            error!("Couldn't decode stored JSON file: {}", err);
            Error::new(ErrorKind::InvalidData, "Invalid json data")
        })
    }

    fn write(&self, value: &T) -> Result<()> {
        let tmp_file = self.path.with_extension("tmp");
        let file = OpenOptions::new()
            .read(false)
            .write(true)
            .create(true)
            .open(&tmp_file)?;
        serde_json::to_writer(file, value)?;

        std::fs::rename(&tmp_file, &self.path)
    }
}

#[cfg(test)]
mod test {
    use serde_derive::{Deserialize, Serialize};
    use tempfile::tempdir;

    use super::*;

    #[test]
    fn test_not_dir() {
        let dir = tempdir().unwrap();
        let store = JsonDiskStore::<String>::new(dir.path());
        assert!(store.is_err());

        let file = dir.path().join("file");
        let store = JsonDiskStore::<String>::new(&file);
        assert!(store.is_ok());
    }

    #[test]
    fn test_write_read() {
        let dir = tempdir().unwrap();
        let file = dir.path().join("store");
        let store = JsonDiskStore::<TestStruct>::new(&file).unwrap();

        let data = TestStruct {
            data: "hello".to_string(),
        };
        store.write(&data).unwrap();

        let read_data = store.read().unwrap();
        assert_eq!(read_data, Some(data));
    }

    #[derive(PartialEq, Debug, Serialize, Deserialize)]
    struct TestStruct {
        data: String,
    }
}