twine_core/constraint/
strictly_negative.rs1use std::{cmp::Ordering, marker::PhantomData, ops::Add};
2
3use num_traits::Zero;
4
5use super::{Constrained, Constraint, ConstraintError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct StrictlyNegative;
36
37impl StrictlyNegative {
38 pub fn new<T: PartialOrd + Zero>(
44 value: T,
45 ) -> Result<Constrained<T, StrictlyNegative>, ConstraintError> {
46 Constrained::<T, StrictlyNegative>::new(value)
47 }
48}
49
50impl<T: PartialOrd + Zero> Constraint<T> for StrictlyNegative {
51 fn check(value: &T) -> Result<(), ConstraintError> {
52 match value.partial_cmp(&T::zero()) {
53 Some(Ordering::Less) => Ok(()),
54 Some(Ordering::Equal) => Err(ConstraintError::Zero),
55 Some(Ordering::Greater) => Err(ConstraintError::Negative),
56 None => Err(ConstraintError::NotANumber),
57 }
58 }
59}
60
61impl<T> Add for Constrained<T, StrictlyNegative>
72where
73 T: Add<Output = T> + PartialOrd + Zero,
74{
75 type Output = Self;
76
77 fn add(self, rhs: Self) -> Self {
78 let value = self.value + rhs.value;
79 debug_assert!(
80 value < T::zero(),
81 "Addition produced a non-negative value, violating StrictlyNegative bound invariant"
82 );
83 Self {
84 value,
85 _marker: PhantomData,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn integers() {
96 let x = Constrained::<i32, StrictlyNegative>::new(-1).unwrap();
97 assert_eq!(x.into_inner(), -1);
98
99 let y = StrictlyNegative::new(-42).unwrap();
100 assert_eq!(y.as_ref(), &-42);
101
102 assert!(StrictlyNegative::new(0).is_err());
103 assert!(StrictlyNegative::new(2).is_err());
104 }
105
106 #[test]
107 fn floats() {
108 assert!(Constrained::<f64, StrictlyNegative>::new(-1.0).is_ok());
109 assert!(StrictlyNegative::new(-0.1).is_ok());
110 assert!(StrictlyNegative::new(0.0).is_err());
111 assert!(StrictlyNegative::new(5.0).is_err());
112 assert!(StrictlyNegative::new(f64::NAN).is_err());
113 }
114}