api: filter ingredients by lang

This commit is contained in:
Dmitriy Pleshevskiy 2022-05-10 00:56:08 +03:00
parent 830ea2b9de
commit 661b178612
4 changed files with 105 additions and 24 deletions

View File

@ -1,8 +1,13 @@
use super::types; use super::types;
use crate::repo::ingredient::IngredientRepo; use crate::repo::ingredient::{GetIngredientOpts, IngredientRepo};
pub fn execute(repo: &impl IngredientRepo) -> Vec<types::Ingredient> { #[derive(Default)]
repo.get_ingredients() pub struct RequestOpts {
lang: Option<types::Lang>,
}
pub fn execute(repo: &impl IngredientRepo, opts: RequestOpts) -> Vec<types::Ingredient> {
repo.get_ingredients(GetIngredientOpts { lang: opts.lang })
} }
#[cfg(test)] #[cfg(test)]
@ -13,16 +18,50 @@ mod tests {
#[test] #[test]
fn should_return_all_ingredients() { fn should_return_all_ingredients() {
let repo = crate::repo::ingredient::InMemoryIngredientRepo::new(); let repo = crate::repo::ingredient::InMemoryIngredientRepo::new();
let res = execute(&repo); let res = execute(&repo, RequestOpts::default());
match res.as_slice() { match res.as_slice() {
[first, second] => { [apple, orange, salt, sugar] => {
assert_eq!(first.key, String::from("apple")); assert_eq!(apple.key, String::from("apple"));
assert_eq!(first.lang, Lang::Rus); assert_eq!(apple.lang, Lang::Rus);
assert_eq!(first.name, String::from("Яблоко")); assert_eq!(apple.name, String::from("Яблоко"));
assert_eq!(second.key, String::from("salt"));
assert_eq!(second.lang, Lang::Rus); assert_eq!(orange.key, String::from("orange"));
assert_eq!(second.name, String::from("Соль")); 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!(), _ => unimplemented!(),
} }

View File

@ -5,6 +5,12 @@ pub enum Lang {
Eng, Eng,
} }
impl Default for Lang {
fn default() -> Self {
Lang::Rus
}
}
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Ingredient { pub struct Ingredient {
pub key: String, pub key: String,
@ -14,19 +20,30 @@ pub struct Ingredient {
impl From<&db::data::Ingredient> for Ingredient { impl From<&db::data::Ingredient> for Ingredient {
fn from(db: &db::data::Ingredient) -> Self { 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 { #[derive(Debug)]
fn from((db, lang): (&db::data::Ingredient, Lang)) -> Self { 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<Self, Self::Error> {
let key = db.key.to_string(); let key = db.key.to_string();
let name = match lang { let name = match lang {
Lang::Rus => db.translates.ru.to_string(), Lang::Rus => Some(db.translates.ru),
Lang::Eng => db.translates.en.unwrap().to_string(), 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)
}
} }
} }

View File

@ -1,14 +1,24 @@
use crate::domain::ingredient::types; use crate::domain::ingredient::types;
#[derive(Default)]
pub struct GetIngredientOpts {
pub lang: Option<types::Lang>,
}
pub trait IngredientRepo { pub trait IngredientRepo {
fn get_ingredients(&self) -> Vec<types::Ingredient>; fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient>;
} }
pub struct StaticIngredientRepo {} pub struct StaticIngredientRepo {}
impl IngredientRepo for StaticIngredientRepo { impl IngredientRepo for StaticIngredientRepo {
fn get_ingredients(&self) -> Vec<types::Ingredient> { fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
db::INGREDIENTS.iter().map(From::from).collect::<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::<Vec<_>>()
} }
} }
@ -29,6 +39,13 @@ impl InMemoryIngredientRepo {
en: Some("Apple"), en: Some("Apple"),
}, },
}, },
db::data::Ingredient {
key: "orange",
translates: db::data::IngredientTranslate {
ru: "Апельсин",
en: None,
},
},
db::data::Ingredient { db::data::Ingredient {
key: "salt", key: "salt",
translates: db::data::IngredientTranslate { translates: db::data::IngredientTranslate {
@ -36,6 +53,13 @@ impl InMemoryIngredientRepo {
en: Some("Salt"), en: Some("Salt"),
}, },
}, },
db::data::Ingredient {
key: "sugar",
translates: db::data::IngredientTranslate {
ru: "Сахар",
en: None,
},
},
], ],
} }
} }
@ -43,12 +67,12 @@ impl InMemoryIngredientRepo {
#[cfg(test)] #[cfg(test)]
impl IngredientRepo for InMemoryIngredientRepo { impl IngredientRepo for InMemoryIngredientRepo {
fn get_ingredients(&self) -> Vec<types::Ingredient> { fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
let langs = [types::Lang::Rus].repeat(self.ingredients.len()); let langs = [opts.lang.unwrap_or_default()].repeat(self.ingredients.len());
self.ingredients self.ingredients
.iter() .iter()
.zip(langs) .zip(langs)
.map(From::from) .filter_map(|tup| TryFrom::try_from(tup).ok())
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
} }

View File

@ -15,8 +15,9 @@ pub fn start() {
handles.push(thread::spawn(move || { handles.push(thread::spawn(move || {
for rq in server.incoming_requests() { for rq in server.incoming_requests() {
use domain::ingredient::fetch_list;
let repo = StaticIngredientRepo {}; 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 data = serde_json::to_string(&ingredients).unwrap();
let response = tiny_http::Response::from_string(data); let response = tiny_http::Response::from_string(data);