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() { 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); for _ in 0..4 { let server = server.clone(); handles.push(thread::spawn(move || { for rq in server.incoming_requests() { 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 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")), }; } })); } for h in handles { h.join().unwrap(); } } #[derive(Default, Debug)] struct FetchIngredientsOpts<'a> { lang: Option<&'a str>, keys: Option>, } 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), ("key", val) => { let mut keys = opts.keys.unwrap_or_default(); keys.push(val); opts.keys = Some(keys) } _ => {} }; 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()); let keys = rest .keys .map(|keys| keys.into_iter().map(String::from).collect()); Self { lang, keys } } } #[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() } }