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);
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!(),
}

View File

@ -9,15 +9,36 @@ pub struct Recipe {
pub ingredients: Vec<RecipeIngredient>,
}
impl From<(&db::data::Recipe, Lang, Vec<Ingredient>)> for Recipe {
fn from((db, lang, ingredients): (&db::data::Recipe, Lang, Vec<Ingredient>)) -> Self {
impl TryFrom<(&db::data::Recipe, Lang, Vec<Ingredient>)> for Recipe {
type Error = ();
fn try_from(
(db, lang, ingredients): (&db::data::Recipe, Lang, Vec<Ingredient>),
) -> Result<Self, Self::Error> {
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::<Result<Vec<_>, _>>()?;
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<Ingredient>)> 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,
})
}
}

View File

@ -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<types::Recipe>;
}
@ -10,121 +14,100 @@ pub struct StaticRecipeRepo;
impl RecipeRepo for StaticRecipeRepo {
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 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<db::data::Recipe>,
pub recipes: Vec<db::data::Recipe>,
}
#[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<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 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()
}
}