use std::ops::Bound;
use std::ops::RangeBounds;
use crate::argument::ArgumentBound;
use crate::argument::ArgumentError;
use crate::argument::ArgumentErrorKind;
use crate::argument::ArgumentResult;
use crate::argument::ComparisonConstraint;
use crate::argument::RangeConstraint;
use crate::argument::internal::NumericValue;
use crate::argument::internal::Sealed;
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,
},
))
}
}
}
#[inline]
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,
},
))
}
}
#[inline]
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),
}
}
#[inline]
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)
}
#[inline]
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()),
}
}
#[inline]
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(),
},
))
}
}
#[inline]
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
}