use std::fmt;
use std::error::Error;
use std::str::FromStr;
use std::convert::TryInto;
use std::ops::RangeBounds;
use crate::error::ConfigError;
use crate::item::{StringItem, TypedItem};
#[derive(Debug)]
pub enum ValidatorError {
Empty,
BelowMinimum(String),
AboveMaximum(String),
NotInRange(Option<String>, Option<String>)
}
impl fmt::Display for ValidatorError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Empty => write!(f, "must not be empty."),
Self::BelowMinimum(min) => write!(f, "must be >= {}.", min),
Self::AboveMaximum(max) => write!(f, "must be <= {}.", max),
Self::NotInRange(start, end) => {
write!(f, "must be ")?;
if let Some(start) = start {
write!(f, "{}.", start)?;
}
if start.is_some() && end.is_some() {
write!(f, " and ")?;
}
if let Some(end) = end {
write!(f, "{}.", end)?;
}
Ok(())
}
}
}
}
impl Error for ValidatorError {
}
impl ValidatorError {
fn from_range<T: fmt::Display, R: RangeBounds<T>>(range: &R) -> Self {
let start = match range.start_bound() {
std::ops::Bound::Included(v) => { Some(format!(">= {}", v)) },
std::ops::Bound::Excluded(v) => { Some(format!("> {}", v)) },
std::ops::Bound::Unbounded => { None}
};
let end = match range.end_bound() {
std::ops::Bound::Included(v) => { Some(format!("<= {}", v)) },
std::ops::Bound::Excluded(v) => { Some(format!("< {}", v)) },
std::ops::Bound::Unbounded => { None }
};
Self::NotInRange(start, end)
}
}
pub trait Range<T: FromStr + PartialOrd + fmt::Display> {
fn min(self, minimum: T) -> Result<TypedItem<T>, ConfigError>;
fn max(self, maximum: T) -> Result<TypedItem<T>, ConfigError>;
fn in_range<R: RangeBounds<T>>(self, range: R) -> Result<TypedItem<T>, ConfigError>;
}
impl <T: FromStr + PartialOrd + fmt::Display> Range<T> for Result<TypedItem<T>, ConfigError> {
fn min(self, minimum: T) -> Result<TypedItem<T>, ConfigError> {
self?.filter(|v| if *v < minimum {
Err(Box::new(ValidatorError::BelowMinimum(format!("{}", minimum))))
} else {
Ok(())
}
)
}
fn max(self, maximum: T) -> Result<TypedItem<T>, ConfigError> {
self?.filter(|v| if *v > maximum {
Err(Box::new(ValidatorError::AboveMaximum(format!("{}", maximum))))
} else {
Ok(())
}
)
}
fn in_range<R: RangeBounds<T>>(self, range: R) -> Result<TypedItem<T>, ConfigError> {
self?.filter(|v| if range.contains(v) {
Ok(())
} else {
Err(Box::new(ValidatorError::from_range(&range)))
}
)
}
}
impl <T: FromStr + PartialOrd + fmt::Display> Range<T> for Result<StringItem, ConfigError> where T::Err: Error + 'static {
fn min(self, minimum: T) -> Result<TypedItem<T>, ConfigError> {
(self.try_into() as Result<TypedItem<T>, ConfigError>).min(minimum)
}
fn max(self, maximum: T) -> Result<TypedItem<T>, ConfigError> {
(self.try_into() as Result<TypedItem<T>, ConfigError>).max(maximum)
}
fn in_range<R: RangeBounds<T>>(self, range: R) -> Result<TypedItem<T>, ConfigError> {
(self.try_into() as Result<TypedItem<T>, ConfigError>).in_range(range)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Config;
use crate::confpath::ConfPath;
use crate::item::ValueExtractor;
use crate::sources::defaults::Defaults;
#[test]
fn range_good() {
let mut c = Config::default();
let mut d = Defaults::default();
d.set(c.root().push_all(&["ten"]), "10", "10");
d.set(c.root().push_all(&["five"]), "5", "5");
d.set(c.root().push_all(&["zero"]), "0", "0");
d.set(c.root().push_all(&["neg_one"]), "-1", "-1");
c.add_source(d);
assert_eq!(c.get(ConfPath::from(&["ten"])).min(5).value().unwrap(), 10u32);
assert_eq!(c.get(ConfPath::from(&["five"])).min(5).value().unwrap(), 5u32);
assert_eq!(c.get(ConfPath::from(&["zero"])).min(0).value().unwrap(), 0u32);
assert_eq!(c.get(ConfPath::from(&["neg_one"])).min(-1).value().unwrap(), -1i32);
assert_eq!(c.get(ConfPath::from(&["ten"])).max(10).value().unwrap(), 10u32);
assert_eq!(c.get(ConfPath::from(&["five"])).max(10).value().unwrap(), 5u32);
assert_eq!(c.get(ConfPath::from(&["zero"])).max(0).value().unwrap(), 0u32);
assert_eq!(c.get(ConfPath::from(&["neg_one"])).max(0).value().unwrap(), -1i32);
assert_eq!(c.get(ConfPath::from(&["ten"])).in_range(0..=10).value().unwrap(), 10u32);
assert_eq!(c.get(ConfPath::from(&["ten"])).in_range(10..11).value().unwrap(), 10u32);
assert_eq!(c.get(ConfPath::from(&["five"])).in_range(0..=10).value().unwrap(), 5u32);
assert_eq!(c.get(ConfPath::from(&["zero"])).in_range(-10..=10).value().unwrap(), 0i32);
assert_eq!(c.get(ConfPath::from(&["neg_one"])).in_range(-1..=0).value().unwrap(), -1i32);
}
#[test]
#[should_panic(expected = "BelowMinimum")]
fn range_min_bad() {
let mut c = Config::default();
let mut d = Defaults::default();
d.set(c.root().push_all(&["ten"]), "10", "10");
c.add_source(d);
let _: u32 = c.get(ConfPath::from(&["ten"])).min(20).value().unwrap();
}
#[test]
#[should_panic(expected = "AboveMaximum")]
fn range_max_bad() {
let mut c = Config::default();
let mut d = Defaults::default();
d.set(c.root().push_all(&["ten"]), "10", "10");
c.add_source(d);
let _: u32 = c.get(ConfPath::from(&["ten"])).max(5).value().unwrap();
}
#[test]
#[should_panic(expected = "NotInRange")]
fn range_between_bad_lower() {
let mut c = Config::default();
let mut d = Defaults::default();
d.set(c.root().push_all(&["ten"]), "10", "10");
c.add_source(d);
let _: u32 = c.get(ConfPath::from(&["ten"])).in_range(20..30).value().unwrap();
}
#[test]
#[should_panic(expected = "NotInRange")]
fn range_between_bad_upper() {
let mut c = Config::default();
let mut d = Defaults::default();
d.set(c.root().push_all(&["ten"]), "10", "10");
c.add_source(d);
let _: u32 = c.get(ConfPath::from(&["ten"])).in_range(0..5).value().unwrap();
}
}