rust_recipe/
nutrition_information.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3
4/// Nutritional Information from a recipe.
5/// See <https://schema.org/NutritionInformation>
6#[derive(Default, Debug, PartialEq)]
7pub struct NutritionInformation {
8    /// The number of calories.
9    pub calories: Option<f32>,
10
11    /// The number of grams of carbohydrates.
12    pub carbohydrate_grams: Option<f32>,
13
14    /// The number of milligrams of cholesterol.
15    pub cholesterol_milligrams: Option<f32>,
16
17    /// The number of grams of fat.
18    pub fat_grams: Option<f32>,
19
20    /// The number of grams of fiber.
21    pub fiber_grams: Option<f32>,
22
23    /// The number of grams of protein.
24    pub protein_grams: Option<f32>,
25
26    /// The number of grams of saturated fat.
27    pub saturated_fat_grams: Option<f32>,
28
29    /// The serving size, in terms of the number of volume or mass.
30    pub serving_size: Option<String>,
31
32    /// The number of milligrams of sodium.
33    pub sodium_milligrams: Option<f32>,
34
35    /// The number of grams of sugar.
36    pub sugar_grams: Option<f32>,
37
38    /// The number of grams of trans fat.
39    pub trans_fat_grams: Option<f32>,
40
41    /// The number of grams of unsaturated fat.
42    pub unsaturated_fat_grams: Option<f32>,
43}
44
45impl NutritionInformation {
46    pub fn add_info(&mut self, field_name: &str, value: &str) {
47        let numeric_value = get_first_number_from_str(value);
48        match field_name {
49            "calories" => self.calories = numeric_value,
50            "carbohydrateContent" => self.carbohydrate_grams = numeric_value,
51            "cholesterolContent" => self.cholesterol_milligrams = numeric_value,
52            "fatContent" => self.fat_grams = numeric_value,
53            "fiberContent" => self.fiber_grams = numeric_value,
54            "proteinContent" => self.protein_grams = numeric_value,
55            "saturatedFatContent" => self.saturated_fat_grams = numeric_value,
56            "servingSize" => self.serving_size = Some(value.to_string()),
57            "sodiumContent" => self.sodium_milligrams = numeric_value,
58            "sugarContent" => self.sugar_grams = numeric_value,
59            "transFatContent" => self.trans_fat_grams = numeric_value,
60            "unsaturatedFatContent" => self.unsaturated_fat_grams = numeric_value,
61            _ => (),
62        }
63    }
64}
65
66fn get_first_number_from_str(s: &str) -> Option<f32> {
67    static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(\d*[.])?\d+"#).unwrap());
68    let captures = RE.captures(s);
69    match captures {
70        Some(caps) => caps[0].parse().ok(),
71        None => None,
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_add_info_success() {
81        let mut ni = NutritionInformation::default();
82        ni.add_info("calories", "200 calories");
83        ni.add_info("carbohydrateContent", "1 carbs");
84        ni.add_info("cholesterolContent", "2 cholesterol");
85        ni.add_info("fatContent", "3 fat");
86        ni.add_info("fiberContent", "4 fiber");
87        ni.add_info("proteinContent", "5 protein");
88        ni.add_info("saturatedFatContent", "6 fat");
89        ni.add_info("servingSize", "7 servings");
90        ni.add_info("sodiumContent", "8 sodium");
91        ni.add_info("sugarContent", "9 sugar");
92        ni.add_info("transFatContent", "10 fat");
93        ni.add_info("unsaturatedFatContent", "11 fat");
94
95        assert_eq!(Some(200_f32), ni.calories);
96        assert_eq!(Some(1_f32), ni.carbohydrate_grams);
97        assert_eq!(Some(2_f32), ni.cholesterol_milligrams);
98        assert_eq!(Some(3_f32), ni.fat_grams);
99        assert_eq!(Some(4_f32), ni.fiber_grams);
100        assert_eq!(Some(5_f32), ni.protein_grams);
101        assert_eq!(Some(6_f32), ni.saturated_fat_grams);
102        assert_eq!(Some(String::from("7 servings")), ni.serving_size);
103        assert_eq!(Some(8_f32), ni.sodium_milligrams);
104        assert_eq!(Some(9_f32), ni.sugar_grams);
105        assert_eq!(Some(10_f32), ni.trans_fat_grams);
106        assert_eq!(Some(11_f32), ni.unsaturated_fat_grams);
107    }
108
109    #[test]
110    fn test_add_info_failure() {
111        let mut ni = NutritionInformation::default();
112        ni.add_info("not valid", "100 of something");
113        assert_eq!(NutritionInformation::default(), ni);
114    }
115
116    #[test]
117    fn test_get_first_number_from_str_success_int() {
118        let res = get_first_number_from_str("About 200 calories");
119        assert_eq!(Some(200_f32), res)
120    }
121
122    #[test]
123    fn test_get_first_number_from_str_success_float() {
124        let res = get_first_number_from_str("200.1 calories");
125        assert_eq!(Some(200.1), res)
126    }
127
128    #[test]
129    fn test_get_first_number_from_str_failure() {
130        let res = get_first_number_from_str("some calories");
131        assert_eq!(None, res)
132    }
133}