twine_core/constraint/
strictly_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)]
35pub struct StrictlyPositive;
36
37impl StrictlyPositive {
38 pub fn new<T: PartialOrd + Zero>(
44 value: T,
45 ) -> Result<Constrained<T, StrictlyPositive>, ConstraintError> {
46 Constrained::<T, StrictlyPositive>::new(value)
47 }
48}
49
50impl<T: PartialOrd + Zero> Constraint<T> for StrictlyPositive {
51 fn check(value: &T) -> Result<(), ConstraintError> {
52 match value.partial_cmp(&T::zero()) {
53 Some(Ordering::Greater) => Ok(()),
54 Some(Ordering::Equal) => Err(ConstraintError::Zero),
55 Some(Ordering::Less) => Err(ConstraintError::Negative),
56 None => Err(ConstraintError::NotANumber),
57 }
58 }
59}
60
61impl<T> Add for Constrained<T, StrictlyPositive>
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-positive value, violating StrictlyPositive bound invariant"
82 );
83 Self {
84 value,
85 _marker: PhantomData,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use uom::si::{f64::MassRate, mass_rate::kilogram_per_second};
93
94 use super::*;
95
96 #[test]
97 fn integers() {
98 let x = Constrained::<i32, StrictlyPositive>::new(1).unwrap();
99 assert_eq!(x.into_inner(), 1);
100
101 let y = StrictlyPositive::new(42).unwrap();
102 assert_eq!(y.as_ref(), &42);
103
104 assert!(StrictlyPositive::new(0).is_err());
105 assert!(StrictlyPositive::new(-2).is_err());
106 }
107
108 #[test]
109 fn floats() {
110 assert!(Constrained::<f64, StrictlyPositive>::new(1.0).is_ok());
111 assert!(StrictlyPositive::new(0.1).is_ok());
112 assert!(StrictlyPositive::new(0.0).is_err());
113 assert!(StrictlyPositive::new(-5.0).is_err());
114 assert!(StrictlyPositive::new(f64::NAN).is_err());
115 }
116
117 #[test]
118 fn mass_rates() {
119 let mass_rate = MassRate::new::<kilogram_per_second>(5.0);
120 assert!(StrictlyPositive::new(mass_rate).is_ok());
121
122 let mass_rate = MassRate::new::<kilogram_per_second>(0.0);
123 assert!(StrictlyPositive::new(mass_rate).is_err());
124
125 let mass_rate = MassRate::new::<kilogram_per_second>(-2.0);
126 assert!(StrictlyPositive::new(mass_rate).is_err());
127 }
128}