1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn non_empty_text(
8 value: impl AsRef<str>,
9 error: PressureSystemValueError,
10) -> Result<String, PressureSystemValueError> {
11 let trimmed = value.as_ref().trim();
12
13 if trimmed.is_empty() {
14 Err(error)
15 } else {
16 Ok(trimmed.to_string())
17 }
18}
19
20fn normalized_key(value: &str) -> String {
21 value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum PressureSystemValueError {
27 EmptyPressureSystemName,
29 EmptyPressureSystemStrength,
31}
32
33impl fmt::Display for PressureSystemValueError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::EmptyPressureSystemName => {
37 formatter.write_str("pressure system name cannot be empty")
38 },
39 Self::EmptyPressureSystemStrength => {
40 formatter.write_str("pressure system strength cannot be empty")
41 },
42 }
43 }
44}
45
46impl Error for PressureSystemValueError {}
47
48#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
50pub enum PressureSystemKind {
51 High,
53 Low,
55 Ridge,
57 Trough,
59 Cyclone,
61 Anticyclone,
63 Unknown,
65 Custom(String),
67}
68
69impl fmt::Display for PressureSystemKind {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::High => formatter.write_str("high"),
73 Self::Low => formatter.write_str("low"),
74 Self::Ridge => formatter.write_str("ridge"),
75 Self::Trough => formatter.write_str("trough"),
76 Self::Cyclone => formatter.write_str("cyclone"),
77 Self::Anticyclone => formatter.write_str("anticyclone"),
78 Self::Unknown => formatter.write_str("unknown"),
79 Self::Custom(value) => formatter.write_str(value),
80 }
81 }
82}
83
84impl FromStr for PressureSystemKind {
85 type Err = PressureSystemKindParseError;
86
87 fn from_str(value: &str) -> Result<Self, Self::Err> {
88 let trimmed = value.trim();
89
90 if trimmed.is_empty() {
91 return Err(PressureSystemKindParseError::Empty);
92 }
93
94 match normalized_key(trimmed).as_str() {
95 "high" => Ok(Self::High),
96 "low" => Ok(Self::Low),
97 "ridge" => Ok(Self::Ridge),
98 "trough" => Ok(Self::Trough),
99 "cyclone" => Ok(Self::Cyclone),
100 "anticyclone" => Ok(Self::Anticyclone),
101 "unknown" => Ok(Self::Unknown),
102 _ => Ok(Self::Custom(trimmed.to_string())),
103 }
104 }
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum PressureSystemKindParseError {
110 Empty,
112}
113
114impl fmt::Display for PressureSystemKindParseError {
115 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Self::Empty => formatter.write_str("pressure system kind cannot be empty"),
118 }
119 }
120}
121
122impl Error for PressureSystemKindParseError {}
123
124#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
126pub enum CycloneKind {
127 TropicalCyclone,
129 ExtratropicalCyclone,
131 SubtropicalCyclone,
133 Mesocyclone,
135 PolarLow,
137 Unknown,
139 Custom(String),
141}
142
143impl fmt::Display for CycloneKind {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::TropicalCyclone => formatter.write_str("tropical-cyclone"),
147 Self::ExtratropicalCyclone => formatter.write_str("extratropical-cyclone"),
148 Self::SubtropicalCyclone => formatter.write_str("subtropical-cyclone"),
149 Self::Mesocyclone => formatter.write_str("mesocyclone"),
150 Self::PolarLow => formatter.write_str("polar-low"),
151 Self::Unknown => formatter.write_str("unknown"),
152 Self::Custom(value) => formatter.write_str(value),
153 }
154 }
155}
156
157impl FromStr for CycloneKind {
158 type Err = CycloneKindParseError;
159
160 fn from_str(value: &str) -> Result<Self, Self::Err> {
161 let trimmed = value.trim();
162
163 if trimmed.is_empty() {
164 return Err(CycloneKindParseError::Empty);
165 }
166
167 match normalized_key(trimmed).as_str() {
168 "tropical-cyclone" | "tropicalcyclone" => Ok(Self::TropicalCyclone),
169 "extratropical-cyclone" | "extratropicalcyclone" => Ok(Self::ExtratropicalCyclone),
170 "subtropical-cyclone" | "subtropicalcyclone" => Ok(Self::SubtropicalCyclone),
171 "mesocyclone" => Ok(Self::Mesocyclone),
172 "polar-low" | "polarlow" => Ok(Self::PolarLow),
173 "unknown" => Ok(Self::Unknown),
174 _ => Ok(Self::Custom(trimmed.to_string())),
175 }
176 }
177}
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum CycloneKindParseError {
182 Empty,
184}
185
186impl fmt::Display for CycloneKindParseError {
187 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 match self {
189 Self::Empty => formatter.write_str("cyclone kind cannot be empty"),
190 }
191 }
192}
193
194impl Error for CycloneKindParseError {}
195
196#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
198pub struct PressureSystemName(String);
199
200impl PressureSystemName {
201 pub fn new(value: impl AsRef<str>) -> Result<Self, PressureSystemValueError> {
207 non_empty_text(value, PressureSystemValueError::EmptyPressureSystemName).map(Self)
208 }
209
210 #[must_use]
212 pub fn as_str(&self) -> &str {
213 &self.0
214 }
215}
216
217impl fmt::Display for PressureSystemName {
218 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
219 formatter.write_str(self.as_str())
220 }
221}
222
223impl FromStr for PressureSystemName {
224 type Err = PressureSystemValueError;
225
226 fn from_str(value: &str) -> Result<Self, Self::Err> {
227 Self::new(value)
228 }
229}
230
231#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
233pub struct PressureSystemStrength(String);
234
235impl PressureSystemStrength {
236 pub fn new(value: impl AsRef<str>) -> Result<Self, PressureSystemValueError> {
242 non_empty_text(value, PressureSystemValueError::EmptyPressureSystemStrength).map(Self)
243 }
244
245 #[must_use]
247 pub fn as_str(&self) -> &str {
248 &self.0
249 }
250}
251
252impl fmt::Display for PressureSystemStrength {
253 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
254 formatter.write_str(self.as_str())
255 }
256}
257
258impl FromStr for PressureSystemStrength {
259 type Err = PressureSystemValueError;
260
261 fn from_str(value: &str) -> Result<Self, Self::Err> {
262 Self::new(value)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::{
269 CycloneKind, CycloneKindParseError, PressureSystemKind, PressureSystemKindParseError,
270 PressureSystemName, PressureSystemValueError,
271 };
272 use core::str::FromStr;
273
274 #[test]
275 fn valid_pressure_system_name() {
276 let name = PressureSystemName::new("Aleutian Low").unwrap();
277
278 assert_eq!(name.as_str(), "Aleutian Low");
279 }
280
281 #[test]
282 fn empty_pressure_system_name_rejected() {
283 assert_eq!(
284 PressureSystemName::new(" "),
285 Err(PressureSystemValueError::EmptyPressureSystemName)
286 );
287 }
288
289 #[test]
290 fn pressure_system_kind_display_and_parse() {
291 assert_eq!(PressureSystemKind::Anticyclone.to_string(), "anticyclone");
292 assert_eq!(
293 PressureSystemKind::from_str("ridge").unwrap(),
294 PressureSystemKind::Ridge
295 );
296 assert_eq!(
297 PressureSystemKind::from_str(" "),
298 Err(PressureSystemKindParseError::Empty)
299 );
300 }
301
302 #[test]
303 fn cyclone_kind_display_and_parse() {
304 assert_eq!(CycloneKind::PolarLow.to_string(), "polar-low");
305 assert_eq!(
306 CycloneKind::from_str("mesocyclone").unwrap(),
307 CycloneKind::Mesocyclone
308 );
309 assert_eq!(
310 CycloneKind::from_str(" "),
311 Err(CycloneKindParseError::Empty)
312 );
313 }
314
315 #[test]
316 fn custom_pressure_system_kind() {
317 assert_eq!(
318 PressureSystemKind::from_str("blocking high").unwrap(),
319 PressureSystemKind::Custom(String::from("blocking high"))
320 );
321 }
322}