Skip to main content

type_lib/rules/
numeric.rs

1//! Numeric rules: sign checks and inclusive integer ranges.
2//!
3//! Sign rules ([`Positive`], [`NonNegative`], [`Negative`], [`NonPositive`])
4//! apply to the signed integer and floating-point primitives. [`InRange`] applies
5//! to the integer primitives that fit losslessly in an `i64`
6//! (`i8`/`i16`/`i32`/`i64`, `u8`/`u16`/`u32`).
7
8use crate::{ValidationError, Validator};
9
10/// Accepts strictly positive numbers (`value > 0`).
11///
12/// # Examples
13///
14/// ```rust
15/// use type_lib::rules::Positive;
16/// use type_lib::Validator;
17///
18/// assert!(Positive::validate(&3).is_ok());
19/// assert!(Positive::validate(&0).is_err());
20/// assert!(Positive::validate(&-1.5_f64).is_err());
21/// ```
22pub struct Positive;
23
24/// Accepts non-negative numbers (`value >= 0`).
25///
26/// # Examples
27///
28/// ```rust
29/// use type_lib::rules::NonNegative;
30/// use type_lib::Validator;
31///
32/// assert!(NonNegative::validate(&0).is_ok());
33/// assert!(NonNegative::validate(&-1).is_err());
34/// ```
35pub struct NonNegative;
36
37/// Accepts strictly negative numbers (`value < 0`).
38///
39/// # Examples
40///
41/// ```rust
42/// use type_lib::rules::Negative;
43/// use type_lib::Validator;
44///
45/// assert!(Negative::validate(&-2).is_ok());
46/// assert!(Negative::validate(&0).is_err());
47/// ```
48pub struct Negative;
49
50/// Accepts non-positive numbers (`value <= 0`).
51///
52/// # Examples
53///
54/// ```rust
55/// use type_lib::rules::NonPositive;
56/// use type_lib::Validator;
57///
58/// assert!(NonPositive::validate(&0).is_ok());
59/// assert!(NonPositive::validate(&1).is_err());
60/// ```
61pub struct NonPositive;
62
63macro_rules! impl_sign_rules {
64    ($($t:ty),* $(,)?) => {
65        $(
66            impl Validator<$t> for Positive {
67                type Error = ValidationError;
68
69                fn validate(value: &$t) -> Result<(), Self::Error> {
70                    if *value > (0 as $t) {
71                        Ok(())
72                    } else {
73                        Err(ValidationError::new("positive", "value must be greater than zero"))
74                    }
75                }
76            }
77
78            impl Validator<$t> for NonNegative {
79                type Error = ValidationError;
80
81                fn validate(value: &$t) -> Result<(), Self::Error> {
82                    if *value >= (0 as $t) {
83                        Ok(())
84                    } else {
85                        Err(ValidationError::new("non_negative", "value must not be negative"))
86                    }
87                }
88            }
89
90            impl Validator<$t> for Negative {
91                type Error = ValidationError;
92
93                fn validate(value: &$t) -> Result<(), Self::Error> {
94                    if *value < (0 as $t) {
95                        Ok(())
96                    } else {
97                        Err(ValidationError::new("negative", "value must be less than zero"))
98                    }
99                }
100            }
101
102            impl Validator<$t> for NonPositive {
103                type Error = ValidationError;
104
105                fn validate(value: &$t) -> Result<(), Self::Error> {
106                    if *value <= (0 as $t) {
107                        Ok(())
108                    } else {
109                        Err(ValidationError::new("non_positive", "value must not be positive"))
110                    }
111                }
112            }
113        )*
114    };
115}
116
117impl_sign_rules!(i8, i16, i32, i64, i128, isize, f32, f64);
118
119/// Accepts integers within the inclusive range `MIN..=MAX`.
120///
121/// Implemented for the integer types that convert losslessly to `i64`
122/// (`i8`/`i16`/`i32`/`i64`, `u8`/`u16`/`u32`). The bounds are `i64` const
123/// generics, so the rule covers any range expressible in an `i64`.
124///
125/// # Examples
126///
127/// ```rust
128/// use type_lib::rules::InRange;
129/// use type_lib::Validator;
130///
131/// // A percentage: 0 to 100 inclusive.
132/// assert!(InRange::<0, 100>::validate(&50_u8).is_ok());
133/// assert!(InRange::<0, 100>::validate(&150_i32).is_err());
134///
135/// // Ranges may be negative.
136/// assert!(InRange::<-10, 10>::validate(&-5_i16).is_ok());
137/// ```
138pub struct InRange<const MIN: i64, const MAX: i64>;
139
140macro_rules! impl_in_range {
141    ($($t:ty),* $(,)?) => {
142        $(
143            impl<const MIN: i64, const MAX: i64> Validator<$t> for InRange<MIN, MAX> {
144                type Error = ValidationError;
145
146                fn validate(value: &$t) -> Result<(), Self::Error> {
147                    let value = i64::from(*value);
148                    if value >= MIN && value <= MAX {
149                        Ok(())
150                    } else {
151                        Err(ValidationError::new("in_range", "value is out of range"))
152                    }
153                }
154            }
155        )*
156    };
157}
158
159impl_in_range!(i8, i16, i32, i64, u8, u16, u32);
160
161#[cfg(test)]
162mod tests {
163    #![allow(clippy::unwrap_used, clippy::expect_used)]
164
165    use super::*;
166
167    #[test]
168    fn sign_rules_on_integers() {
169        assert!(Positive::validate(&1_i32).is_ok());
170        assert!(Positive::validate(&0_i32).is_err());
171        assert!(NonNegative::validate(&0_i32).is_ok());
172        assert!(NonNegative::validate(&-1_i32).is_err());
173        assert!(Negative::validate(&-1_i32).is_ok());
174        assert!(Negative::validate(&0_i32).is_err());
175        assert!(NonPositive::validate(&0_i32).is_ok());
176        assert!(NonPositive::validate(&1_i32).is_err());
177    }
178
179    #[test]
180    fn sign_rules_on_floats() {
181        assert!(Positive::validate(&0.5_f64).is_ok());
182        assert!(Positive::validate(&0.0_f64).is_err());
183        assert!(Negative::validate(&-0.5_f32).is_ok());
184    }
185
186    #[test]
187    fn in_range_inclusive_bounds_and_codes() {
188        assert!(InRange::<0, 100>::validate(&0_u8).is_ok());
189        assert!(InRange::<0, 100>::validate(&100_u8).is_ok());
190        assert_eq!(
191            InRange::<0, 100>::validate(&101_i32).unwrap_err().code(),
192            "in_range",
193        );
194        assert!(InRange::<-5, 5>::validate(&-5_i16).is_ok());
195        assert!(InRange::<-5, 5>::validate(&-6_i16).is_err());
196    }
197}