use std::{
env::{self, VarError},
fmt::Debug,
fs::File,
io::{Read, Write},
path::Path,
};
#[derive(Debug, thiserror::Error)]
pub enum LoadError {
#[error("filing error")]
IO(#[from] std::io::Error),
#[error("decoder error")]
Decoder(#[from] bincode::error::DecodeError),
#[error("encoder error")]
Encoder(#[from] bincode::error::EncodeError),
#[error("builder error: {0}")]
Builder(String),
#[error("DATA_REPO not set")]
DataRepo(#[from] VarError),
}
pub type Result<T> = std::result::Result<T, LoadError>;
pub trait Codec
where
Self: Sized + serde::ser::Serialize,
for<'de> Self: serde::de::Deserialize<'de>,
{
fn load<R>(reader: &mut R) -> Result<Self>
where
R: Read,
{
Ok(bincode::serde::decode_from_std_read(
reader,
bincode::config::standard(),
)?)
}
fn save<W>(&self, writer: &mut W) -> Result<usize>
where
W: Write,
{
Ok(bincode::serde::encode_into_std_write(
self,
writer,
bincode::config::standard(),
)?)
}
}
pub trait Filing: Codec
where
Self: Sized + serde::ser::Serialize,
for<'de> Self: serde::de::Deserialize<'de>,
{
fn from_path<P>(path: P) -> Result<Self>
where
P: AsRef<Path> + Debug,
{
log::info!("decoding from {path:?}");
Self::load(&mut File::open(path)?)
}
fn from_data_repo(file_name: impl AsRef<Path>) -> Result<Self> {
let data_repo = env::var("DATA_REPO")?;
let path = Path::new(&data_repo).join(file_name);
Self::from_path(path)
}
fn to_path<P>(&self, path: P) -> Result<()>
where
P: AsRef<Path> + Debug,
{
log::info!("encoding to {path:?}");
self.save(&mut File::create(path)?)?;
Ok(())
}
fn to_data_repo(&self, file_name: impl AsRef<Path>) -> Result<()> {
let data_repo = env::var("DATA_REPO")?;
let path = Path::new(&data_repo).join(file_name);
Self::to_path(self, path)
}
fn from_path_or_else<P, F, B>(path: P, builder: F) -> Result<Self>
where
P: AsRef<Path> + Debug,
F: FnOnce() -> B,
Self: TryFrom<B>,
<Self as TryFrom<B>>::Error: std::fmt::Debug,
{
Self::from_path(&path).or_else(|_| {
let this =
Self::try_from(builder()).map_err(|e| LoadError::Builder(format!("{e:?}")))?;
this.to_path(path)?;
Ok(this)
})
}
fn from_data_repo_or_else<P, F, B>(file_name: P, builder: F) -> Result<Self>
where
P: AsRef<Path>,
F: FnOnce() -> B,
Self: TryFrom<B>,
<Self as TryFrom<B>>::Error: std::fmt::Debug,
{
let data_repo = env::var("DATA_REPO")?;
let path = Path::new(&data_repo).join(file_name);
Self::from_path_or_else(path, builder)
}
fn from_path_or_default<P, F, B>(path: P) -> Result<Self>
where
P: AsRef<Path> + Debug,
B: Default,
F: FnOnce() -> B,
Self: TryFrom<B>,
<Self as TryFrom<B>>::Error: std::fmt::Debug,
{
Self::from_path_or_else(path, Default::default)
}
fn from_data_repo_or_default<P, F, B>(file_name: P) -> Result<Self>
where
P: AsRef<Path>,
B: Default,
F: FnOnce() -> B,
Self: TryFrom<B>,
<Self as TryFrom<B>>::Error: std::fmt::Debug,
{
Self::from_data_repo_or_else(file_name, Default::default)
}
}