1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn normalized_key(value: &str) -> String {
8 value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
9}
10
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub enum WeatherFrontKind {
14 Cold,
16 Warm,
18 Stationary,
20 Occluded,
22 Dryline,
24 SquallLine,
26 Unknown,
28 Custom(String),
30}
31
32impl fmt::Display for WeatherFrontKind {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Cold => formatter.write_str("cold"),
36 Self::Warm => formatter.write_str("warm"),
37 Self::Stationary => formatter.write_str("stationary"),
38 Self::Occluded => formatter.write_str("occluded"),
39 Self::Dryline => formatter.write_str("dryline"),
40 Self::SquallLine => formatter.write_str("squall-line"),
41 Self::Unknown => formatter.write_str("unknown"),
42 Self::Custom(value) => formatter.write_str(value),
43 }
44 }
45}
46
47impl FromStr for WeatherFrontKind {
48 type Err = WeatherFrontKindParseError;
49
50 fn from_str(value: &str) -> Result<Self, Self::Err> {
51 let trimmed = value.trim();
52
53 if trimmed.is_empty() {
54 return Err(WeatherFrontKindParseError::Empty);
55 }
56
57 match normalized_key(trimmed).as_str() {
58 "cold" => Ok(Self::Cold),
59 "warm" => Ok(Self::Warm),
60 "stationary" => Ok(Self::Stationary),
61 "occluded" => Ok(Self::Occluded),
62 "dryline" => Ok(Self::Dryline),
63 "squall-line" | "squallline" => Ok(Self::SquallLine),
64 "unknown" => Ok(Self::Unknown),
65 _ => Ok(Self::Custom(trimmed.to_string())),
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum WeatherFrontKindParseError {
73 Empty,
75}
76
77impl fmt::Display for WeatherFrontKindParseError {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Self::Empty => formatter.write_str("weather front kind cannot be empty"),
81 }
82 }
83}
84
85impl Error for WeatherFrontKindParseError {}
86
87#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub enum FrontMovement {
90 Advancing,
92 Retreating,
94 Stationary,
96 Weakening,
98 Strengthening,
100 Unknown,
102 Custom(String),
104}
105
106impl fmt::Display for FrontMovement {
107 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 Self::Advancing => formatter.write_str("advancing"),
110 Self::Retreating => formatter.write_str("retreating"),
111 Self::Stationary => formatter.write_str("stationary"),
112 Self::Weakening => formatter.write_str("weakening"),
113 Self::Strengthening => formatter.write_str("strengthening"),
114 Self::Unknown => formatter.write_str("unknown"),
115 Self::Custom(value) => formatter.write_str(value),
116 }
117 }
118}
119
120impl FromStr for FrontMovement {
121 type Err = FrontMovementParseError;
122
123 fn from_str(value: &str) -> Result<Self, Self::Err> {
124 let trimmed = value.trim();
125
126 if trimmed.is_empty() {
127 return Err(FrontMovementParseError::Empty);
128 }
129
130 match normalized_key(trimmed).as_str() {
131 "advancing" => Ok(Self::Advancing),
132 "retreating" => Ok(Self::Retreating),
133 "stationary" => Ok(Self::Stationary),
134 "weakening" => Ok(Self::Weakening),
135 "strengthening" => Ok(Self::Strengthening),
136 "unknown" => Ok(Self::Unknown),
137 _ => Ok(Self::Custom(trimmed.to_string())),
138 }
139 }
140}
141
142#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144pub enum FrontMovementParseError {
145 Empty,
147}
148
149impl fmt::Display for FrontMovementParseError {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 match self {
152 Self::Empty => formatter.write_str("front movement cannot be empty"),
153 }
154 }
155}
156
157impl Error for FrontMovementParseError {}
158
159#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
161pub enum FrontStrength {
162 Weak,
164 Moderate,
166 Strong,
168 Severe,
170 Unknown,
172 Custom(String),
174}
175
176impl fmt::Display for FrontStrength {
177 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 Self::Weak => formatter.write_str("weak"),
180 Self::Moderate => formatter.write_str("moderate"),
181 Self::Strong => formatter.write_str("strong"),
182 Self::Severe => formatter.write_str("severe"),
183 Self::Unknown => formatter.write_str("unknown"),
184 Self::Custom(value) => formatter.write_str(value),
185 }
186 }
187}
188
189impl FromStr for FrontStrength {
190 type Err = FrontStrengthParseError;
191
192 fn from_str(value: &str) -> Result<Self, Self::Err> {
193 let trimmed = value.trim();
194
195 if trimmed.is_empty() {
196 return Err(FrontStrengthParseError::Empty);
197 }
198
199 match normalized_key(trimmed).as_str() {
200 "weak" => Ok(Self::Weak),
201 "moderate" => Ok(Self::Moderate),
202 "strong" => Ok(Self::Strong),
203 "severe" => Ok(Self::Severe),
204 "unknown" => Ok(Self::Unknown),
205 _ => Ok(Self::Custom(trimmed.to_string())),
206 }
207 }
208}
209
210#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212pub enum FrontStrengthParseError {
213 Empty,
215}
216
217impl fmt::Display for FrontStrengthParseError {
218 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
219 match self {
220 Self::Empty => formatter.write_str("front strength cannot be empty"),
221 }
222 }
223}
224
225impl Error for FrontStrengthParseError {}
226
227#[cfg(test)]
228mod tests {
229 use super::{
230 FrontMovement, FrontMovementParseError, FrontStrength, FrontStrengthParseError,
231 WeatherFrontKind, WeatherFrontKindParseError,
232 };
233 use core::str::FromStr;
234
235 #[test]
236 fn front_kind_display_and_parse() {
237 assert_eq!(WeatherFrontKind::SquallLine.to_string(), "squall-line");
238 assert_eq!(
239 WeatherFrontKind::from_str("cold").unwrap(),
240 WeatherFrontKind::Cold
241 );
242 assert_eq!(
243 WeatherFrontKind::from_str(" "),
244 Err(WeatherFrontKindParseError::Empty)
245 );
246 }
247
248 #[test]
249 fn movement_display_and_parse() {
250 assert_eq!(FrontMovement::Advancing.to_string(), "advancing");
251 assert_eq!(
252 FrontMovement::from_str("weakening").unwrap(),
253 FrontMovement::Weakening
254 );
255 assert_eq!(
256 FrontMovement::from_str(" "),
257 Err(FrontMovementParseError::Empty)
258 );
259 }
260
261 #[test]
262 fn strength_display_and_parse() {
263 assert_eq!(FrontStrength::Strong.to_string(), "strong");
264 assert_eq!(
265 FrontStrength::from_str("severe").unwrap(),
266 FrontStrength::Severe
267 );
268 assert_eq!(
269 FrontStrength::from_str(" "),
270 Err(FrontStrengthParseError::Empty)
271 );
272 }
273
274 #[test]
275 fn custom_front_kind() {
276 assert_eq!(
277 WeatherFrontKind::from_str("lee trough").unwrap(),
278 WeatherFrontKind::Custom(String::from("lee trough"))
279 );
280 }
281}