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)]
66pub struct Weight(f32);
67
68impl Weight {
69 pub fn new(raw: f32) -> Result<Self, MeasureError> {
78 if !raw.is_finite() {
79 return Err(MeasureError::NonFiniteValue { value: raw });
80 }
81 if raw < 0.0 {
82 return Err(MeasureError::NegativeValue { value: raw });
83 }
84 Ok(Self(raw))
85 }
86
87 pub fn get(self) -> f32 {
89 self.0
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn area_km2_accepts_zero() {
99 let a = AreaKm2::new(0.0).unwrap();
100 assert_eq!(a.get(), 0.0);
101 }
102
103 #[test]
104 fn area_km2_accepts_positive() {
105 let a = AreaKm2::new(123.45).unwrap();
106 assert_eq!(a.get(), 123.45);
107 }
108
109 #[test]
110 fn area_km2_rejects_negative() {
111 assert!(matches!(
112 AreaKm2::new(-1.0),
113 Err(MeasureError::NegativeValue { value: _ })
114 ));
115 }
116
117 #[test]
118 fn area_km2_rejects_nan() {
119 assert!(matches!(
120 AreaKm2::new(f32::NAN),
121 Err(MeasureError::NonFiniteValue { value: _ })
122 ));
123 }
124
125 #[test]
126 fn area_km2_rejects_inf() {
127 assert!(matches!(
128 AreaKm2::new(f32::INFINITY),
129 Err(MeasureError::NonFiniteValue { value: _ })
130 ));
131 }
132
133 #[test]
134 fn area_km2_rejects_neg_inf() {
135 assert!(matches!(
136 AreaKm2::new(f32::NEG_INFINITY),
137 Err(MeasureError::NonFiniteValue { value: _ })
138 ));
139 }
140
141 #[test]
142 fn weight_accepts_zero() {
143 let w = Weight::new(0.0).unwrap();
144 assert_eq!(w.get(), 0.0);
145 }
146
147 #[test]
148 fn weight_accepts_positive() {
149 let w = Weight::new(0.75).unwrap();
150 assert_eq!(w.get(), 0.75);
151 }
152
153 #[test]
154 fn weight_rejects_negative() {
155 assert!(matches!(
156 Weight::new(-0.1),
157 Err(MeasureError::NegativeValue { value: _ })
158 ));
159 }
160
161 #[test]
162 fn weight_rejects_nan() {
163 assert!(matches!(
164 Weight::new(f32::NAN),
165 Err(MeasureError::NonFiniteValue { value: _ })
166 ));
167 }
168
169 #[test]
170 fn weight_rejects_inf() {
171 assert!(matches!(
172 Weight::new(f32::INFINITY),
173 Err(MeasureError::NonFiniteValue { value: _ })
174 ));
175 }
176
177 #[test]
178 fn area_km2_min_positive_succeeds() {
179 let a = AreaKm2::new(f32::MIN_POSITIVE).unwrap();
180 assert_eq!(a.get(), f32::MIN_POSITIVE);
181 }
182
183 #[test]
184 fn weight_neg_infinity_fails_with_non_finite_not_negative() {
185 assert!(matches!(
188 Weight::new(f32::NEG_INFINITY),
189 Err(MeasureError::NonFiniteValue { value: _ })
190 ));
191 }
192}