use std::ops::{
Bound,
RangeBounds,
};
use crate::argument::{
ArgumentBound,
ArgumentError,
ArgumentErrorKind,
ArgumentResult,
ArgumentValue,
ComparisonConstraint,
RangeConstraint,
sealed::Sealed,
};
trait NumericValue: Sealed + Copy + PartialOrd {
fn zero() -> Self;
fn to_argument_value(self) -> ArgumentValue;
fn is_nan(self) -> bool;
}
macro_rules! impl_numeric_value_for_integer {
($($numeric_type:ty),+ $(,)?) => {
$(
impl NumericValue for $numeric_type {
#[inline]
fn zero() -> Self {
0
}
#[inline]
fn to_argument_value(self) -> ArgumentValue {
ArgumentValue::from(self)
}
#[inline]
fn is_nan(self) -> bool {
false
}
}
)+
};
}
impl_numeric_value_for_integer!(i8, i16, i32, i64, i128, isize);
impl_numeric_value_for_integer!(u8, u16, u32, u64, u128, usize);
impl NumericValue for f32 {
#[inline]
fn zero() -> Self {
0.0
}
#[inline]
fn to_argument_value(self) -> ArgumentValue {
ArgumentValue::from(self)
}
#[inline]
fn is_nan(self) -> bool {
self.is_nan()
}
}
impl NumericValue for f64 {
#[inline]
fn zero() -> Self {
0.0
}
#[inline]
fn to_argument_value(self) -> ArgumentValue {
ArgumentValue::from(self)
}
#[inline]
fn is_nan(self) -> bool {
self.is_nan()
}
}
pub trait NumericArgument: Sealed + Sized {
fn require_zero(self, path: &str) -> ArgumentResult<Self>;
fn require_non_zero(self, path: &str) -> ArgumentResult<Self>;
fn require_positive(self, path: &str) -> ArgumentResult<Self>;
fn require_non_negative(self, path: &str) -> ArgumentResult<Self>;
fn require_negative(self, path: &str) -> ArgumentResult<Self>;
fn require_non_positive(self, path: &str) -> ArgumentResult<Self>;
fn require_less_than(self, path: &str, bound: Self)
-> ArgumentResult<Self>;
fn require_at_most(self, path: &str, bound: Self) -> ArgumentResult<Self>;
fn require_greater_than(
self,
path: &str,
bound: Self,
) -> ArgumentResult<Self>;
fn require_at_least(self, path: &str, bound: Self) -> ArgumentResult<Self>;
fn require_in_range<R>(self, path: &str, range: R) -> ArgumentResult<Self>
where
R: RangeBounds<Self>;
}
impl<T> NumericArgument for T
where
T: NumericValue,
{
#[inline]
fn require_zero(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::EqualTo(zero.to_argument_value()),
|actual, bound| actual == bound,
)
}
#[inline]
fn require_non_zero(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::NotEqualTo(zero.to_argument_value()),
|actual, bound| actual != bound,
)
}
#[inline]
fn require_positive(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::GreaterThan(zero.to_argument_value()),
|actual, bound| actual > bound,
)
}
#[inline]
fn require_non_negative(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::AtLeast(zero.to_argument_value()),
|actual, bound| actual >= bound,
)
}
#[inline]
fn require_negative(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::LessThan(zero.to_argument_value()),
|actual, bound| actual < bound,
)
}
#[inline]
fn require_non_positive(self, path: &str) -> ArgumentResult<Self> {
let zero = T::zero();
validate_comparison(
self,
path,
zero,
ComparisonConstraint::AtMost(zero.to_argument_value()),
|actual, bound| actual <= bound,
)
}
#[inline]
fn require_less_than(
self,
path: &str,
bound: Self,
) -> ArgumentResult<Self> {
validate_comparison(
self,
path,
bound,
ComparisonConstraint::LessThan(bound.to_argument_value()),
|actual, bound| actual < bound,
)
}
#[inline]
fn require_at_most(self, path: &str, bound: Self) -> ArgumentResult<Self> {
validate_comparison(
self,
path,
bound,
ComparisonConstraint::AtMost(bound.to_argument_value()),
|actual, bound| actual <= bound,
)
}
#[inline]
fn require_greater_than(
self,
path: &str,
bound: Self,
) -> ArgumentResult<Self> {
validate_comparison(
self,
path,
bound,
ComparisonConstraint::GreaterThan(bound.to_argument_value()),
|actual, bound| actual > bound,
)
}
#[inline]
fn require_at_least(self, path: &str, bound: Self) -> ArgumentResult<Self> {
validate_comparison(
self,
path,
bound,
ComparisonConstraint::AtLeast(bound.to_argument_value()),
|actual, bound| actual >= bound,
)
}
#[inline]
fn require_in_range<R>(self, path: &str, range: R) -> ArgumentResult<Self>
where
R: RangeBounds<Self>,
{
let (lower_bound, upper_bound) = snapshot_range_bounds(&range);
let constraint = capture_range_constraint(lower_bound, upper_bound);
validate_range_structure(path, lower_bound, upper_bound, &constraint)?;
validate_not_nan(path, self)?;
if range_contains(lower_bound, upper_bound, self) {
Ok(self)
} else {
Err(ArgumentError::new(
path,
ArgumentErrorKind::Range {
actual: self.to_argument_value(),
constraint,
},
))
}
}
}
fn validate_not_nan<T>(path: &str, value: T) -> ArgumentResult<()>
where
T: NumericValue,
{
if value.is_nan() {
Err(ArgumentError::new(path, ArgumentErrorKind::NotANumber))
} else {
Ok(())
}
}
fn validate_comparison<T, F>(
actual: T,
path: &str,
bound: T,
constraint: ComparisonConstraint,
predicate: F,
) -> ArgumentResult<T>
where
T: NumericValue,
F: FnOnce(T, T) -> bool,
{
validate_not_nan(path, actual)?;
validate_not_nan(path, bound)?;
if predicate(actual, bound) {
Ok(actual)
} else {
Err(ArgumentError::new(
path,
ArgumentErrorKind::Comparison {
actual: actual.to_argument_value(),
constraint,
},
))
}
}
fn copy_range_bound<T>(bound: Bound<&T>) -> Bound<T>
where
T: NumericValue,
{
match bound {
Bound::Unbounded => Bound::Unbounded,
Bound::Included(value) => Bound::Included(*value),
Bound::Excluded(value) => Bound::Excluded(*value),
}
}
fn snapshot_range_bounds<T, R>(range: &R) -> (Bound<T>, Bound<T>)
where
T: NumericValue,
R: RangeBounds<T>,
{
let lower_bound = copy_range_bound(range.start_bound());
let upper_bound = copy_range_bound(range.end_bound());
(lower_bound, upper_bound)
}
fn capture_argument_bound<T>(bound: Bound<T>) -> ArgumentBound
where
T: NumericValue,
{
match bound {
Bound::Unbounded => ArgumentBound::Unbounded,
Bound::Included(value) => {
ArgumentBound::Included(value.to_argument_value())
}
Bound::Excluded(value) => {
ArgumentBound::Excluded(value.to_argument_value())
}
}
}
fn capture_range_constraint<T>(
lower_bound: Bound<T>,
upper_bound: Bound<T>,
) -> RangeConstraint
where
T: NumericValue,
{
RangeConstraint::new(
capture_argument_bound(lower_bound),
capture_argument_bound(upper_bound),
)
}
fn validate_range_structure<T>(
path: &str,
lower_bound: Bound<T>,
upper_bound: Bound<T>,
constraint: &RangeConstraint,
) -> ArgumentResult<()>
where
T: NumericValue,
{
validate_range_bound_not_nan(path, lower_bound)?;
validate_range_bound_not_nan(path, upper_bound)?;
let is_valid = match (lower_bound, upper_bound) {
(Bound::Unbounded, _) | (_, Bound::Unbounded) => return Ok(()),
(Bound::Included(lower), Bound::Included(upper)) => lower <= upper,
(Bound::Included(lower), Bound::Excluded(upper))
| (Bound::Excluded(lower), Bound::Included(upper))
| (Bound::Excluded(lower), Bound::Excluded(upper)) => lower < upper,
};
if is_valid {
Ok(())
} else {
Err(ArgumentError::new(
path,
ArgumentErrorKind::InvalidRangeConstraint {
constraint: constraint.clone(),
},
))
}
}
fn validate_range_bound_not_nan<T>(
path: &str,
bound: Bound<T>,
) -> ArgumentResult<()>
where
T: NumericValue,
{
match bound {
Bound::Included(value) | Bound::Excluded(value) => {
validate_not_nan(path, value)
}
Bound::Unbounded => Ok(()),
}
}
fn range_contains<T>(
lower_bound: Bound<T>,
upper_bound: Bound<T>,
actual: T,
) -> bool
where
T: NumericValue,
{
let satisfies_lower = match lower_bound {
Bound::Unbounded => true,
Bound::Included(lower) => actual >= lower,
Bound::Excluded(lower) => actual > lower,
};
let satisfies_upper = match upper_bound {
Bound::Unbounded => true,
Bound::Included(upper) => actual <= upper,
Bound::Excluded(upper) => actual < upper,
};
satisfies_lower && satisfies_upper
}