1#[derive(Debug, thiserror::Error)]
5pub enum MeasureError {
6 #[error("value must be non-negative, got {value}")]
8 NegativeValue {
9 value: f32,
11 },
12
13 #[error("value must be finite, got {value}")]
15 NonFiniteValue {
16 value: f32,
18 },
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
30pub struct AreaKm2(f32);
31
32impl AreaKm2 {
33 pub fn new(raw: f32) -> Result<Self, MeasureError> {
42 if !raw.is_finite() {
43 return Err(MeasureError::NonFiniteValue { value: raw });
44 }
45 if raw < 0.0 {
46 return Err(MeasureError::NegativeValue { value: raw });
47 }
48 Ok(Self(raw))
49 }
50
51 pub fn get(self) -> f32 {
53 self.0
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
65pub struct Weight(f32);
66
67impl Weight {
68 pub fn new(raw: f32) -> Result<Self, MeasureError> {
77 if !raw.is_finite() {
78 return Err(MeasureError::NonFiniteValue { value: raw });
79 }
80 if raw < 0.0 {
81 return Err(MeasureError::NegativeValue { value: raw });
82 }
83 Ok(Self(raw))
84 }
85
86 pub fn get(self) -> f32 {
88 self.0
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn area_km2_accepts_zero() {
98 let a = AreaKm2::new(0.0).unwrap();
99 assert_eq!(a.get(), 0.0);
100 }
101
102 #[test]
103 fn area_km2_accepts_positive() {
104 let a = AreaKm2::new(123.45).unwrap();
105 assert_eq!(a.get(), 123.45);
106 }
107
108 #[test]
109 fn area_km2_rejects_negative() {
110 assert!(matches!(
111 AreaKm2::new(-1.0),
112 Err(MeasureError::NegativeValue { value: _ })
113 ));
114 }
115
116 #[test]
117 fn area_km2_rejects_nan() {
118 assert!(matches!(
119 AreaKm2::new(f32::NAN),
120 Err(MeasureError::NonFiniteValue { value: _ })
121 ));
122 }
123
124 #[test]
125 fn area_km2_rejects_inf() {
126 assert!(matches!(
127 AreaKm2::new(f32::INFINITY),
128 Err(MeasureError::NonFiniteValue { value: _ })
129 ));
130 }
131
132 #[test]
133 fn area_km2_rejects_neg_inf() {
134 assert!(matches!(
135 AreaKm2::new(f32::NEG_INFINITY),
136 Err(MeasureError::NonFiniteValue { value: _ })
137 ));
138 }
139
140 #[test]
141 fn weight_accepts_zero() {
142 let w = Weight::new(0.0).unwrap();
143 assert_eq!(w.get(), 0.0);
144 }
145
146 #[test]
147 fn weight_accepts_positive() {
148 let w = Weight::new(0.75).unwrap();
149 assert_eq!(w.get(), 0.75);
150 }
151
152 #[test]
153 fn weight_rejects_negative() {
154 assert!(matches!(
155 Weight::new(-0.1),
156 Err(MeasureError::NegativeValue { value: _ })
157 ));
158 }
159
160 #[test]
161 fn weight_rejects_nan() {
162 assert!(matches!(
163 Weight::new(f32::NAN),
164 Err(MeasureError::NonFiniteValue { value: _ })
165 ));
166 }
167
168 #[test]
169 fn weight_rejects_inf() {
170 assert!(matches!(
171 Weight::new(f32::INFINITY),
172 Err(MeasureError::NonFiniteValue { value: _ })
173 ));
174 }
175
176 #[test]
177 fn area_km2_min_positive_succeeds() {
178 let a = AreaKm2::new(f32::MIN_POSITIVE).unwrap();
179 assert_eq!(a.get(), f32::MIN_POSITIVE);
180 }
181
182 #[test]
183 fn weight_neg_infinity_fails_with_non_finite_not_negative() {
184 assert!(matches!(
187 Weight::new(f32::NEG_INFINITY),
188 Err(MeasureError::NonFiniteValue { value: _ })
189 ));
190 }
191}