garde/rules/
range.rs

1//! Range validation.
2//!
3//! ```rust
4//! #[derive(garde::Validate)]
5//! struct Test {
6//!     #[garde(range(min=10,max=100))]
7//!     v: u64,
8//! }
9//! ```
10//!
11//! The entrypoint is the [`Bounds`] trait. Implementing this trait for a type allows that type to be used with the `#[garde(range(...))]` rule.
12//!
13//! This trait is implemented for all primitive integer types.
14
15use std::fmt::Display;
16
17use crate::error::Error;
18
19#[inline]
20pub fn apply<T: Bounds>(
21    v: &T,
22    (min, max): (Option<T::Size>, Option<T::Size>),
23) -> Result<(), Error> {
24    let min = min.unwrap_or(T::MIN);
25    let max = max.unwrap_or(T::MAX);
26    if let Err(e) = v.validate_bounds(min, max) {
27        match e {
28            OutOfBounds::Lower => return Err(Error::new(format!("lower than {min}"))),
29            OutOfBounds::Upper => return Err(Error::new(format!("greater than {max}"))),
30        }
31    }
32    Ok(())
33}
34
35pub trait Bounds: PartialOrd {
36    type Size: Copy + Sized + Display;
37
38    const MIN: Self::Size;
39    const MAX: Self::Size;
40
41    fn validate_bounds(
42        &self,
43        lower_bound: Self::Size,
44        upper_bound: Self::Size,
45    ) -> Result<(), OutOfBounds>;
46}
47
48pub enum OutOfBounds {
49    Lower,
50    Upper,
51}
52
53macro_rules! impl_for {
54    ($($T:ty),*) => {
55        $(
56            impl Bounds for $T {
57                type Size = $T;
58
59                const MIN: Self::Size = <$T>::MIN;
60                const MAX: Self::Size = <$T>::MAX;
61
62                fn validate_bounds(
63                    &self,
64                    lower_bound: Self::Size,
65                    upper_bound: Self::Size,
66                ) -> Result<(), OutOfBounds> {
67                    if self < &lower_bound {
68                        Err(OutOfBounds::Lower)
69                    } else if self > &upper_bound {
70                        Err(OutOfBounds::Upper)
71                    } else {
72                        Ok(())
73                    }
74                }
75            }
76        )*
77    };
78}
79
80impl_for!(u8, u16, u32, u64, usize, u128, i8, i16, i32, i64, isize, i128, f32, f64);
81
82#[cfg(feature = "rust_decimal")]
83impl_for!(rust_decimal::Decimal);
84
85impl<T: Bounds> Bounds for Option<T> {
86    type Size = T::Size;
87
88    const MIN: Self::Size = T::MIN;
89    const MAX: Self::Size = T::MAX;
90
91    fn validate_bounds(
92        &self,
93        lower_bound: Self::Size,
94        upper_bound: Self::Size,
95    ) -> Result<(), OutOfBounds> {
96        match self {
97            Some(value) => value.validate_bounds(lower_bound, upper_bound),
98            None => Ok(()),
99        }
100    }
101}