summa 0.8.8

Fast full-text search server
use crate::errors::{Error, SummaResult, ValidationError};
use config::{Config, Environment, File};
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};

pub trait Persistable {
    fn save(&self) -> SummaResult<&Self>;
}

pub trait Loadable {
    fn from_file(config_filepath: &Path, env_prefix: Option<&str>) -> SummaResult<Self>
    where
        Self: Sized;
}

pub struct AutosaveLockWriteGuard<'a, T: Persistable> {
    data: &'a mut T,
}

impl<'a, T: Persistable> AutosaveLockWriteGuard<'a, T> {
    pub fn new(data: &'a mut T) -> Self {
        AutosaveLockWriteGuard { data }
    }
}

impl<'a, T: Persistable> Drop for AutosaveLockWriteGuard<'a, T> {
    fn drop(&mut self) {
        match self.data.save() {
            Ok(_) | Err(Error::Validation(ValidationError::MissingPath(_))) => (),
            Err(e) => panic!("{:?}", e),
        };
    }
}

impl<'a, T: Persistable> Deref for AutosaveLockWriteGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.data
    }
}

impl<'a, T: Persistable> DerefMut for AutosaveLockWriteGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

#[derive(Clone, Debug)]
pub struct ConfigHolder<TConfig: Serialize> {
    config: TConfig,
    config_filepath: Option<PathBuf>,
}

impl<'a, TConfig: Serialize + Deserialize<'a>> ConfigHolder<TConfig> {
    pub fn file(config: TConfig, config_filepath: &Path) -> Self {
        ConfigHolder {
            config,
            config_filepath: Some(config_filepath.to_path_buf()),
        }
    }
    pub fn memory(config: TConfig) -> Self {
        ConfigHolder { config, config_filepath: None }
    }
    pub fn autosave(&mut self) -> AutosaveLockWriteGuard<ConfigHolder<TConfig>> {
        AutosaveLockWriteGuard::new(self)
    }
}

impl<'a, TConfig: Serialize + Deserialize<'a>> Loadable for ConfigHolder<TConfig> {
    fn from_file(config_filepath: &Path, env_prefix: Option<&str>) -> SummaResult<ConfigHolder<TConfig>> {
        let mut s = Config::builder();
        if !config_filepath.exists() {
            return Err(ValidationError::MissingPath(config_filepath.to_path_buf()).into());
        }
        if config_filepath.exists() {
            s = s.add_source(File::from(config_filepath));
        }
        if let Some(env_prefix) = env_prefix {
            s = s.add_source(Environment::with_prefix(env_prefix).separator("."));
        }
        let config: TConfig = s.build()?.try_deserialize().map_err(Error::Config)?;
        let config_holder = ConfigHolder::file(config, config_filepath);
        Ok(config_holder)
    }
}

impl<TConfig: Serialize> Persistable for ConfigHolder<TConfig> {
    fn save(&self) -> SummaResult<&Self> {
        self.config_filepath
            .as_ref()
            .map(|config_filepath| {
                std::fs::File::create(config_filepath)
                    .and_then(|mut file| file.write_all(&serde_yaml::to_vec(&self.config).unwrap()))
                    .map_err(|e| Error::IO((e, Some(config_filepath.to_path_buf()))))?;
                Ok(self)
            })
            .unwrap_or_else(|| Err(Error::Validation(ValidationError::MissingPath(PathBuf::new()))))
    }
}

impl<TConfig: Serialize> Deref for ConfigHolder<TConfig> {
    type Target = TConfig;

    fn deref(&self) -> &Self::Target {
        &self.config
    }
}

impl<TConfig: Serialize> DerefMut for ConfigHolder<TConfig> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.config
    }
}

impl<TConfig: Serialize> std::fmt::Display for ConfigHolder<TConfig> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", serde_yaml::to_string(&self.config).unwrap())
    }
}