use_atmospheric_pressure/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn validate_pressure(value: f64) -> Result<f64, PressureValueError> {
8 if !value.is_finite() {
9 return Err(PressureValueError::NonFinitePressure(value));
10 }
11
12 if value < 0.0 {
13 return Err(PressureValueError::NegativePressure(value));
14 }
15
16 Ok(value)
17}
18
19#[derive(Clone, Copy, Debug, PartialEq)]
21pub enum PressureValueError {
22 NonFinitePressure(f64),
24 NegativePressure(f64),
26}
27
28impl fmt::Display for PressureValueError {
29 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::NonFinitePressure(value) => {
32 write!(formatter, "pressure must be finite, got {value}")
33 },
34 Self::NegativePressure(value) => {
35 write!(formatter, "pressure cannot be negative, got {value}")
36 },
37 }
38 }
39}
40
41impl Error for PressureValueError {}
42
43#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
45pub enum PressureUnitLabel {
46 Hectopascal,
48 Millibar,
50 Unknown,
52 Custom(String),
54}
55
56impl fmt::Display for PressureUnitLabel {
57 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 Self::Hectopascal => formatter.write_str("hPa"),
60 Self::Millibar => formatter.write_str("mbar"),
61 Self::Unknown => formatter.write_str("unknown"),
62 Self::Custom(value) => formatter.write_str(value),
63 }
64 }
65}
66
67impl FromStr for PressureUnitLabel {
68 type Err = PressureUnitLabelParseError;
69
70 fn from_str(value: &str) -> Result<Self, Self::Err> {
71 let trimmed = value.trim();
72
73 if trimmed.is_empty() {
74 return Err(PressureUnitLabelParseError::Empty);
75 }
76
77 match trimmed.to_ascii_lowercase().as_str() {
78 "hpa" | "hectopascal" | "hectopascals" => Ok(Self::Hectopascal),
79 "mbar" | "millibar" | "millibars" => Ok(Self::Millibar),
80 "unknown" => Ok(Self::Unknown),
81 _ => Ok(Self::Custom(trimmed.to_string())),
82 }
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub enum PressureUnitLabelParseError {
89 Empty,
91}
92
93impl fmt::Display for PressureUnitLabelParseError {
94 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Self::Empty => formatter.write_str("pressure unit label cannot be empty"),
97 }
98 }
99}
100
101impl Error for PressureUnitLabelParseError {}
102
103#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub enum PressureTendency {
106 Rising,
108 Falling,
110 Steady,
112 Unknown,
114 Custom(String),
116}
117
118impl fmt::Display for PressureTendency {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 Self::Rising => formatter.write_str("rising"),
122 Self::Falling => formatter.write_str("falling"),
123 Self::Steady => formatter.write_str("steady"),
124 Self::Unknown => formatter.write_str("unknown"),
125 Self::Custom(value) => formatter.write_str(value),
126 }
127 }
128}
129
130impl FromStr for PressureTendency {
131 type Err = PressureTendencyParseError;
132
133 fn from_str(value: &str) -> Result<Self, Self::Err> {
134 let trimmed = value.trim();
135
136 if trimmed.is_empty() {
137 return Err(PressureTendencyParseError::Empty);
138 }
139
140 match trimmed
141 .to_ascii_lowercase()
142 .replace(['_', ' '], "-")
143 .as_str()
144 {
145 "rising" => Ok(Self::Rising),
146 "falling" => Ok(Self::Falling),
147 "steady" => Ok(Self::Steady),
148 "unknown" => Ok(Self::Unknown),
149 _ => Ok(Self::Custom(trimmed.to_string())),
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, Eq, PartialEq)]
156pub enum PressureTendencyParseError {
157 Empty,
159}
160
161impl fmt::Display for PressureTendencyParseError {
162 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::Empty => formatter.write_str("pressure tendency cannot be empty"),
165 }
166 }
167}
168
169impl Error for PressureTendencyParseError {}
170
171#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
173pub struct AtmosphericPressure(f64);
174
175impl AtmosphericPressure {
176 pub fn new(hectopascals: f64) -> Result<Self, PressureValueError> {
182 validate_pressure(hectopascals).map(Self)
183 }
184
185 #[must_use]
187 pub fn hectopascals(&self) -> f64 {
188 self.0
189 }
190
191 #[must_use]
193 pub fn unit_label(&self) -> PressureUnitLabel {
194 PressureUnitLabel::Hectopascal
195 }
196}
197
198#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
200pub struct SeaLevelPressure(f64);
201
202impl SeaLevelPressure {
203 pub fn new(hectopascals: f64) -> Result<Self, PressureValueError> {
209 validate_pressure(hectopascals).map(Self)
210 }
211
212 #[must_use]
214 pub fn hectopascals(&self) -> f64 {
215 self.0
216 }
217
218 #[must_use]
220 pub fn unit_label(&self) -> PressureUnitLabel {
221 PressureUnitLabel::Hectopascal
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::{
228 AtmosphericPressure, PressureTendency, PressureTendencyParseError, PressureValueError,
229 SeaLevelPressure,
230 };
231 use core::str::FromStr;
232
233 #[test]
234 fn valid_pressure() {
235 let pressure = AtmosphericPressure::new(1008.5).unwrap();
236
237 assert_eq!(pressure.hectopascals(), 1008.5);
238 }
239
240 #[test]
241 fn negative_pressure_rejected() {
242 assert_eq!(
243 AtmosphericPressure::new(-1.0),
244 Err(PressureValueError::NegativePressure(-1.0))
245 );
246 }
247
248 #[test]
249 fn sea_level_pressure_construction() {
250 let pressure = SeaLevelPressure::new(1016.3).unwrap();
251
252 assert_eq!(pressure.hectopascals(), 1016.3);
253 }
254
255 #[test]
256 fn tendency_display_and_parse() {
257 assert_eq!(PressureTendency::Steady.to_string(), "steady");
258 assert_eq!(
259 PressureTendency::from_str("falling").unwrap(),
260 PressureTendency::Falling
261 );
262 assert_eq!(
263 PressureTendency::from_str(" "),
264 Err(PressureTendencyParseError::Empty)
265 );
266 }
267
268 #[test]
269 fn custom_tendency() {
270 assert_eq!(
271 PressureTendency::from_str("rapid rise").unwrap(),
272 PressureTendency::Custom(String::from("rapid rise"))
273 );
274 }
275}