openstranded_common_helpers/
lib.rs1use std::collections::HashMap;
22
23#[must_use]
37pub fn parse_color(s: &str) -> Option<[u8; 3]> {
38 let parts: Vec<&str> = s.split(',').collect();
39 if parts.len() != 3 {
40 return None;
41 }
42 Some([
43 parts[0].trim().parse().ok()?,
44 parts[1].trim().parse().ok()?,
45 parts[2].trim().parse().ok()?,
46 ])
47}
48
49#[must_use]
63pub fn first_str<'a>(fields: &'a HashMap<String, Vec<String>>, key: &str) -> Option<&'a str> {
64 fields.get(key)?.first().map(String::as_str)
65}
66
67#[must_use]
81pub fn first_u32(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<u32> {
82 first_str(fields, key)?.parse().ok()
83}
84
85#[must_use]
99pub fn first_f32(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<f32> {
100 first_str(fields, key)?.parse().ok()
101}
102
103#[must_use]
116pub fn first_string(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<String> {
117 first_str(fields, key).map(ToOwned::to_owned)
118}
119
120#[must_use]
134pub fn parse_optional_u32(s: &str) -> Option<Option<u32>> {
135 if s.eq_ignore_ascii_case("none") {
136 return Some(None);
137 }
138 s.parse::<u32>().ok().map(Some)
139}
140
141#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_parse_color_rgb() {
149 assert_eq!(parse_color("255,0,128"), Some([255, 0, 128]));
150 }
151
152 #[test]
153 fn test_parse_color_whitespace() {
154 assert_eq!(parse_color(" 10 , 20 , 30 "), Some([10, 20, 30]));
155 }
156
157 #[test]
158 fn test_parse_color_too_few() {
159 assert_eq!(parse_color("255,0"), None);
160 }
161
162 #[test]
163 fn test_parse_color_too_many() {
164 assert_eq!(parse_color("1,2,3,4"), None);
165 }
166
167 #[test]
168 fn test_parse_color_invalid() {
169 assert_eq!(parse_color("abc,def,ghi"), None);
170 }
171
172 #[test]
173 fn test_parse_color_empty() {
174 assert_eq!(parse_color(""), None);
175 }
176
177 #[test]
178 fn test_first_str_found() {
179 let mut fields = HashMap::new();
180 fields.insert("name".into(), vec!["Wood".into()]);
181 assert_eq!(first_str(&fields, "name"), Some("Wood"));
182 }
183
184 #[test]
185 fn test_first_str_not_found() {
186 let fields = HashMap::new();
187 assert_eq!(first_str(&fields, "missing"), None);
188 }
189
190 #[test]
191 fn test_first_str_multiple_values() {
192 let mut fields = HashMap::new();
193 fields.insert("key".into(), vec!["first".into(), "second".into()]);
194 assert_eq!(first_str(&fields, "key"), Some("first"));
195 }
196
197 #[test]
198 fn test_first_u32_ok() {
199 let mut fields = HashMap::new();
200 fields.insert("id".into(), vec!["42".into()]);
201 assert_eq!(first_u32(&fields, "id"), Some(42));
202 }
203
204 #[test]
205 fn test_first_u32_invalid() {
206 let mut fields = HashMap::new();
207 fields.insert("id".into(), vec!["not_a_number".into()]);
208 assert_eq!(first_u32(&fields, "id"), None);
209 }
210
211 #[test]
212 fn test_first_u32_not_found() {
213 let fields = HashMap::new();
214 assert_eq!(first_u32(&fields, "missing"), None);
215 }
216
217 #[test]
218 fn test_first_f32_ok() {
219 let mut fields = HashMap::new();
220 fields.insert("weight".into(), vec!["3.5".into()]);
221 let val = first_f32(&fields, "weight").unwrap();
222 assert!((val - 3.5).abs() < 1e-6);
223 }
224
225 #[test]
226 fn test_first_f32_integer_string() {
227 let mut fields = HashMap::new();
228 fields.insert("val".into(), vec!["7".into()]);
229 let val = first_f32(&fields, "val").unwrap();
230 assert!((val - 7.0).abs() < 1e-6);
231 }
232
233 #[test]
234 fn test_first_f32_not_found() {
235 let fields = HashMap::new();
236 assert_eq!(first_f32(&fields, "missing"), None);
237 }
238
239 #[test]
240 fn test_first_string_ok() {
241 let mut fields = HashMap::new();
242 fields.insert("name".into(), vec!["Wood".into()]);
243 assert_eq!(first_string(&fields, "name"), Some("Wood".into()));
244 }
245
246 #[test]
247 fn test_first_string_not_found() {
248 let fields = HashMap::new();
249 assert_eq!(first_string(&fields, "missing"), None);
250 }
251
252 #[test]
253 fn test_parse_optional_u32_number() {
254 assert_eq!(parse_optional_u32("42"), Some(Some(42)));
255 }
256
257 #[test]
258 fn test_parse_optional_u32_none() {
259 assert_eq!(parse_optional_u32("none"), Some(None));
260 }
261
262 #[test]
263 fn test_parse_optional_u32_none_uppercase() {
264 assert_eq!(parse_optional_u32("NONE"), Some(None));
265 }
266
267 #[test]
268 fn test_parse_optional_u32_invalid() {
269 assert_eq!(parse_optional_u32("abc"), None);
270 }
271}