badpod/
basic.rs

1use crate::Other;
2
3pub enum NumberConstraint {
4    None,
5    Positive,
6    NonNegative,
7    Range(f64, f64),
8}
9
10pub enum BoolType {
11    TrueFalse,
12    YesNo,
13}
14
15/// Used for deserializing boolean values.
16#[derive(Debug, PartialEq, Eq)]
17pub enum Bool {
18    Ok(bool),
19    Other(Other),
20}
21
22impl Default for Bool {
23    fn default() -> Self {
24        Bool::Ok(false)
25    }
26}
27
28impl Bool {
29    pub fn parse(s: &str, bool_type: BoolType) -> Self {
30        match bool_type {
31            BoolType::TrueFalse => match s {
32                "true" => Bool::Ok(true),
33                "false" => Bool::Ok(false),
34                _ => Bool::Other((s.to_string(), "should be \"true\" or \"false\"".to_string())),
35            },
36            BoolType::YesNo => match s {
37                "yes" => Bool::Ok(true),
38                "no" => Bool::Ok(false),
39                _ => Bool::Other((s.to_string(), "should be \"yes\" or \"no\"".to_string())),
40            },
41        }
42    }
43}
44
45impl std::str::FromStr for Bool {
46    type Err = String;
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        match s.parse::<bool>() {
50            Ok(x) => Ok(Self::Ok(x)),
51            Err(e) => Ok(Self::Other((s.to_string(), e.to_string()))),
52        }
53    }
54}
55
56impl std::fmt::Display for Bool {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Self::Ok(t) => write!(f, "{t}"),
60            Self::Other((s, _)) => write!(f, "{s}"),
61        }
62    }
63}
64
65/// Used for deserializing integer values.
66#[derive(Debug, PartialEq, Eq)]
67pub enum Integer {
68    Ok(i64),
69    Other(Other),
70}
71
72impl Integer {
73    pub fn parse(s: &str, constraint: NumberConstraint) -> Self {
74        match s.parse::<i64>() {
75            Ok(x) => match constraint {
76                NumberConstraint::None => Self::Ok(x),
77                NumberConstraint::Positive => {
78                    if x > 0 {
79                        Self::Ok(x)
80                    } else {
81                        Self::Other((s.to_string(), "should be positive".to_string()))
82                    }
83                }
84                NumberConstraint::NonNegative => {
85                    if x >= 0 {
86                        Self::Ok(x)
87                    } else {
88                        Self::Other((s.to_string(), "should be non-negative".to_string()))
89                    }
90                }
91                NumberConstraint::Range(min, max) => {
92                    if x as f64 >= min && x as f64 <= max {
93                        Self::Ok(x)
94                    } else {
95                        Self::Other((s.to_string(), format!("should be in range [{min}, {max}]")))
96                    }
97                }
98            },
99            Err(_) => Self::Other((s.to_string(), "should be an integer".to_string())),
100        }
101    }
102}
103
104impl std::fmt::Display for Integer {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        match self {
107            Self::Ok(t) => write!(f, "{t}"),
108            Self::Other((s, _)) => write!(f, "{s}"),
109        }
110    }
111}
112
113/// Used for deserializing floating-point values.
114#[derive(Debug, PartialEq)]
115pub enum Float {
116    Ok(f64),
117    Other(Other),
118}
119
120impl Float {
121    pub fn parse(s: &str, constraint: NumberConstraint) -> Self {
122        match s.parse::<f64>() {
123            Ok(x) => match constraint {
124                NumberConstraint::None => Self::Ok(x),
125                NumberConstraint::Positive => {
126                    if x > 0.0 {
127                        Self::Ok(x)
128                    } else {
129                        Self::Other((s.to_string(), "should be positive".to_string()))
130                    }
131                }
132                NumberConstraint::NonNegative => {
133                    if x >= 0.0 {
134                        Self::Ok(x)
135                    } else {
136                        Self::Other((s.to_string(), "should be non-negative".to_string()))
137                    }
138                }
139                NumberConstraint::Range(min, max) => {
140                    if x >= min && x <= max {
141                        Self::Ok(x)
142                    } else {
143                        Self::Other((s.to_string(), format!("should be in range [{min}, {max}]")))
144                    }
145                }
146            },
147            Err(_) => Self::Other((
148                s.to_string(),
149                "should be a floating-point number".to_string(),
150            )),
151        }
152    }
153}
154
155impl std::fmt::Display for Float {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        match self {
158            Self::Ok(t) => write!(f, "{t}"),
159            Self::Other((s, _)) => write!(f, "{s}"),
160        }
161    }
162}
163
164/// Used for deserializing values that could be either integers or floats.
165///
166/// Preference is given to the *former*.
167#[derive(Debug, PartialEq)]
168pub enum Number {
169    Integer(i64),
170    Float(f64),
171    Other(Other),
172}
173
174impl Number {
175    pub fn parse(s: &str, constraint: NumberConstraint) -> Self {
176        match s.parse::<i64>() {
177            Ok(x) => match constraint {
178                NumberConstraint::None => Self::Integer(x),
179                NumberConstraint::Positive => {
180                    if x > 0 {
181                        Self::Integer(x)
182                    } else {
183                        Self::Other((s.to_string(), "should be positive".to_string()))
184                    }
185                }
186                NumberConstraint::NonNegative => {
187                    if x >= 0 {
188                        Self::Integer(x)
189                    } else {
190                        Self::Other((s.to_string(), "should be non-negative".to_string()))
191                    }
192                }
193                NumberConstraint::Range(min, max) => {
194                    if x as f64 >= min && x as f64 <= max {
195                        Self::Integer(x)
196                    } else {
197                        Self::Other((s.to_string(), format!("should be in range [{min}, {max}]")))
198                    }
199                }
200            },
201            Err(_) => match s.parse::<f64>() {
202                Ok(x) => match constraint {
203                    NumberConstraint::None => Self::Float(x),
204                    NumberConstraint::Positive => {
205                        if x > 0.0 {
206                            Self::Float(x)
207                        } else {
208                            Self::Other((s.to_string(), "should be positive".to_string()))
209                        }
210                    }
211                    NumberConstraint::NonNegative => {
212                        if x >= 0.0 {
213                            Self::Float(x)
214                        } else {
215                            Self::Other((s.to_string(), "should be non-negative".to_string()))
216                        }
217                    }
218                    NumberConstraint::Range(min, max) => {
219                        if x >= min && x <= max {
220                            Self::Float(x)
221                        } else {
222                            Self::Other((
223                                s.to_string(),
224                                format!("should be in range [{min}, {max}]"),
225                            ))
226                        }
227                    }
228                },
229                Err(_) => Self::Other((s.to_string(), "should be a number".to_string())),
230            },
231        }
232    }
233}
234
235impl std::fmt::Display for Number {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        match self {
238            Self::Integer(t) => write!(f, "{t}"),
239            Self::Float(t) => write!(f, "{t}"),
240            Self::Other((s, _)) => write!(f, "{s}"),
241        }
242    }
243}
244
245/// Duration.
246#[derive(Debug, PartialEq, Eq)]
247pub enum Duration {
248    Duration(chrono::Duration),
249    Other(Other),
250}
251
252impl Duration {
253    pub fn parse_from_int_string(s: &str) -> Self {
254        match s.parse::<u64>() {
255            Ok(x) => Self::Duration(chrono::Duration::seconds(x as i64)),
256            Err(_) => Self::Other((
257                s.to_string(),
258                "should be a non-negative integer".to_string(),
259            )),
260        }
261    }
262
263    pub fn parse_from_float_string(s: &str) -> Self {
264        match s.parse::<f64>() {
265            Ok(x) => {
266                // Convert to nanoseconds and round to the nearest integer.
267                let x = (x * 1_000_000_000.0).round() as i64;
268                Self::Duration(chrono::Duration::nanoseconds(x))
269            }
270            Err(_) => Self::Other((
271                s.to_string(),
272                "should be a non-negative floating-point number".to_string(),
273            )),
274        }
275    }
276}