use std::{
env::{self, VarError},
fmt::Debug,
fs::File,
io::{Read, Write},
path::Path,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum FilingError {
#[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, FilingError>;
pub trait Codec
where
Self: Sized + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
{
#[inline]
fn decode<R>(reader: &mut R) -> Result<Self>
where
R: Read,
{
Ok(bincode::serde::decode_from_std_read(
reader,
bincode::config::standard(),
)?)
}
#[inline]
fn encode<W>(&self, writer: &mut W) -> Result<()>
where
W: Write,
{
bincode::serde::encode_into_std_write(self, writer, bincode::config::standard())?;
Ok(())
}
}
impl<T> Filing for T where
T: Sized + Codec + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>
{
}
pub trait Filing: Codec
where
Self: Sized + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
{
fn from_path<P>(path: P) -> Result<Self>
where
P: AsRef<Path> + Debug,
{
log::info!("decoding from {path:?}");
let file = File::open(path)?;
let mut buffer = std::io::BufReader::new(file);
Self::decode(&mut buffer)
}
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:?}");
let file = File::create(path)?;
let mut buffer = std::io::BufWriter::new(file);
self.encode(&mut buffer)?;
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| FilingError::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)
}
fn from_path_or<P, B>(path: P, current_builder: B) -> Result<Self>
where
P: AsRef<Path> + Debug,
Self: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
B: Clone + PartialEq + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
<Self as TryFrom<B>>::Error: std::fmt::Debug,
{
match <ObjectAndBuilder<Self, B> as Filing>::from_path(&path) {
Ok(ObjectAndBuilder { object, builder }) if builder == current_builder => Ok(object),
_ => {
let object = Self::try_from(current_builder.clone())
.map_err(|e| FilingError::Builder(format!("{e:?}")))?;
let this = ObjectAndBuilder {
object,
builder: current_builder,
};
this.to_path(path)?;
let ObjectAndBuilder { object, .. } = this;
Ok(object)
}
}
}
fn from_data_repo_or<P, B>(file_name: P, current_builder: B) -> Result<Self>
where
P: AsRef<Path>,
Self: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
B: Clone + PartialEq + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
<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(path, current_builder)
}
}
#[derive(Serialize, Deserialize)]
struct ObjectAndBuilder<T, B>
where
T: TryFrom<B>,
{
object: T,
builder: B,
}
impl<T, B> Codec for ObjectAndBuilder<T, B>
where
T: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
B: serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
{
}