From 661b1786124779ec4a856c7e7e74d0721c455dfa Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Tue, 10 May 2022 00:56:08 +0300 Subject: [PATCH] api: filter ingredients by lang --- api/src/domain/ingredient/fetch_list.rs | 61 ++++++++++++++++++++----- api/src/domain/ingredient/types.rs | 29 +++++++++--- api/src/repo/ingredient.rs | 36 ++++++++++++--- api/src/server.rs | 3 +- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/api/src/domain/ingredient/fetch_list.rs b/api/src/domain/ingredient/fetch_list.rs index 816aba6..b899f56 100644 --- a/api/src/domain/ingredient/fetch_list.rs +++ b/api/src/domain/ingredient/fetch_list.rs @@ -1,8 +1,13 @@ use super::types; -use crate::repo::ingredient::IngredientRepo; +use crate::repo::ingredient::{GetIngredientOpts, IngredientRepo}; -pub fn execute(repo: &impl IngredientRepo) -> Vec { - repo.get_ingredients() +#[derive(Default)] +pub struct RequestOpts { + lang: Option, +} + +pub fn execute(repo: &impl IngredientRepo, opts: RequestOpts) -> Vec { + repo.get_ingredients(GetIngredientOpts { lang: opts.lang }) } #[cfg(test)] @@ -13,16 +18,50 @@ mod tests { #[test] fn should_return_all_ingredients() { let repo = crate::repo::ingredient::InMemoryIngredientRepo::new(); - let res = execute(&repo); + let res = execute(&repo, RequestOpts::default()); match res.as_slice() { - [first, second] => { - assert_eq!(first.key, String::from("apple")); - assert_eq!(first.lang, Lang::Rus); - assert_eq!(first.name, String::from("Яблоко")); - assert_eq!(second.key, String::from("salt")); - assert_eq!(second.lang, Lang::Rus); - assert_eq!(second.name, String::from("Соль")); + [apple, orange, salt, sugar] => { + assert_eq!(apple.key, String::from("apple")); + assert_eq!(apple.lang, Lang::Rus); + assert_eq!(apple.name, String::from("Яблоко")); + + assert_eq!(orange.key, String::from("orange")); + assert_eq!(orange.lang, Lang::Rus); + assert_eq!(orange.name, String::from("Апельсин")); + + assert_eq!(salt.key, String::from("salt")); + assert_eq!(salt.lang, Lang::Rus); + assert_eq!(salt.name, String::from("Соль")); + + assert_eq!(sugar.key, String::from("sugar")); + assert_eq!(sugar.lang, Lang::Rus); + assert_eq!(sugar.name, String::from("Сахар")); + } + _ => unimplemented!(), + } + } + + #[test] + fn should_return_filtered_by_lang() { + let repo = crate::repo::ingredient::InMemoryIngredientRepo::new(); + let res = execute( + &repo, + RequestOpts { + lang: Some(Lang::Eng), + ..RequestOpts::default() + }, + ); + + match res.as_slice() { + [apple, salt] => { + assert_eq!(apple.key, String::from("apple")); + assert_eq!(apple.lang, Lang::Eng); + assert_eq!(apple.name, String::from("Apple")); + + assert_eq!(salt.key, String::from("salt")); + assert_eq!(salt.lang, Lang::Eng); + assert_eq!(salt.name, String::from("Salt")); } _ => unimplemented!(), } diff --git a/api/src/domain/ingredient/types.rs b/api/src/domain/ingredient/types.rs index 73fe242..1263bdf 100644 --- a/api/src/domain/ingredient/types.rs +++ b/api/src/domain/ingredient/types.rs @@ -5,6 +5,12 @@ pub enum Lang { Eng, } +impl Default for Lang { + fn default() -> Self { + Lang::Rus + } +} + #[derive(Debug, Clone, Serialize)] pub struct Ingredient { pub key: String, @@ -14,19 +20,30 @@ pub struct Ingredient { impl From<&db::data::Ingredient> for Ingredient { fn from(db: &db::data::Ingredient) -> Self { - Self::from((db, Lang::Rus)) + Self::try_from((db, Lang::Rus)).unwrap() } } -impl From<(&db::data::Ingredient, Lang)> for Ingredient { - fn from((db, lang): (&db::data::Ingredient, Lang)) -> Self { +#[derive(Debug)] +pub enum IngredientValidError { + TranslationNotFound, +} + +impl TryFrom<(&db::data::Ingredient, Lang)> for Ingredient { + type Error = IngredientValidError; + + fn try_from((db, lang): (&db::data::Ingredient, Lang)) -> Result { let key = db.key.to_string(); let name = match lang { - Lang::Rus => db.translates.ru.to_string(), - Lang::Eng => db.translates.en.unwrap().to_string(), + Lang::Rus => Some(db.translates.ru), + Lang::Eng => db.translates.en, }; - Self { key, lang, name } + if let Some(name) = name.map(String::from) { + Ok(Self { key, lang, name }) + } else { + Err(IngredientValidError::TranslationNotFound) + } } } diff --git a/api/src/repo/ingredient.rs b/api/src/repo/ingredient.rs index 6ed7c05..1224b12 100644 --- a/api/src/repo/ingredient.rs +++ b/api/src/repo/ingredient.rs @@ -1,14 +1,24 @@ use crate::domain::ingredient::types; +#[derive(Default)] +pub struct GetIngredientOpts { + pub lang: Option, +} + pub trait IngredientRepo { - fn get_ingredients(&self) -> Vec; + fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec; } pub struct StaticIngredientRepo {} impl IngredientRepo for StaticIngredientRepo { - fn get_ingredients(&self) -> Vec { - db::INGREDIENTS.iter().map(From::from).collect::>() + fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec { + let langs = [opts.lang.unwrap_or_default()].repeat(db::INGREDIENTS.len()); + db::INGREDIENTS + .iter() + .zip(langs) + .filter_map(|tup| TryFrom::try_from(tup).ok()) + .collect::>() } } @@ -29,6 +39,13 @@ impl InMemoryIngredientRepo { en: Some("Apple"), }, }, + db::data::Ingredient { + key: "orange", + translates: db::data::IngredientTranslate { + ru: "Апельсин", + en: None, + }, + }, db::data::Ingredient { key: "salt", translates: db::data::IngredientTranslate { @@ -36,6 +53,13 @@ impl InMemoryIngredientRepo { en: Some("Salt"), }, }, + db::data::Ingredient { + key: "sugar", + translates: db::data::IngredientTranslate { + ru: "Сахар", + en: None, + }, + }, ], } } @@ -43,12 +67,12 @@ impl InMemoryIngredientRepo { #[cfg(test)] impl IngredientRepo for InMemoryIngredientRepo { - fn get_ingredients(&self) -> Vec { - let langs = [types::Lang::Rus].repeat(self.ingredients.len()); + fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec { + let langs = [opts.lang.unwrap_or_default()].repeat(self.ingredients.len()); self.ingredients .iter() .zip(langs) - .map(From::from) + .filter_map(|tup| TryFrom::try_from(tup).ok()) .collect::>() } } diff --git a/api/src/server.rs b/api/src/server.rs index 89f8584..34d30c7 100644 --- a/api/src/server.rs +++ b/api/src/server.rs @@ -15,8 +15,9 @@ pub fn start() { handles.push(thread::spawn(move || { for rq in server.incoming_requests() { + use domain::ingredient::fetch_list; let repo = StaticIngredientRepo {}; - let ingredients = domain::ingredient::fetch_list::execute(&repo); + let ingredients = fetch_list::execute(&repo, fetch_list::RequestOpts::default()); let data = serde_json::to_string(&ingredients).unwrap(); let response = tiny_http::Response::from_string(data);