use serde::{Deserialize, Serialize};
use crate::{
composition::{Composition, IntoComposition, Micro, PAC, Solids, SolidsBreakdown},
constants::{self},
error::Result,
validate::assert_are_positive,
};
#[doc = include_str!("../../docs/bibs/5.md")]
#[doc = include_str!("../../docs/bibs/6.md")]
#[derive(PartialEq, Serialize, Deserialize, Copy, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub enum MicroSpec {
Salt,
Lecithin,
Stabilizer {
strength: f64,
},
Emulsifier {
strength: f64,
},
EmulsifierStabilizer {
emulsifier_strength: f64,
stabilizer_strength: f64,
},
}
impl IntoComposition for MicroSpec {
fn into_composition(self) -> Result<Composition> {
let make_emulsifier_stabilizer_composition =
|emulsifiers_strength: Option<f64>, stabilizers_strength: Option<f64>| -> Result<Composition> {
let emulsifiers_strength = emulsifiers_strength.unwrap_or(0.0);
let stabilizers_strength = stabilizers_strength.unwrap_or(0.0);
assert_are_positive(&[emulsifiers_strength, stabilizers_strength])?;
Ok(Composition::new()
.solids(Solids::new().other(SolidsBreakdown::new().others(100.0)))
.micro(
Micro::new()
.emulsifiers(emulsifiers_strength)
.stabilizers(stabilizers_strength),
))
};
match self {
MicroSpec::Salt => Ok(Composition::new()
.solids(Solids::new().other(SolidsBreakdown::new().others(100.0)))
.micro(Micro::new().salt(100.0))
.pac(PAC::new().salt(constants::pac::SALT))),
MicroSpec::Lecithin => Ok(Composition::new()
.solids(Solids::new().other(SolidsBreakdown::new().others(100.0)))
.micro(Micro::new().lecithin(100.0).emulsifiers(100.0))),
MicroSpec::Stabilizer { strength } => make_emulsifier_stabilizer_composition(None, Some(strength)),
MicroSpec::Emulsifier { strength } => make_emulsifier_stabilizer_composition(Some(strength), None),
MicroSpec::EmulsifierStabilizer {
emulsifier_strength,
stabilizer_strength,
} => make_emulsifier_stabilizer_composition(Some(emulsifier_strength), Some(stabilizer_strength)),
}
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
#[allow(clippy::unwrap_used, clippy::float_cmp)]
pub(crate) mod tests {
use std::sync::LazyLock;
use crate::tests::asserts::shadow_asserts::assert_eq;
#[expect(unused_imports)]
use crate::tests::asserts::*;
use super::*;
use crate::{composition::CompKey, ingredient::Category, specs::IngredientSpec};
pub(crate) const ING_SPEC_MICRO_SALT_STR: &str = r#"{
"name": "Salt",
"category": "Micro",
"MicroSpec": "Salt"
}"#;
pub(crate) static ING_SPEC_MICRO_SALT: LazyLock<IngredientSpec> = LazyLock::new(|| IngredientSpec {
name: "Salt".to_string(),
category: Category::Micro,
spec: MicroSpec::Salt.into(),
});
#[test]
fn into_composition_micro_spec_salt() {
let comp = MicroSpec::Salt.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Salt), 100.0);
assert_eq!(comp.get(CompKey::PACslt), 585.0);
}
pub(crate) const ING_SPEC_MICRO_LECITHIN_STR: &str = r#"{
"name": "Lecithin",
"category": "Micro",
"MicroSpec": "Lecithin"
}"#;
pub(crate) static ING_SPEC_MICRO_LECITHIN: LazyLock<IngredientSpec> = LazyLock::new(|| IngredientSpec {
name: "Lecithin".to_string(),
category: Category::Micro,
spec: MicroSpec::Lecithin.into(),
});
#[test]
fn into_composition_micro_spec_lecithin() {
let comp = MicroSpec::Lecithin.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Emulsifiers), 100.0);
assert_eq!(comp.get(CompKey::Lecithin), 100.0);
}
pub(crate) const ING_SPEC_MICRO_STABILIZER_STR: &str = r#"{
"name": "Rich Ice Cream SB",
"category": "Micro",
"MicroSpec": {
"Stabilizer": {
"strength": 100
}
}
}"#;
pub(crate) static ING_SPEC_MICRO_STABILIZER: LazyLock<IngredientSpec> = LazyLock::new(|| IngredientSpec {
name: "Rich Ice Cream SB".to_string(),
category: Category::Micro,
spec: MicroSpec::Stabilizer { strength: 100.0 }.into(),
});
#[test]
fn into_composition_micro_spec_stabilizer_rich_ice_cream_sb() {
let comp = ING_SPEC_MICRO_STABILIZER.spec.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Stabilizers), 100.0);
}
#[test]
fn into_composition_micro_spec_stabilizer_not_100() {
let comp = MicroSpec::Stabilizer { strength: 85.0 }.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Stabilizers), 85.0);
}
#[test]
fn into_composition_micro_spec_emulsifier_not_100() {
let comp = MicroSpec::Emulsifier { strength: 60.0 }.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Emulsifiers), 60.0);
}
pub(crate) const ING_SPEC_MICRO_LOUIS_STAB2K_STR: &str = r#"{
"name": "Louis Francois Stab 2000",
"category": "Micro",
"MicroSpec": {
"EmulsifierStabilizer": {
"emulsifier_strength": 100,
"stabilizer_strength": 40
}
}
}"#;
pub(crate) static ING_SPEC_MICRO_LOUIS_STAB2K: LazyLock<IngredientSpec> = LazyLock::new(|| IngredientSpec {
name: "Louis Francois Stab 2000".to_string(),
category: Category::Micro,
spec: MicroSpec::EmulsifierStabilizer {
emulsifier_strength: 100.0,
stabilizer_strength: 40.0,
}
.into(),
});
#[test]
fn into_composition_micro_spec_emulsifier_stabilizer_louis_francois_stab_2000() {
let comp = ING_SPEC_MICRO_LOUIS_STAB2K.spec.into_composition().unwrap();
assert_eq!(comp.get(CompKey::Energy), 0.0);
assert_eq!(comp.get(CompKey::OtherSNFS), 100.0);
assert_eq!(comp.get(CompKey::TotalSolids), 100.0);
assert_eq!(comp.get(CompKey::Emulsifiers), 100.0);
assert_eq!(comp.get(CompKey::Stabilizers), 40.0);
}
pub(crate) static INGREDIENT_ASSETS_TABLE_MICRO: LazyLock<Vec<(&str, IngredientSpec, Option<Composition>)>> =
LazyLock::new(|| {
vec![
(ING_SPEC_MICRO_SALT_STR, ING_SPEC_MICRO_SALT.clone(), None),
(ING_SPEC_MICRO_LECITHIN_STR, ING_SPEC_MICRO_LECITHIN.clone(), None),
(ING_SPEC_MICRO_STABILIZER_STR, ING_SPEC_MICRO_STABILIZER.clone(), None),
(ING_SPEC_MICRO_LOUIS_STAB2K_STR, ING_SPEC_MICRO_LOUIS_STAB2K.clone(), None),
]
});
}