diff --git a/api/src/domain/recipe/fetch_list.rs b/api/src/domain/recipe/fetch_list.rs index 4600de3..c616991 100644 --- a/api/src/domain/recipe/fetch_list.rs +++ b/api/src/domain/recipe/fetch_list.rs @@ -20,7 +20,7 @@ mod tests { let res = execute(&repo); match res.as_slice() { - [salad, pizza_base] => { + [salad] => { assert_eq!(salad.key, String::from("fruit_salad")); assert_eq!(salad.lang, Lang::Rus); assert_eq!(salad.name, String::from("Фруктовый салат")); @@ -44,13 +44,32 @@ mod tests { } _ => unreachable!(), } + } + _ => unreachable!(), + } + } - assert_eq!(pizza_base.key, String::from("thin_crispy_pizza_base")); - assert_eq!(pizza_base.lang, Lang::Rus); - assert_eq!( - pizza_base.name, - String::from("Тонкая хрустящая основа для пиццы") - ); + #[test] + fn should_not_return_recipes_if_ingredients_not_exist_in_db() { + let mut repo = InMemoryRecipeRepo::new().with_no_ingredients_found(); + let res = execute(&repo); + + match res.as_slice() { + [salad] => { + assert_eq!(salad.key, String::from("fruit_salad")); + } + _ => unreachable!(), + } + + if let Some(rec) = repo.recipes.get_mut(1) { + rec.ingredients.pop(); // remove wheat flour from ingredients + } + + let res = execute(&repo); + match res.as_slice() { + [salad, no_found] => { + assert_eq!(salad.key, String::from("fruit_salad")); + assert_eq!(no_found.key, String::from("no_ingredients_found")); } _ => unreachable!(), } diff --git a/api/src/domain/recipe/types.rs b/api/src/domain/recipe/types.rs index 909e300..475a628 100644 --- a/api/src/domain/recipe/types.rs +++ b/api/src/domain/recipe/types.rs @@ -9,15 +9,36 @@ pub struct Recipe { pub ingredients: Vec, } -impl From<(&db::data::Recipe, Lang, Vec)> for Recipe { - fn from((db, lang, ingredients): (&db::data::Recipe, Lang, Vec)) -> Self { +impl TryFrom<(&db::data::Recipe, Lang, Vec)> for Recipe { + type Error = (); + + fn try_from( + (db, lang, ingredients): (&db::data::Recipe, Lang, Vec), + ) -> Result { let tr = &db.translates; let ctr = match lang { Lang::Rus => &tr.rus, Lang::Eng => tr.eng.as_ref().unwrap_or(&tr.rus), }; - Self { + let ingredients = db + .ingredients + .iter() + .map(|sing| { + ingredients + .iter() + .find(|ing| sing.key == ing.key) + .map(|ing| RecipeIngredient { + ingredient: ing.clone(), + measure: sing.measure.into(), + }) + .ok_or(()) + }) + .collect::, _>>()?; + + let instructions = ctr.instructions.iter().copied().map(String::from).collect(); + + Ok(Self { key: db.key.to_string(), lang: if ctr.name == tr.rus.name { Lang::Rus @@ -25,21 +46,9 @@ impl From<(&db::data::Recipe, Lang, Vec)> for Recipe { lang }, name: ctr.name.to_string(), - instructions: ctr.instructions.iter().copied().map(String::from).collect(), - ingredients: db - .ingredients - .iter() - .filter_map(|sing| { - ingredients - .iter() - .find(|ing| sing.key == ing.key) - .map(|ing| RecipeIngredient { - ingredient: ing.clone(), - measure: sing.measure.into(), - }) - }) - .collect(), - } + instructions, + ingredients, + }) } } diff --git a/api/src/repo/recipe.rs b/api/src/repo/recipe.rs index d7335b6..06969e3 100644 --- a/api/src/repo/recipe.rs +++ b/api/src/repo/recipe.rs @@ -1,7 +1,11 @@ use crate::domain::{misc_types::Lang, recipe::types}; +use crate::repo; use super::ingredient::IngredientRepo; +use db::data as Db; +use Db::RecipeIngredientMeasure as DbRIM; + pub trait RecipeRepo { fn get_recipes(&self) -> Vec; } @@ -10,121 +14,100 @@ pub struct StaticRecipeRepo; impl RecipeRepo for StaticRecipeRepo { fn get_recipes(&self) -> Vec { - let ings_repo = crate::repo::ingredient::StaticIngredientRepo; + let ings_repo = repo::ingredient::StaticIngredientRepo; let ings = ings_repo.get_ingredients(Default::default()); let langs = [Lang::default()].repeat(db::RECIPES.len()); db::RECIPES .iter() .zip(langs) - .map(|(rec, lang)| From::from((rec, lang, ings.clone()))) + .filter_map(|(rec, lang)| types::Recipe::try_from((rec, lang, ings.clone())).ok()) .collect() } } #[cfg(test)] pub struct InMemoryRecipeRepo { - recipes: Vec, + pub recipes: Vec, } #[cfg(test)] impl InMemoryRecipeRepo { pub fn new() -> Self { - use db::data as Db; - use Db::RecipeIngredientMeasure as DbRIM; - Self { - recipes: vec![ - Db::Recipe { - key: "fruit_salad", - steps: 0, - ingredients: vec![ - Db::RecipeIngredient { - key: "banana", - measure: DbRIM::Gram(150), - }, - Db::RecipeIngredient { - key: "apple", - measure: DbRIM::Gram(150), - }, - Db::RecipeIngredient { - key: "orange", - measure: DbRIM::Gram(150), - }, - ], - translates: Db::RecipeTranslates { - rus: Db::RecipeTranslate { - name: "Фруктовый салат", - instructions: vec![ - "Нарезать бананы кружочками", - "Нарезать яблоки и апельсины кубиками", - "Все ингредиенты перемешать", - ], - }, - eng: None, + recipes: vec![Db::Recipe { + key: "fruit_salad", + steps: 0, + ingredients: vec![ + Db::RecipeIngredient { + key: "banana", + measure: DbRIM::Gram(150), }, + Db::RecipeIngredient { + key: "apple", + measure: DbRIM::Gram(150), + }, + Db::RecipeIngredient { + key: "orange", + measure: DbRIM::Gram(150), + }, + ], + translates: Db::RecipeTranslates { + rus: Db::RecipeTranslate { + name: "Фруктовый салат", + instructions: vec![ + "Нарезать бананы кружочками", + "Нарезать яблоки и апельсины кубиками", + "Все ингредиенты перемешать", + ], + }, + eng: None, }, - Db::Recipe { - key: "thin_crispy_pizza_base", - steps: 7, - ingredients: vec![ - Db::RecipeIngredient { - key: "salt", - measure: DbRIM::Gram(5), - }, - Db::RecipeIngredient { - key: "sugar", - measure: DbRIM::Gram(4), - }, - Db::RecipeIngredient { - key: "wheat_flour", - measure: DbRIM::Gram(500), - }, - Db::RecipeIngredient { - key: "dry_yeast", - measure: DbRIM::Gram(7), - }, - Db::RecipeIngredient { - key: "olive_oil", - measure: DbRIM::MilliLiter(25), - }, - Db::RecipeIngredient { - key: "water", - measure: DbRIM::MilliLiter(250), - }, - ], - translates: Db::RecipeTranslates { - rus: Db::RecipeTranslate { - name: "Тонкая хрустящая основа для пиццы", - instructions: vec![ - "Растворить дрожжи в воде, подогретой до температуры тела.", - "Добавить соль, сахар, оливковое масло и хорошо перемешать до однородной консистенции.", - "Добавить муку и замесить тесто. Вымешивать не менее 15 минут.", - "Разделить тесто на 3 порции, каждую завернуть в пищевую плёнку и дать настояться около 30 минут.", - "Растянуть один кусок теста до тонкого состояния и аккуратно переложить на смазанный растительным маслом противень.", - "Смазать соусом, чтобы тесто не осталось жидким и чтобы начинка не скользила по нему, и поставить в предварительно разогретую до максимальной температуры (не меньше 250 градусов) на 5-10 минут. В результате тесто хорошо пропечется и станет хрустящим.", - "Теперь на основу можно выкладывать начинку на ваш вкус и запекать до момента, когда сыр растает.", - ], - }, - eng: None, - }, + }], + } + } + + pub fn with_no_ingredients_found(mut self) -> Self { + self.recipes.push(Db::Recipe { + key: "no_ingredients_found", + steps: 0, + ingredients: vec![ + Db::RecipeIngredient { + key: "salt", + measure: DbRIM::Gram(5), + }, + Db::RecipeIngredient { + key: "sugar", + measure: DbRIM::Gram(4), + }, + Db::RecipeIngredient { + key: "wheat_flour", + measure: DbRIM::Gram(500), }, ], - } + translates: Db::RecipeTranslates { + rus: Db::RecipeTranslate { + name: "Не найдены ингредиенты", + instructions: vec![], + }, + eng: None, + }, + }); + self } } #[cfg(test)] impl RecipeRepo for InMemoryRecipeRepo { fn get_recipes(&self) -> Vec { - let ings_repo = crate::repo::ingredient::InMemoryIngredientRepo::new(); + let ings_repo = repo::ingredient::InMemoryIngredientRepo::new(); let ings = ings_repo.get_ingredients(Default::default()); let langs = [Lang::default()].repeat(self.recipes.len()); self.recipes .iter() .zip(langs) - .map(|(rec, lang)| From::from((rec, lang, ings.clone()))) + .filter_map(|(rec, lang)| types::Recipe::try_from((rec, lang, ings.clone())).ok()) .collect() } }