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, } }