use std::collections::HashMap;
#[must_use]
pub fn parse_color(s: &str) -> Option<[u8; 3]> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 3 {
return None;
}
Some([
parts[0].trim().parse().ok()?,
parts[1].trim().parse().ok()?,
parts[2].trim().parse().ok()?,
])
}
#[must_use]
pub fn first_str<'a>(fields: &'a HashMap<String, Vec<String>>, key: &str) -> Option<&'a str> {
fields.get(key)?.first().map(String::as_str)
}
#[must_use]
pub fn first_u32(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<u32> {
first_str(fields, key)?.parse().ok()
}
#[must_use]
pub fn first_f32(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<f32> {
first_str(fields, key)?.parse().ok()
}
#[must_use]
pub fn first_string(fields: &HashMap<String, Vec<String>>, key: &str) -> Option<String> {
first_str(fields, key).map(ToOwned::to_owned)
}
#[must_use]
pub fn parse_optional_u32(s: &str) -> Option<Option<u32>> {
if s.eq_ignore_ascii_case("none") {
return Some(None);
}
s.parse::<u32>().ok().map(Some)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_color_rgb() {
assert_eq!(parse_color("255,0,128"), Some([255, 0, 128]));
}
#[test]
fn test_parse_color_whitespace() {
assert_eq!(parse_color(" 10 , 20 , 30 "), Some([10, 20, 30]));
}
#[test]
fn test_parse_color_too_few() {
assert_eq!(parse_color("255,0"), None);
}
#[test]
fn test_parse_color_too_many() {
assert_eq!(parse_color("1,2,3,4"), None);
}
#[test]
fn test_parse_color_invalid() {
assert_eq!(parse_color("abc,def,ghi"), None);
}
#[test]
fn test_parse_color_empty() {
assert_eq!(parse_color(""), None);
}
#[test]
fn test_first_str_found() {
let mut fields = HashMap::new();
fields.insert("name".into(), vec!["Wood".into()]);
assert_eq!(first_str(&fields, "name"), Some("Wood"));
}
#[test]
fn test_first_str_not_found() {
let fields = HashMap::new();
assert_eq!(first_str(&fields, "missing"), None);
}
#[test]
fn test_first_str_multiple_values() {
let mut fields = HashMap::new();
fields.insert("key".into(), vec!["first".into(), "second".into()]);
assert_eq!(first_str(&fields, "key"), Some("first"));
}
#[test]
fn test_first_u32_ok() {
let mut fields = HashMap::new();
fields.insert("id".into(), vec!["42".into()]);
assert_eq!(first_u32(&fields, "id"), Some(42));
}
#[test]
fn test_first_u32_invalid() {
let mut fields = HashMap::new();
fields.insert("id".into(), vec!["not_a_number".into()]);
assert_eq!(first_u32(&fields, "id"), None);
}
#[test]
fn test_first_u32_not_found() {
let fields = HashMap::new();
assert_eq!(first_u32(&fields, "missing"), None);
}
#[test]
fn test_first_f32_ok() {
let mut fields = HashMap::new();
fields.insert("weight".into(), vec!["3.5".into()]);
let val = first_f32(&fields, "weight").unwrap();
assert!((val - 3.5).abs() < 1e-6);
}
#[test]
fn test_first_f32_integer_string() {
let mut fields = HashMap::new();
fields.insert("val".into(), vec!["7".into()]);
let val = first_f32(&fields, "val").unwrap();
assert!((val - 7.0).abs() < 1e-6);
}
#[test]
fn test_first_f32_not_found() {
let fields = HashMap::new();
assert_eq!(first_f32(&fields, "missing"), None);
}
#[test]
fn test_first_string_ok() {
let mut fields = HashMap::new();
fields.insert("name".into(), vec!["Wood".into()]);
assert_eq!(first_string(&fields, "name"), Some("Wood".into()));
}
#[test]
fn test_first_string_not_found() {
let fields = HashMap::new();
assert_eq!(first_string(&fields, "missing"), None);
}
#[test]
fn test_parse_optional_u32_number() {
assert_eq!(parse_optional_u32("42"), Some(Some(42)));
}
#[test]
fn test_parse_optional_u32_none() {
assert_eq!(parse_optional_u32("none"), Some(None));
}
#[test]
fn test_parse_optional_u32_none_uppercase() {
assert_eq!(parse_optional_u32("NONE"), Some(None));
}
#[test]
fn test_parse_optional_u32_invalid() {
assert_eq!(parse_optional_u32("abc"), None);
}
}