api: filter recipes if ingredient not exist in db
This commit is contained in:
parent
01216f2bbe
commit
c19407940b
|
@ -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!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue