use std::{fs, io, path::Path};
use serde::{Serialize, de::DeserializeOwned};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CacheError {
#[error("{0}")]
Io(#[from] io::Error),
#[error("Serialization or deserialization failed: {0}")]
Serde(Box<dyn std::error::Error>),
}
pub trait Cachable: Default {
fn save_to_cache(&self, path: impl AsRef<Path>) -> Result<(), CacheError>
where
Self: Serialize,
{
let path = path.as_ref();
match fs::symlink_metadata(path) {
Ok(m) => {
if m.is_dir() {
return Err(CacheError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"input path was a directory",
)));
}
}
Err(err) if err.kind() == io::ErrorKind::NotFound => (),
Err(err) => return Err(CacheError::Io(err)),
}
fs::create_dir_all(
path.parent()
.expect("Path was confirmed to be a file above"),
)?;
let writer = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
postcard::to_io(self, writer).map_err(|err| CacheError::Serde(Box::new(err)))?;
Ok(())
}
fn load_from_cache(path: impl AsRef<Path>) -> Self
where
Self: DeserializeOwned,
{
let path = path.as_ref();
let Ok(file) = fs::File::open(path) else {
return Self::default();
};
let reader = io::BufReader::new(file);
let mut scratch = [0u8; 4096];
postcard::from_io((reader, &mut scratch))
.map(|(t, _)| t)
.unwrap_or_default()
}
}
impl<T: Default> Cachable for T {}