api: fetch ingredient by key
This commit is contained in:
parent
ad2c142203
commit
1bc6689104
|
@ -0,0 +1,96 @@
|
||||||
|
use super::types;
|
||||||
|
use crate::repo::ingredient::IngredientRepo;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct RequestOpts {
|
||||||
|
pub lang: Option<types::Lang>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ResponseError {
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(
|
||||||
|
repo: &impl IngredientRepo,
|
||||||
|
key: String,
|
||||||
|
opts: RequestOpts,
|
||||||
|
) -> Result<types::Ingredient, ResponseError> {
|
||||||
|
match repo.get_ingredient_opt(key, opts.into()) {
|
||||||
|
Some(ing) => Ok(ing),
|
||||||
|
_ => Err(ResponseError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::domain::ingredient::types::Lang;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_ingredient() {
|
||||||
|
let repo = crate::repo::ingredient::InMemoryIngredientRepo::new();
|
||||||
|
let res = execute(&repo, String::from("apple"), RequestOpts::default());
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(apple) => {
|
||||||
|
assert_eq!(apple.key, String::from("apple"));
|
||||||
|
assert_eq!(apple.lang, Lang::Rus);
|
||||||
|
assert_eq!(apple.name, String::from("Яблоко"));
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_ingredient_with_choosed_lang() {
|
||||||
|
let repo = crate::repo::ingredient::InMemoryIngredientRepo::new();
|
||||||
|
let res = execute(
|
||||||
|
&repo,
|
||||||
|
String::from("apple"),
|
||||||
|
RequestOpts {
|
||||||
|
lang: Some(Lang::Eng),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(apple) => {
|
||||||
|
assert_eq!(apple.key, String::from("apple"));
|
||||||
|
assert_eq!(apple.lang, Lang::Eng);
|
||||||
|
assert_eq!(apple.name, String::from("Apple"));
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_ingredient_with_fallback_lang() {
|
||||||
|
let repo = crate::repo::ingredient::InMemoryIngredientRepo::new();
|
||||||
|
let res = execute(
|
||||||
|
&repo,
|
||||||
|
String::from("orange"),
|
||||||
|
RequestOpts {
|
||||||
|
lang: Some(Lang::Eng),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(orange) => {
|
||||||
|
assert_eq!(orange.key, String::from("orange"));
|
||||||
|
assert_eq!(orange.lang, Lang::Rus);
|
||||||
|
assert_eq!(orange.name, String::from("Апельсин"));
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_not_found_error() {
|
||||||
|
let repo = crate::repo::ingredient::InMemoryIngredientRepo::new();
|
||||||
|
let res = execute(&repo, String::from("wildberries"), RequestOpts::default());
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(ResponseError::NotFound) => {}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod fetch_by_key;
|
||||||
pub mod fetch_list;
|
pub mod fetch_list;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
use crate::domain::ingredient::{fetch_list, types};
|
use crate::domain::ingredient::{fetch_by_key, fetch_list, types};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GetIngredientOpts {
|
pub struct GetIngredientOpts {
|
||||||
pub lang: Option<types::Lang>,
|
pub lang: Option<types::Lang>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fetch_by_key::RequestOpts> for GetIngredientOpts {
|
||||||
|
fn from(app: fetch_by_key::RequestOpts) -> Self {
|
||||||
|
Self { lang: app.lang }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GetIngredientsOpts {
|
||||||
|
pub lang: Option<types::Lang>,
|
||||||
pub keys: Option<Vec<String>>,
|
pub keys: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<fetch_list::RequestOpts> for GetIngredientOpts {
|
impl From<fetch_list::RequestOpts> for GetIngredientsOpts {
|
||||||
fn from(app: fetch_list::RequestOpts) -> Self {
|
fn from(app: fetch_list::RequestOpts) -> Self {
|
||||||
Self {
|
Self {
|
||||||
lang: app.lang,
|
lang: app.lang,
|
||||||
|
@ -16,13 +27,27 @@ impl From<fetch_list::RequestOpts> for GetIngredientOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IngredientRepo {
|
pub trait IngredientRepo {
|
||||||
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient>;
|
fn get_ingredient_opt(&self, key: String, opts: GetIngredientOpts)
|
||||||
|
-> Option<types::Ingredient>;
|
||||||
|
|
||||||
|
fn get_ingredients(&self, opts: GetIngredientsOpts) -> Vec<types::Ingredient>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StaticIngredientRepo;
|
pub struct StaticIngredientRepo;
|
||||||
|
|
||||||
impl IngredientRepo for StaticIngredientRepo {
|
impl IngredientRepo for StaticIngredientRepo {
|
||||||
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
|
fn get_ingredient_opt(
|
||||||
|
&self,
|
||||||
|
key: String,
|
||||||
|
opts: GetIngredientOpts,
|
||||||
|
) -> Option<types::Ingredient> {
|
||||||
|
db::INGREDIENTS
|
||||||
|
.iter()
|
||||||
|
.find(|ing| ing.key == &key)
|
||||||
|
.map(|ing| types::Ingredient::from((ing, opts.lang.unwrap_or_default())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ingredients(&self, opts: GetIngredientsOpts) -> Vec<types::Ingredient> {
|
||||||
let langs = [opts.lang.unwrap_or_default()].repeat(db::INGREDIENTS.len());
|
let langs = [opts.lang.unwrap_or_default()].repeat(db::INGREDIENTS.len());
|
||||||
db::INGREDIENTS
|
db::INGREDIENTS
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -78,7 +103,18 @@ impl InMemoryIngredientRepo {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl IngredientRepo for InMemoryIngredientRepo {
|
impl IngredientRepo for InMemoryIngredientRepo {
|
||||||
fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec<types::Ingredient> {
|
fn get_ingredient_opt(
|
||||||
|
&self,
|
||||||
|
key: String,
|
||||||
|
opts: GetIngredientOpts,
|
||||||
|
) -> Option<types::Ingredient> {
|
||||||
|
self.ingredients
|
||||||
|
.iter()
|
||||||
|
.find(|ing| ing.key == &key)
|
||||||
|
.map(|ing| types::Ingredient::from((ing, opts.lang.unwrap_or_default())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ingredients(&self, opts: GetIngredientsOpts) -> Vec<types::Ingredient> {
|
||||||
let langs = [opts.lang.unwrap_or_default()].repeat(self.ingredients.len());
|
let langs = [opts.lang.unwrap_or_default()].repeat(self.ingredients.len());
|
||||||
self.ingredients
|
self.ingredients
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -55,3 +55,44 @@ pub fn fetch_list(url: &Url) -> Response<Cursor<Vec<u8>>> {
|
||||||
Response::from_string(data)
|
Response::from_string(data)
|
||||||
.with_header(Header::from_str("content-type: application/json").unwrap())
|
.with_header(Header::from_str("content-type: application/json").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct FetchIngredientOpts<'a> {
|
||||||
|
lang: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<QueryParams<'a>> for FetchIngredientOpts<'a> {
|
||||||
|
fn from(params: QueryParams<'a>) -> Self {
|
||||||
|
params
|
||||||
|
.into_iter()
|
||||||
|
.fold(FetchIngredientOpts::default(), |mut opts, p| {
|
||||||
|
match p {
|
||||||
|
("lang", val) => opts.lang = Some(val),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
opts
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FetchIngredientOpts<'_>> for domain::ingredient::fetch_by_key::RequestOpts {
|
||||||
|
fn from(rest: FetchIngredientOpts) -> Self {
|
||||||
|
let lang = rest.lang.and_then(|l| Lang::from_str(l).ok());
|
||||||
|
Self { lang }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_by_key(url: &Url, key: &str) -> Response<Cursor<Vec<u8>>> {
|
||||||
|
use domain::ingredient::fetch_by_key;
|
||||||
|
let opts = FetchIngredientOpts::from(url.query_params());
|
||||||
|
|
||||||
|
let repo = StaticIngredientRepo;
|
||||||
|
|
||||||
|
// TODO: catch notfound error
|
||||||
|
let ingredient = fetch_by_key::execute(&repo, key.to_string(), opts.into()).ok();
|
||||||
|
let data = serde_json::to_string(&ingredient).unwrap();
|
||||||
|
|
||||||
|
Response::from_string(data)
|
||||||
|
.with_header(Header::from_str("content-type: application/json").unwrap())
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ pub fn start() {
|
||||||
let res = rest::ctrl::ingredient::fetch_list(&url);
|
let res = rest::ctrl::ingredient::fetch_list(&url);
|
||||||
rq.respond(res)
|
rq.respond(res)
|
||||||
}
|
}
|
||||||
|
["api", "ingredients", key] => {
|
||||||
|
let res = rest::ctrl::ingredient::fetch_by_key(&url, key);
|
||||||
|
rq.respond(res)
|
||||||
|
}
|
||||||
_ => rq.respond(Response::from_string("Not found")),
|
_ => rq.respond(Response::from_string("Not found")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue