1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
//! Zero-boilerplate configuration management
//!
//! ### Usage
//!
//! ```rust no_run
//! use serde_derive::Deserialize;
//! use configit::Loader;
//!
//! #[derive(Debug, Deserialize)]
//! pub struct AppConfig {
//!     host: String,
//!     port: u16,
//! }
//!
//! let config = AppConfig::load("config.toml").expect("couldn't load `config.toml` file");
//! println!("config: {config:?}");
//! ```

#[cfg(not(any(feature = "toml_conf", feature = "yaml_conf")))]
compile_error!(
    r#"Exactly one config language feature must be enabled to use configit.
    Please enable one of either the `toml_conf` or `yaml_conf` features."#
);

#[cfg(all(feature = "toml_conf", feature = "yaml_conf"))]
compile_error!(
    r#"Exactly one config language feature must be enabled to compile
    configit.  Please disable one of either the `toml_conf` or `yaml_conf` features.
    NOTE: `toml_conf` is a default feature, so disabling it might mean switching off
    default features for configit in your Cargo.toml"#
);

use std::fs;
use std::path::Path;

#[cfg(feature = "yaml_conf")]
use serde_yaml::{from_str as from_deserialize, to_string as to_serialize};
use thiserror::Error;
#[cfg(feature = "toml_conf")]
use toml::{from_str as from_deserialize, to_string_pretty as to_serialize};

pub type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug, Error)]
pub enum Error {
    #[error("{0}")]
    Io(#[from] std::io::Error),

    #[cfg(feature = "toml_conf")]
    #[error("{0}")]
    TomlDeserialize(#[from] toml::de::Error),

    #[cfg(feature = "toml_conf")]
    #[error("{0}")]
    TomlSerialize(#[from] toml::ser::Error),

    #[cfg(feature = "yaml_conf")]
    #[error("{0}")]
    Yaml(#[from] serde_yaml::Error),
}

pub trait Loader {
    type Output;

    fn load<P: AsRef<Path>>(filename: P) -> Result<Self::Output>;
    fn load_from_reader<R: std::io::Read>(reader: &mut R) -> Result<Self::Output>;
}

pub trait Storage {
    fn store<P: AsRef<Path>>(&self, filename: P) -> Result<()>;
    fn store_to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()>;
}

impl<T> Loader for T
where
    T: serde::de::DeserializeOwned + Sized,
{
    type Output = Self;

    fn load<P: AsRef<Path>>(filename: P) -> Result<Self> {
        let content = fs::read_to_string(filename)?;
        let data = from_deserialize(&content)?;
        Ok(data)
    }

    fn load_from_reader<R: std::io::Read>(reader: &mut R) -> Result<Self::Output> {
        let mut content = String::new();
        reader.read_to_string(&mut content)?;
        let data = from_deserialize(&content)?;
        Ok(data)
    }
}

impl<T> Storage for T
where
    T: serde::ser::Serialize + Sized,
{
    fn store<P: AsRef<Path>>(&self, filename: P) -> Result<()> {
        let content = to_serialize(self)?;
        fs::write(filename, content)?;
        Ok(())
    }

    fn store_to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
        let content = to_serialize(self)?;
        writer.write_all(content.as_bytes())?;
        Ok(())
    }
}