twine_core/constraint/
non_positive.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)]
34pub struct NonPositive;
35
36impl NonPositive {
37 pub fn new<T: PartialOrd + Zero>(
43 value: T,
44 ) -> Result<Constrained<T, NonPositive>, ConstraintError> {
45 Constrained::<T, NonPositive>::new(value)
46 }
47
48 #[must_use]
52 pub fn zero<T: PartialOrd + Zero>() -> Constrained<T, NonPositive> {
53 Constrained::<T, NonPositive>::zero()
54 }
55}
56
57impl<T: PartialOrd + Zero> Constraint<T> for NonPositive {
58 fn check(value: &T) -> Result<(), ConstraintError> {
59 match value.partial_cmp(&T::zero()) {
60 Some(Ordering::Less | Ordering::Equal) => Ok(()),
61 Some(Ordering::Greater) => Err(ConstraintError::Positive),
62 None => Err(ConstraintError::NotANumber),
63 }
64 }
65}
66
67impl<T> Add for Constrained<T, NonPositive>
78where
79 T: Add<Output = T> + PartialOrd + Zero,
80{
81 type Output = Self;
82
83 fn add(self, rhs: Self) -> Self {
84 let value = self.value + rhs.value;
85 debug_assert!(
86 value <= T::zero(),
87 "Addition produced a positive value, violating NonPositive bound invariant"
88 );
89 Self {
90 value,
91 _marker: PhantomData,
92 }
93 }
94}
95
96impl<T> Zero for Constrained<T, NonPositive>
97where
98 T: PartialOrd + Zero,
99{
100 fn zero() -> Self {
101 Self {
102 value: T::zero(),
103 _marker: PhantomData,
104 }
105 }
106
107 fn is_zero(&self) -> bool {
108 self.value == T::zero()
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use uom::si::{f64::Power, power::watt};
116
117 #[test]
118 fn integers() {
119 let neg_one = Constrained::<i32, NonPositive>::new(-1).unwrap();
120 assert_eq!(neg_one.into_inner(), -1);
121
122 let neg_two = NonPositive::new(-2).unwrap();
123 assert_eq!(neg_two.as_ref(), &-2);
124
125 let zero = NonPositive::zero();
126 assert_eq!(zero.into_inner(), 0);
127
128 let sum = neg_one + neg_two + zero;
129 assert_eq!(sum.into_inner(), -3);
130
131 assert!(NonPositive::new(2).is_err());
132 }
133
134 #[test]
135 fn floats() {
136 assert!(Constrained::<f64, NonPositive>::new(-2.0).is_ok());
137 assert!(NonPositive::new(0.0).is_ok());
138 assert!(NonPositive::new(2.0).is_err());
139 assert!(NonPositive::new(f64::NAN).is_err());
140 }
141
142 #[test]
143 fn powers() {
144 let neg_mass_rate = Power::new::<watt>(-5.0);
145 assert!(NonPositive::new(neg_mass_rate).is_ok());
146
147 let zero_mass_rate = Power::new::<watt>(0.0);
148 assert!(NonPositive::new(zero_mass_rate).is_ok());
149
150 let pos_mass_rate = Power::new::<watt>(2.0);
151 assert!(NonPositive::new(pos_mass_rate).is_err());
152 }
153}