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 crate::repo::ingredient::IngredientRepo;
use crate::repo::ingredient::{GetIngredientOpts, IngredientRepo};
pub fn execute(repo: &impl IngredientRepo) -> Vec<types::Ingredient> {
repo.get_ingredients()
#[derive(Default)]
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)]
@ -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!(),
}

View File

@ -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<Self, Self::Error> {
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)
}
}
}

View File

@ -1,14 +1,24 @@
use crate::domain::ingredient::types;
#[derive(Default)]
pub struct GetIngredientOpts {
pub lang: Option<types::Lang>,
}
pub trait IngredientRepo {
fn get_ingredients(&self) -> Vec<types::Ingredient>;
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient>;
}
pub struct StaticIngredientRepo {}
impl IngredientRepo for StaticIngredientRepo {
fn get_ingredients(&self) -> Vec<types::Ingredient> {
db::INGREDIENTS.iter().map(From::from).collect::<Vec<_>>()
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
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"),
},
},
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<types::Ingredient> {
let langs = [types::Lang::Rus].repeat(self.ingredients.len());
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
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::<Vec<_>>()
}
}

View File

@ -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);