vipera 0.2.4

simple configuration file crate, it just works.
Documentation
mod config;

use std::{fs::read_to_string, path::PathBuf};

use anyhow::{bail, Context, Result};

pub use config::Configuration;

#[derive(Default)]
pub struct Vipera {
    config_name: Option<String>,
    config_paths: Vec<PathBuf>,
    config_type: Option<ConfigType>,
}

enum ConfigType {
    Toml,
    Yaml,
    Json,
}

impl Vipera {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_config_name(mut self, name: impl Into<String>) -> Result<Self> {
        let name: String = name.into();
        let path = PathBuf::from(&name);
        let ext = path
            .extension()
            .and_then(|s| s.to_str())
            .map(|s| s.to_lowercase())
            .context(format!(
                "Could not determine a valid file extension for path: {:?}",
                path
            ))?;
        let config_type = match ext.as_str() {
            "toml" => ConfigType::Toml,
            "yaml" | "yml" => ConfigType::Yaml,
            "json" => ConfigType::Json,
            _ => bail!("Unsupported configuration format: .{}", ext),
        };
        self.config_name = Some(name);
        self.config_type = Some(config_type);
        Ok(self)
    }

    pub fn add_config_path(mut self, path: impl Into<String>) -> Result<Self> {
        let path_str = path.into();
        let path = PathBuf::from(shellexpand::full(&path_str)?.to_string());
        self.config_paths.push(path);
        Ok(self)
    }

    pub(crate) fn read_in_config<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
        let config_path = self.get_config_file()?;
        let content = read_to_string(&config_path)?;
        let this = match self.config_type.as_ref().unwrap() {
            #[cfg(feature = "toml")]
            ConfigType::Toml => toml::from_str::<T>(&content)?,
            #[cfg(feature = "yaml")]
            ConfigType::Yaml => serde_yml::from_str::<T>(&content)?,
            #[cfg(feature = "json")]
            ConfigType::Json => serde_json::from_str::<T>(&content)?,
            #[allow(unreachable_patterns)]
            _ => bail!("The format is not supported by vipera yet, or not enabled in features"),
        };
        Ok(this)
    }

    pub(crate) fn get_config_file(&self) -> Result<PathBuf> {
        for path in &self.config_paths {
            let config_path = path.join(self.config_name.as_ref().unwrap());
            if config_path.is_file() {
                return Ok(config_path);
            }
        }
        bail!("The config file is not found");
    }
}