preftool 0.2.0

Configuration library for CLI tools/servers.
Documentation
use crate::*;
use std::fmt;

struct Errors<'a>(&'a [Error]);

impl<'a> fmt::Display for Errors<'a> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    for error in self.0 {
      f.write_str("\n\nError: ")?;
      fmt::Display::fmt(error, f)?;
    }

    Ok(())
  }
}

impl<'a> fmt::Debug for Errors<'a> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    f.debug_list().entries(self.0).finish()
  }
}

error_chain! {
  errors {
    ValidationFailed(field: String, msg: String) {
      description("validation failed")
      display("validation failed for field {} with message: {}", field, msg)
    }

    MultipleErrors(errors: Vec<Error>) {
      description("validation failed")
      display("multiple validators failed: {}", Errors(errors))
    }
  }
}

impl Error {
  pub fn validation_failed<S1: Into<String>, S2: Into<String>>(field: S1, msg: S2) -> Self {
    Self::from(ErrorKind::ValidationFailed(field.into(), msg.into()))
  }

  #[doc(hidden)]
  pub fn append_to(self, errors: &mut Vec<Error>) {
    match self.0 {
      ErrorKind::MultipleErrors(e) => errors.extend(e),
      _ => errors.push(self),
    }
  }
}

impl From<Vec<Error>> for Error {
  fn from(mut inner: Vec<Error>) -> Self {
    if inner.len() == 0 {
      panic!("From<Vec<Error>> should not be called on empty vec's");
    } else if inner.len() == 1 {
      inner.remove(0)
    } else {
      ErrorKind::MultipleErrors(inner).into()
    }
  }
}

/// Helper trait for dealing with validation results.
/// Custon `validate` functions (used by the derive macro)
/// requires that the return value implements this trait.
pub trait ValidationResult {
  fn into_result(self) -> Result<()>;
}

impl ValidationResult for Result<()> {
  fn into_result(self) -> Result<()> {
    self
  }
}

impl ValidationResult for () {
  fn into_result(self) -> Result<()> {
    Ok(())
  }
}

impl<E: Into<Error>> ValidationResult for E {
  fn into_result(self) -> Result<()> {
    Err(self.into())
  }
}

/// A type which can be bound by configuration.
pub trait Options {
  /// Bind values from configuration - normally not called directly.
  fn bind_config<C: Configuration>(&mut self, config: &C) -> Result<()>;

  /// Validate current instance after binding.
  fn validate(&self) -> Result<()>;

  /// Bind and validate configuration.
  fn bind<C: Configuration>(&mut self, config: &C) -> Result<()> {
    self.bind_config(config)?;
    self.validate()
  }
}

/// Trait for validating options
pub trait ValidateOptions {
  /// Validate current instance after binding.
  fn validate(&self) -> Result<()> {
    Ok(())
  }
}

/// Trait to produce a value from config.
pub trait FromConfig: Sized {
  /// Get an instance of self from a configuration object.
  fn from_config<C: Configuration>(config: &C) -> Result<Self>;
}

/// Trait for types used as proxy for configuration to get around rust's
/// orphaning rules.
pub trait ConfigProxy<T> {
  /// Bind value based on mutable reference to original value.
  fn bind_proxy<C: Configuration>(value: &mut T, config: &C) -> Result<()>;
}

impl<T> FromConfig for T
where
  T: Options + Default + Sized,
{
  fn from_config<C: Configuration>(config: &C) -> Result<Self> {
    let mut ret = Self::default();
    ret.bind(config)?;
    Ok(ret)
  }
}

// TODO: This can be made better (no need for default)
impl<P, T> ConfigProxy<T> for P
where
  P: Options + Into<T>,
  T: Clone + Into<P>,
{
  fn bind_proxy<C: Configuration>(value: &mut T, config: &C) -> Result<()> {
    let mut proxy = value.clone().into();
    Options::bind(&mut proxy, config)?;
    std::mem::replace(value, proxy.into());
    Ok(())
  }
}

impl Options for String {
  fn bind_config<C: Configuration>(&mut self, config: &C) -> Result<()> {
    if let Some(val) = config.value() {
      *self = val.to_owned();
    }

    Ok(())
  }

  fn validate(&self) -> Result<()> {
    Ok(())
  }
}