use std::io;
use std::path::PathBuf;
use serde::de::DeserializeOwned;
use crate::transport::{self, Transport};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IO error")]
Io {
#[from]
source: io::Error,
},
#[error("JSON serialization error")]
Json {
source: serde_json::Error,
path: PathBuf,
},
#[error("Transport error")]
Transport {
#[from]
source: transport::Error,
},
}
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) fn write_json<T, TR>(transport: &TR, relpath: &str, obj: &T) -> Result<()>
where
T: serde::Serialize,
TR: AsRef<dyn Transport>,
{
let mut s: String = serde_json::to_string(&obj).map_err(|source| Error::Json {
source,
path: relpath.into(),
})?;
s.push('\n');
transport
.as_ref()
.write_file(relpath, s.as_bytes())
.map_err(Error::from)
}
pub(crate) fn read_json<T, TR>(transport: &TR, path: &str) -> Result<Option<T>>
where
T: DeserializeOwned,
TR: AsRef<dyn Transport>,
{
let bytes = match transport.as_ref().read_file(path) {
Ok(b) => b,
Err(err) if err.is_not_found() => return Ok(None),
Err(err) => return Err(err.into()),
};
serde_json::from_slice(&bytes)
.map(|t| Some(t))
.map_err(|source| Error::Json {
source,
path: path.into(),
})
}
#[cfg(test)]
mod tests {
use assert_fs::prelude::*;
use serde::{Deserialize, Serialize};
use crate::transport::local::LocalTransport;
use super::*;
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct TestContents {
pub id: u64,
pub weather: String,
}
#[test]
fn write_json_to_transport() {
let temp = assert_fs::TempDir::new().unwrap();
let entry = TestContents {
id: 42,
weather: "cold".to_string(),
};
let filename = "test.json";
let transport = LocalTransport::new(temp.path());
super::write_json(&transport, filename, &entry).unwrap();
let json_child = temp.child("test.json");
json_child.assert(concat!(r#"{"id":42,"weather":"cold"}"#, "\n"));
temp.close().unwrap();
}
#[test]
fn read_json_from_transport() {
let temp = assert_fs::TempDir::new().unwrap();
temp.child("test.json")
.write_str(r#"{"id": 42, "weather": "cold"}"#)
.unwrap();
let transport = LocalTransport::new(temp.path());
let content: TestContents = read_json(&transport, "test.json")
.expect("no error")
.expect("file exists");
assert_eq!(
content,
TestContents {
id: 42,
weather: "cold".to_owned()
}
);
temp.close().unwrap();
}
}