cfg_if::cfg_if! { if #[cfg(feature = "sql")] { use mysql::prelude::*; use mysql::*; use mysql_common::frunk::{hlist_pat, HList}; use std::collections::HashMap; use struct_field_names_as_array::FieldNamesAsArray; } else {} } use serde::{Deserialize, Serialize}; #[allow(non_snake_case)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "sql", derive(FieldNamesAsArray))] pub struct Nutrition { pub id: u32, pub name: String, pub manufacturer: String, pub barcode: String, pub amount: u32, pub divisor: u32, pub kJ: f32, pub kcal: f32, pub fat: f32, pub saturated_fat: f32, pub carbohydrate: f32, pub sugar: f32, pub fibres: f32, pub protein: f32, pub salt: f32, pub vitamin_b2: f32, pub vitamin_b12: f32, pub calcium: f32, pub phosphor: f32, pub vitamin_d: f32, } #[cfg(feature = "sql")] type RowType = HList!( u32, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, Option, ); impl Nutrition { #[cfg(feature = "sql")] fn get_sql_fields() -> String { Nutrition::FIELD_NAMES_AS_ARRAY .iter() .cloned() .intersperse(", ") .collect() } #[cfg(feature = "sql")] fn query_map_helper() -> (String, impl Fn(RowType) -> Nutrition) { let sql_query = format!("SELECT {} from nutrition", Self::get_sql_fields()); let construction_closure = |row: RowType| { let hlist_pat![ id, name, manufacturer, barcode, amount, divisor, k_j, kcal, fat, saturated_fat, carbohydrate, sugar, fibres, protein, salt, vitamin_b2, vitamin_b12, calcium, phosphor, vitamin_d, ] = row; Nutrition { id, name: name.unwrap_or("".to_string()), manufacturer: manufacturer.unwrap_or("".to_string()), barcode: barcode.unwrap_or("".to_string()), amount: amount.unwrap_or(0), divisor: divisor.unwrap_or(0), kJ: k_j.unwrap_or(0.), kcal: kcal.unwrap_or(0.), fat: fat.unwrap_or(0.), saturated_fat: saturated_fat.unwrap_or(0.), carbohydrate: carbohydrate.unwrap_or(0.), sugar: sugar.unwrap_or(0.), fibres: fibres.unwrap_or(0.), protein: protein.unwrap_or(0.), salt: salt.unwrap_or(0.), vitamin_b2: vitamin_b2.unwrap_or(0.), vitamin_b12: vitamin_b12.unwrap_or(0.), calcium: calcium.unwrap_or(0.), phosphor: phosphor.unwrap_or(0.), vitamin_d: vitamin_d.unwrap_or(0.), } }; (sql_query, construction_closure) } #[cfg(feature = "sql")] pub async fn get_nutrition_rows(sql_pool: Pool) -> Result> { let mut conn = sql_pool .get_conn() .expect("Cannot establish database connection"); let (nutrition_query, nutrition_closure) = Self::query_map_helper(); let nutrition_val: Vec = conn .query_map(nutrition_query, nutrition_closure) .expect("Data in database doesn't match the Nutrition class"); Ok(nutrition_val) } #[cfg(feature = "sql")] pub async fn get_nutrition_hashmap(sql_pool: Pool) -> Result> { let nutrition_vec = Self::get_nutrition_rows(sql_pool).await?; let nutrition_hashmap: HashMap = nutrition_vec.into_iter().map(|n| (n.id, n)).collect(); Ok(nutrition_hashmap) } pub fn total_kcal(&self) -> f32 { match self.divisor { 0 => 0., _ => self.kcal * self.amount as f32 / self.divisor as f32, } } pub fn is_valid(&self) -> bool { // Comparing self.kcal with 0. should be okay, since it is initialized to 0. and if it is // changed in any way, this function should return true self.amount != 0 && self.divisor != 0 && self.kcal != 0. } pub fn id(&self) -> u32 { self.id } pub fn name(&self) -> String { self.name.clone() } pub fn manufacturer(&self) -> String { self.manufacturer.clone() } pub fn barcode(&self) -> String { self.barcode.clone() } }