lunar-lib 0.11.0

Common utilities for lunar applications
Documentation
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),

    /// A serialization or deserialization error occured. These are treated as non-important for cache files
    #[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 {}