Skip to main content

sci_cream/
display.rs

1//! Utilities to facilitate displaying various keys and values
2//!
3//! This module provides traits and functions to convert various lookup keys to strings suitable for
4//! display, as well as functions to compute composition values in various formats for displaying.
5//! This module is not necessary for core computations but is useful for UI and reporting purposes.
6
7#[cfg(feature = "wasm")]
8use wasm_bindgen::prelude::*;
9
10use crate::{composition::CompKey, fpd::FpdKey, properties::PropKey};
11
12/// Trait to convert keys to display-friendly strings at varying levels of verbosity.
13pub trait KeyAsStrings {
14    /// Returns a medium verbosity string representation of the key.
15    fn as_med_str(&self) -> &'static str;
16}
17
18impl KeyAsStrings for CompKey {
19    fn as_med_str(&self) -> &'static str {
20        match self {
21            Self::Energy => "Energy",
22
23            Self::MilkFat => "Milk Fat",
24            Self::MSNF => "MSNF",
25            Self::MilkSNFS => "Milk SNFS",
26            Self::MilkProteins => "Milk Proteins",
27            Self::MilkSolids => "Milk Solids",
28
29            Self::CocoaButter => "Cocoa Butter",
30            Self::CocoaSolids => "Cocoa Solids",
31            Self::CacaoSolids => "Cacao Solids",
32
33            Self::NutFat => "Nut Fat",
34            Self::NutSNF => "Nut SNF",
35            Self::NutSolids => "Nut Solids",
36
37            Self::EggFat => "Egg Fat",
38            Self::EggSNF => "Egg SNF",
39            Self::EggSolids => "Egg Solids",
40
41            Self::OtherFats => "Other Fats",
42            Self::OtherSNFS => "Other SNFS",
43
44            Self::TotalFats => "T. Fats",
45            Self::TotalSNF => "T. SNF",
46            Self::TotalSNFS => "T. SNFS",
47            Self::TotalProteins => "T. Proteins",
48            Self::TotalSolids => "T. Solids",
49
50            Self::Water => "Water",
51
52            Self::Fiber => "Fiber",
53            Self::Glucose => "Glucose",
54            Self::Fructose => "Fructose",
55            Self::Galactose => "Galactose",
56            Self::Sucrose => "Sucrose",
57            Self::Lactose => "Lactose",
58            Self::Maltose => "Maltose",
59            Self::Trehalose => "Trehalose",
60            Self::TotalSugars => "T. Sugars",
61            Self::Erythritol => "Erythritol",
62            Self::TotalPolyols => "T. Polyols",
63            Self::TotalArtificial => "T. Artificial",
64            Self::TotalSweeteners => "T. Sweeteners",
65            Self::TotalCarbohydrates => "T. Carbohydrates",
66
67            Self::Alcohol => "Alcohol",
68            Self::ABV => "ABV",
69
70            Self::Salt => "Salt",
71            Self::Lecithin => "Lecithin",
72            Self::Emulsifiers => "Emulsifiers",
73            Self::Stabilizers => "Stabilizers",
74            Self::EmulsifiersPerFat => "Emul./Fat",
75            Self::StabilizersPerWater => "Stab./Water",
76
77            Self::POD => "POD",
78
79            Self::PACsgr => "PACsgr",
80            Self::PACslt => "PACslt",
81            Self::PACmlk => "PACmlk",
82            Self::PACalc => "PACalc",
83            Self::PACtotal => "PAC",
84            Self::AbsPAC => "Abs.PAC",
85            Self::HF => "HF",
86        }
87    }
88}
89
90impl KeyAsStrings for FpdKey {
91    fn as_med_str(&self) -> &'static str {
92        match self {
93            Self::FPD => "FPD",
94            Self::ServingTemp => "Serving Temp",
95            Self::HardnessAt14C => "Hardness @-14°C",
96        }
97    }
98}
99
100impl KeyAsStrings for PropKey {
101    fn as_med_str(&self) -> &'static str {
102        match self {
103            Self::CompKey(comp_key) => comp_key.as_med_str(),
104            Self::FpdKey(fpd_key) => fpd_key.as_med_str(),
105        }
106    }
107}
108
109/// Computes a composition value as an absolute quantity based on the ingredient or mix quantity.
110///
111/// # Arguments
112///
113/// * `comp` - The composition value (grams per 100 grams) from [`Composition`](crate::Composition).
114/// * `qty` - The total quantity (grams) of the ingredient or mix.
115///
116/// # Examples
117///
118/// ```
119/// use sci_cream::display::composition_value_as_quantity;
120/// let milk_2_fat_comp = 2.0; // 2% milk fat, g/100g
121/// let milk_qty = 500.0; // grams
122/// let milk_fat_qty = composition_value_as_quantity(milk_2_fat_comp, milk_qty);
123/// assert_eq!(milk_fat_qty, 10.0);
124/// ```
125#[cfg_attr(feature = "wasm", wasm_bindgen)]
126#[must_use]
127pub fn composition_value_as_quantity(comp: f64, qty: f64) -> f64 {
128    (comp * qty) / 100.0
129}
130
131/// Computes an ingredient composition value and quantity as a percentage of the total mix.
132///
133/// # Arguments
134///
135/// * `comp` - The composition value (grams per 100 grams) from [`Composition`](crate::Composition).
136/// * `int_qty` - The quantity (grams) of the ingredient.
137/// * `mix_total` - The total quantity (grams) of the entire mix.
138///
139/// # Examples
140///
141/// ```
142/// use sci_cream::display::composition_value_as_percentage;
143/// let milk_2_fat_comp = 2.0; // 2% milk fat, g/100g
144/// let milk_qty = 500.0; // grams
145/// let mix_total = 1000.0; // grams
146/// let milk_fat_percentage = composition_value_as_percentage(milk_2_fat_comp, milk_qty, mix_total);
147/// assert_eq!(milk_fat_percentage, 1.0);
148/// ```
149#[cfg_attr(feature = "wasm", wasm_bindgen)]
150#[must_use]
151pub fn composition_value_as_percentage(comp: f64, int_qty: f64, mix_total: f64) -> f64 {
152    (comp * int_qty) / mix_total
153}
154
155/// WASM compatible wrappers for [`crate::display`] functions and trait methods.
156#[cfg(feature = "wasm")]
157#[cfg_attr(coverage, coverage(off))]
158pub mod wasm {
159    use wasm_bindgen::prelude::*;
160
161    use super::{CompKey, FpdKey, KeyAsStrings};
162
163    /// WASM compatible wrapper for [`KeyAsStrings::as_med_str`] for [`CompKey`]
164    #[wasm_bindgen]
165    #[must_use]
166    pub fn comp_key_as_med_str(key: CompKey) -> String {
167        key.as_med_str().to_string()
168    }
169
170    /// WASM compatible wrapper for [`KeyAsStrings::as_med_str`] for [`FpdKey`]
171    #[wasm_bindgen]
172    #[must_use]
173    pub fn fpd_key_as_med_str(key: FpdKey) -> String {
174        key.as_med_str().to_string()
175    }
176}
177
178#[cfg(test)]
179#[cfg_attr(coverage, coverage(off))]
180#[allow(clippy::float_cmp)]
181mod tests {
182    use std::collections::HashSet;
183
184    use strum::IntoEnumIterator;
185
186    use crate::tests::asserts::shadow_asserts::assert_eq;
187    use crate::tests::asserts::*;
188
189    use super::*;
190
191    #[test]
192    fn comp_keys_as_med_str() {
193        let some_expected = vec![
194            "Energy",
195            "Milk Fat",
196            "MSNF",
197            "Milk SNFS",
198            "Milk Proteins",
199            "Milk Solids",
200            "Cocoa Butter",
201            "Cocoa Solids",
202            "Cacao Solids",
203            "Nut Fat",
204            "Nut SNF",
205            "Nut Solids",
206            "Egg Fat",
207            "Egg SNF",
208            "Egg Solids",
209            "Other Fats",
210            "Other SNFS",
211            "T. Fats",
212            "T. SNF",
213            "T. SNFS",
214            "T. Proteins",
215            "T. Solids",
216            "Water",
217            "Fiber",
218            "Glucose",
219            "Fructose",
220            "Galactose",
221            "Sucrose",
222            "Lactose",
223            "Maltose",
224            "Trehalose",
225            "T. Sugars",
226            "Erythritol",
227            "T. Polyols",
228            "T. Artificial",
229            "T. Sweeteners",
230            "T. Carbohydrates",
231            "Alcohol",
232            "ABV",
233            "Salt",
234            "Lecithin",
235            "Emulsifiers",
236            "Stabilizers",
237            "Emul./Fat",
238            "Stab./Water",
239            "POD",
240            "PACsgr",
241            "PACslt",
242            "PACmlk",
243            "PACalc",
244            "PAC",
245            "Abs.PAC",
246            "HF",
247        ];
248
249        let actual_set: HashSet<&'static str> = CompKey::iter().map(|h| h.as_med_str()).collect();
250
251        for expected in some_expected {
252            assert_true!(actual_set.contains(expected));
253        }
254    }
255
256    #[test]
257    fn fpd_keys_as_med_str() {
258        let expected_vec = vec!["FPD", "Serving Temp", "Hardness @-14°C"];
259
260        let actual_vec: Vec<&'static str> = FpdKey::iter().map(|h| h.as_med_str()).collect();
261        assert_eq!(actual_vec, expected_vec);
262    }
263
264    #[test]
265    fn prop_keys_as_med_str() {
266        assert_eq!(PropKey::CompKey(CompKey::MilkFat).as_med_str(), "Milk Fat");
267        assert_eq!(PropKey::FpdKey(FpdKey::FPD).as_med_str(), "FPD");
268    }
269
270    #[test]
271    fn composition_value_as_quantity() {
272        let comp = 2.0; // 2% milk fat, g/100g
273        let qty = 500.0;
274        let qty_result = super::composition_value_as_quantity(comp, qty);
275        assert_eq!(qty_result, 10.0);
276    }
277
278    #[test]
279    fn composition_value_as_percentage() {
280        let comp = 2.0; // 2% milk fat, g/100g
281        let ing_qty = 500.0;
282        let mix_total = 1000.0;
283        let milk_fat_percentage = super::composition_value_as_percentage(comp, ing_qty, mix_total);
284        assert_eq!(milk_fat_percentage, 1.0);
285    }
286}