From 8c367615a1c00b1b3147576c70ec80090e7ce6bd Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Thu, 12 May 2022 00:01:34 +0300 Subject: [PATCH] api: filter ingredients by lang --- api/src/domain/ingredient/fetch_list.rs | 4 +- api/src/domain/ingredient/types.rs | 14 ++++ api/src/repo/ingredient.rs | 2 +- api/src/server.rs | 97 +++++++++++++++++++++++-- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/api/src/domain/ingredient/fetch_list.rs b/api/src/domain/ingredient/fetch_list.rs index b899f56..860f889 100644 --- a/api/src/domain/ingredient/fetch_list.rs +++ b/api/src/domain/ingredient/fetch_list.rs @@ -1,9 +1,9 @@ use super::types; use crate::repo::ingredient::{GetIngredientOpts, IngredientRepo}; -#[derive(Default)] +#[derive(Default, Debug)] pub struct RequestOpts { - lang: Option, + pub lang: Option, } pub fn execute(repo: &impl IngredientRepo, opts: RequestOpts) -> Vec { diff --git a/api/src/domain/ingredient/types.rs b/api/src/domain/ingredient/types.rs index 1263bdf..7552aee 100644 --- a/api/src/domain/ingredient/types.rs +++ b/api/src/domain/ingredient/types.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)] pub enum Lang { Rus, @@ -11,6 +13,18 @@ impl Default for Lang { } } +impl FromStr for Lang { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "Rus" | "rus" => Ok(Lang::Rus), + "Eng" | "eng" => Ok(Lang::Eng), + _ => Err(()), + } + } +} + #[derive(Debug, Clone, Serialize)] pub struct Ingredient { pub key: String, diff --git a/api/src/repo/ingredient.rs b/api/src/repo/ingredient.rs index 1224b12..acf391d 100644 --- a/api/src/repo/ingredient.rs +++ b/api/src/repo/ingredient.rs @@ -9,7 +9,7 @@ pub trait IngredientRepo { fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec; } -pub struct StaticIngredientRepo {} +pub struct StaticIngredientRepo; impl IngredientRepo for StaticIngredientRepo { fn get_ingredients(&self, opts: GetIngredientOpts) -> Vec { diff --git a/api/src/server.rs b/api/src/server.rs index 34d30c7..8d4c168 100644 --- a/api/src/server.rs +++ b/api/src/server.rs @@ -1,12 +1,15 @@ +use std::str::FromStr; use std::sync::Arc; use std::thread; use crate::domain; +use crate::domain::ingredient::types::Lang; use crate::repo::ingredient::StaticIngredientRepo; pub fn start() { - let server = Arc::new(tiny_http::Server::http("0.0.0.0:33333").unwrap()); - println!("Server listening on port 33333"); + use tiny_http::{Header, Response, Server}; + let server = Arc::new(Server::http("0.0.0.0:33333").unwrap()); + println!("Server listening on http://localhost:33333"); let mut handles = Vec::with_capacity(4); @@ -15,13 +18,23 @@ pub fn start() { handles.push(thread::spawn(move || { for rq in server.incoming_requests() { - use domain::ingredient::fetch_list; - let repo = StaticIngredientRepo {}; - let ingredients = fetch_list::execute(&repo, fetch_list::RequestOpts::default()); - let data = serde_json::to_string(&ingredients).unwrap(); + let url = Url::parse(rq.url()); + let _ = match url.path_segments()[..] { + ["ingredients"] => { + use domain::ingredient::fetch_list; + let opts = FetchIngredientsOpts::from(url.query_params()); - let response = tiny_http::Response::from_string(data); - let _ = rq.respond(response); + let repo = StaticIngredientRepo; + let ingredients = fetch_list::execute(&repo, opts.into()); + let data = serde_json::to_string(&ingredients).unwrap(); + + let response = Response::from_string(data).with_header( + Header::from_str("content-type: application/json").unwrap(), + ); + rq.respond(response) + } + _ => rq.respond(Response::from_string("Not found")), + }; } })); } @@ -30,3 +43,71 @@ pub fn start() { h.join().unwrap(); } } + +#[derive(Default, Debug)] +struct FetchIngredientsOpts<'a> { + lang: Option<&'a str>, +} + +impl<'a> From> for FetchIngredientsOpts<'a> { + fn from(params: QueryParams<'a>) -> Self { + params + .into_iter() + .fold(FetchIngredientsOpts::default(), |mut opts, p| { + match p { + ("lang", val) => opts.lang = Some(val), + _ => {} + }; + + opts + }) + } +} + +impl From> for domain::ingredient::fetch_list::RequestOpts { + fn from(rest: FetchIngredientsOpts) -> Self { + let lang = rest.lang.and_then(|l| Lang::from_str(l).ok()); + Self { lang } + } +} + +#[derive(Debug)] +struct Url<'a> { + path: &'a str, + query: Option<&'a str>, +} + +type QueryParam<'a> = (&'a str, &'a str); + +type QueryParams<'a> = Vec>; + +impl Url<'_> { + pub fn parse(url: &str) -> Url { + let mut parts = url.splitn(2, '?'); + + let path = parts.next().unwrap_or_default(); + let query = parts.next(); + + Url { path, query } + } + + pub fn path_segments(&self) -> Vec<&str> { + self.path.split('/').skip(1).collect() + } + + pub fn query_params(&self) -> Vec { + self.query + .map(|q| { + q.split('&') + .filter_map(|part| { + if let [key, val] = part.splitn(2, '=').collect::>()[..] { + Some((key, val)) + } else { + None + } + }) + .collect() + }) + .unwrap_or_default() + } +}