manaconf 0.2.0

a layered configuration library
Documentation
//! # manaconf
//!
//! Library for building a layered configuration provider.
//!
//! TODO: Better docs here

use std::error::Error;

mod helpers;
mod key;
mod value;
pub mod sources;

pub use key::{Key, KeyBuf};
pub use value::{TryFromValue, Value};

/// Trait implemented by config value sources
pub trait Source {
    /// The error type used by this `Source`
    type Error;

    /// Gets a value from the source with the given `key`
    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error>;
}

/// Wraps a source to map it's error to `Box<dyn Error>`
struct BoxedErrorSource<S> {
    contained: S,
}

impl<S, E> From<S> for BoxedErrorSource<S>
where
    E: Error + 'static,
    S: Source<Error = E>,
{
    fn from(source: S) -> Self {
        BoxedErrorSource { contained: source }
    }
}

impl<S, E> Source for BoxedErrorSource<S>
where
    E: Error + 'static,
    S: Source<Error = E>,
{
    type Error = Box<dyn Error>;

    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error> {
        self.contained
            .get_value(key)
            .map_err(|e| Box::new(e) as Box<dyn Error>)
    }
}

/// Error returned from an implementation of `ValueRead`
#[derive(Debug)]
pub enum ValueReadError {
    /// Error occurred when reading from a `Source`
    SourceReadError(Box<dyn Error>),
    /// Error occurred when attempting to convert a `Value`
    /// to a requested type
    ValueConversionError(Box<dyn Error>),
    /// Value was not present, but was expected to be so
    ValueNotPresent,
}

impl Error for ValueReadError {}
impl std::fmt::Display for ValueReadError {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ValueReadError::SourceReadError(e) => {
                write!(
                    fmt,
                    "Error occurred while reading from configuration source: "
                )?;
                e.fmt(fmt)
            }
            ValueReadError::ValueConversionError(e) => {
                write!(fmt, "Value failed to be converted to requested type: ")?;
                e.fmt(fmt)
            }
            ValueReadError::ValueNotPresent => write!(fmt, "Requested value did not exist"),
        }
    }
}

pub trait ValueRead: Sized {
    /// Gets a value from configuration using the given `key`
    ///
    /// Attempts to convert the value to `T` if the conversion is supported
    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
    where
        K: AsRef<Key>,
        T: TryFromValue;

    /// Gets a value from configuration using the given `key` where the value
    /// is expected to exist, and thus it's an error if it doesn't.
    ///
    /// Attempts to convert the value to `T` if the conversion is supported
    fn get_expected_value<T, K>(&self, key: K) -> Result<T, ValueReadError>
    where
        K: AsRef<Key>,
        T: TryFromValue,
    {
        self.get_value(key)?.ok_or(ValueReadError::ValueNotPresent)
    }

    /// Bind values to type `T`
    fn bind<T: TryFromValueRead>(&self) -> Result<T, ValueReadError> {
        <T as TryFromValueRead>::try_from(self)
    }
}
/// Trait to implement on types that can be constructed from reading values
/// from a `ValueRead`
pub trait TryFromValueRead: Sized {
    fn try_from<R: ValueRead>(value_read: &R) -> Result<Self, ValueReadError>;
}

/// A section of configuration
///
/// This acts as a pre-applied prefix to a config key when fetching values
///
/// # Example
/// ```
/// let section = config.section("My::Config::Section");
/// // This is now the same as requesting `My::Config::Section::Value`
/// let value: String = section.get_value("Value");
/// ```
pub struct Section<'a> {
    key: KeyBuf,
    config: &'a Config,
}

impl<'a> Section<'a> {
    /// Creates a further subsection from this section, similar to a section
    /// created by calling `Config::section`, except in this case the current
    /// sections prefix is prepended to the `key` passed into `section`
    ///
    /// # Example
    /// ```
    /// let section = config.section("first");
    /// let subsection = section.section("second");
    /// // Will read value from "first::second::value"
    /// let value: String = subsection.get_value("value");
    /// ```
    pub fn section<K: AsRef<Key>>(&self, key: K) -> Section {
        self.section(key.as_ref())
    }

    fn _section(&self, key: &Key) -> Section {
        Section { 
            key: self.key.extend_with_suffix(key),
            config: self.config
        }
    }
}

impl<'a> ValueRead for Section<'a> {
    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
    where
        K: AsRef<Key>,
        T: TryFromValue,
    {
        let mut new_key = self.key.to_key_buf();
        new_key.push(key.as_ref());
        self.config.get_value(&new_key)
    }
}

pub struct Config {
    sources: Vec<Box<dyn Source<Error = Box<dyn Error>>>>,
}

impl Config {
    /// Get a section of a config at a given key path
    ///
    /// This allows config values to be accessed from Section,
    /// without having to specify the prefix
    pub fn section<K: AsRef<Key>>(&self, key: K) -> Section {
        Section {
            key: key.as_ref().to_owned(),
            config: self,
        }
    }

    fn _get_value(&self, key: &Key) -> Result<Option<Value>, ValueReadError> {
        if self.sources.len() == 0 {
            return Ok(None);
        }

        // Go through our sources, skipping any that return `Ok(None)`
        // and taking the first one to return `Ok(Some(_))` or `Err()`
        self.sources
            .iter()
            .map(|s| s.get_value(key))
            .skip_while(|v| match v {
                Ok(None) => true,
                _ => false,
            })
            .next()
            .unwrap_or(Ok(None))
            .map_err(|e| ValueReadError::SourceReadError(e))
    }
}

impl ValueRead for Config {
    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
    where
        K: AsRef<Key>,
        T: TryFromValue,
    {
        self._get_value(key.as_ref())?
            .map(|v| T::try_from_value(v))
            .transpose()
            .map_err(|e| ValueReadError::ValueConversionError(Box::new(e)))
    }
}

type BoxedDynamicSource = Box<dyn Source<Error = Box<dyn Error>>>;

pub struct Builder {
    sources: Vec<BoxedDynamicSource>,
}

impl Builder {
    /// Creates a new `Builder` for constructing a `Config`
    pub fn new() -> Self {
        Self {
            sources: Vec::new(),
        }
    }

    /// Adds a new `Source` such that the sources provided keys are available
    /// from the root.
    ///
    /// Note: The order that sources are added, are the order that they are
    /// checked for values, so you want to add your source with the highest
    /// priority first.
    pub fn add_source<S, E>(mut self, source: S) -> Self
    where
        E: Error + 'static,
        S: Source<Error = E> + 'static,
    {
        self.sources.push(Box::new(BoxedErrorSource::from(source)));
        self
    }

    /// Adds a new `Source` such that the sources provided keys are available
    /// from the supplied `prefix`.
    ///
    /// Note: The order that sources are added, are the order that they are
    /// checked for values, so you want to add your source with the highest
    /// priority first.
    pub fn add_source_at_prefix<S, E, K>(self, source: S, prefix: K) -> Self
    where
        E: Error + 'static,
        S: Source<Error = E> + 'static,
        K: AsRef<Key>
    {
        self.add_source(WithPrefixSource { 
            prefix: prefix.as_ref().to_owned(), 
            source 
        })
    }

    /// Build the `Config` from the setup supplied by this builder
    pub fn build(self) -> Config {
        Config {
            sources: self.sources,
        }
    }
}

/// Wraps a source so that it only responds to keys with a given prefix.
///
/// Useful for mounting sources that otherwise would not have the desired
/// prefix
pub struct WithPrefixSource<S: Source> {
    prefix: KeyBuf,
    source: S,
}

impl<S: Source> Source for WithPrefixSource<S> {
    type Error = S::Error;

    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error> {
        if !key.start_with(key) {
            return Ok(None);
        }

        self.source.get_value(&key.strip_prefix(&self.prefix))
    }
}