api: filter recipes if ingredient not exist in db

This commit is contained in:
Dmitriy Pleshevskiy 2022-05-14 23:19:16 +03:00
parent 01216f2bbe
commit c19407940b
3 changed files with 117 additions and 106 deletions

View File

@ -20,7 +20,7 @@ mod tests {
let res = execute(&repo); let res = execute(&repo);
match res.as_slice() { match res.as_slice() {
[salad, pizza_base] => { [salad] => {
assert_eq!(salad.key, String::from("fruit_salad")); assert_eq!(salad.key, String::from("fruit_salad"));
assert_eq!(salad.lang, Lang::Rus); assert_eq!(salad.lang, Lang::Rus);
assert_eq!(salad.name, String::from("Фруктовый салат")); assert_eq!(salad.name, String::from("Фруктовый салат"));
@ -44,13 +44,32 @@ mod tests {
} }
_ => unreachable!(), _ => unreachable!(),
} }
}
_ => unreachable!(),
}
}
assert_eq!(pizza_base.key, String::from("thin_crispy_pizza_base")); #[test]
assert_eq!(pizza_base.lang, Lang::Rus); fn should_not_return_recipes_if_ingredients_not_exist_in_db() {
assert_eq!( let mut repo = InMemoryRecipeRepo::new().with_no_ingredients_found();
pizza_base.name, let res = execute(&repo);
String::from("Тонкая хрустящая основа для пиццы")
); 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!(), _ => unreachable!(),
} }

View File

@ -9,15 +9,36 @@ pub struct Recipe {
pub ingredients: Vec<RecipeIngredient>, pub ingredients: Vec<RecipeIngredient>,
} }
impl From<(&db::data::Recipe, Lang, Vec<Ingredient>)> for Recipe { impl TryFrom<(&db::data::Recipe, Lang, Vec<Ingredient>)> for Recipe {
fn from((db, lang, ingredients): (&db::data::Recipe, Lang, Vec<Ingredient>)) -> Self { type Error = ();
fn try_from(
(db, lang, ingredients): (&db::data::Recipe, Lang, Vec<Ingredient>),
) -> Result<Self, Self::Error> {
let tr = &db.translates; let tr = &db.translates;
let ctr = match lang { let ctr = match lang {
Lang::Rus => &tr.rus, Lang::Rus => &tr.rus,
Lang::Eng => tr.eng.as_ref().unwrap_or(&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::<Result<Vec<_>, _>>()?;
let instructions = ctr.instructions.iter().copied().map(String::from).collect();
Ok(Self {
key: db.key.to_string(), key: db.key.to_string(),
lang: if ctr.name == tr.rus.name { lang: if ctr.name == tr.rus.name {
Lang::Rus Lang::Rus
@ -25,21 +46,9 @@ impl From<(&db::data::Recipe, Lang, Vec<Ingredient>)> for Recipe {
lang lang
}, },
name: ctr.name.to_string(), name: ctr.name.to_string(),
instructions: ctr.instructions.iter().copied().map(String::from).collect(), instructions,
ingredients: db ingredients,
.ingredients })
.iter()
.filter_map(|sing| {
ingredients
.iter()
.find(|ing| sing.key == ing.key)
.map(|ing| RecipeIngredient {
ingredient: ing.clone(),
measure: sing.measure.into(),
})
})
.collect(),
}
} }
} }

View File

@ -1,7 +1,11 @@
use crate::domain::{misc_types::Lang, recipe::types}; use crate::domain::{misc_types::Lang, recipe::types};
use crate::repo;
use super::ingredient::IngredientRepo; use super::ingredient::IngredientRepo;
use db::data as Db;
use Db::RecipeIngredientMeasure as DbRIM;
pub trait RecipeRepo { pub trait RecipeRepo {
fn get_recipes(&self) -> Vec<types::Recipe>; fn get_recipes(&self) -> Vec<types::Recipe>;
} }
@ -10,121 +14,100 @@ pub struct StaticRecipeRepo;
impl RecipeRepo for StaticRecipeRepo { impl RecipeRepo for StaticRecipeRepo {
fn get_recipes(&self) -> Vec<types::Recipe> { fn get_recipes(&self) -> Vec<types::Recipe> {
let ings_repo = crate::repo::ingredient::StaticIngredientRepo; let ings_repo = repo::ingredient::StaticIngredientRepo;
let ings = ings_repo.get_ingredients(Default::default()); let ings = ings_repo.get_ingredients(Default::default());
let langs = [Lang::default()].repeat(db::RECIPES.len()); let langs = [Lang::default()].repeat(db::RECIPES.len());
db::RECIPES db::RECIPES
.iter() .iter()
.zip(langs) .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() .collect()
} }
} }
#[cfg(test)] #[cfg(test)]
pub struct InMemoryRecipeRepo { pub struct InMemoryRecipeRepo {
recipes: Vec<db::data::Recipe>, pub recipes: Vec<db::data::Recipe>,
} }
#[cfg(test)] #[cfg(test)]
impl InMemoryRecipeRepo { impl InMemoryRecipeRepo {
pub fn new() -> Self { pub fn new() -> Self {
use db::data as Db;
use Db::RecipeIngredientMeasure as DbRIM;
Self { Self {
recipes: vec![ recipes: vec![Db::Recipe {
Db::Recipe { key: "fruit_salad",
key: "fruit_salad", steps: 0,
steps: 0, ingredients: vec![
ingredients: vec![ Db::RecipeIngredient {
Db::RecipeIngredient { key: "banana",
key: "banana", measure: DbRIM::Gram(150),
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::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 { pub fn with_no_ingredients_found(mut self) -> Self {
key: "salt", self.recipes.push(Db::Recipe {
measure: DbRIM::Gram(5), key: "no_ingredients_found",
}, steps: 0,
Db::RecipeIngredient { ingredients: vec![
key: "sugar", Db::RecipeIngredient {
measure: DbRIM::Gram(4), key: "salt",
}, measure: DbRIM::Gram(5),
Db::RecipeIngredient { },
key: "wheat_flour", Db::RecipeIngredient {
measure: DbRIM::Gram(500), key: "sugar",
}, measure: DbRIM::Gram(4),
Db::RecipeIngredient { },
key: "dry_yeast", Db::RecipeIngredient {
measure: DbRIM::Gram(7), key: "wheat_flour",
}, measure: DbRIM::Gram(500),
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,
},
}, },
], ],
} translates: Db::RecipeTranslates {
rus: Db::RecipeTranslate {
name: "Не найдены ингредиенты",
instructions: vec![],
},
eng: None,
},
});
self
} }
} }
#[cfg(test)] #[cfg(test)]
impl RecipeRepo for InMemoryRecipeRepo { impl RecipeRepo for InMemoryRecipeRepo {
fn get_recipes(&self) -> Vec<types::Recipe> { fn get_recipes(&self) -> Vec<types::Recipe> {
let ings_repo = crate::repo::ingredient::InMemoryIngredientRepo::new(); let ings_repo = repo::ingredient::InMemoryIngredientRepo::new();
let ings = ings_repo.get_ingredients(Default::default()); let ings = ings_repo.get_ingredients(Default::default());
let langs = [Lang::default()].repeat(self.recipes.len()); let langs = [Lang::default()].repeat(self.recipes.len());
self.recipes self.recipes
.iter() .iter()
.zip(langs) .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() .collect()
} }
} }