refac(rest): move server and resolvers to rest layer

This commit is contained in:
Dmitriy Pleshevskiy 2022-05-12 22:32:51 +03:00
parent 4626157319
commit e976651169
7 changed files with 137 additions and 124 deletions

View File

@ -5,8 +5,8 @@ extern crate serde;
mod domain;
mod repo;
mod server;
mod rest;
fn main() {
server::start();
rest::server::start();
}

View File

@ -0,0 +1,57 @@
use std::io::Cursor;
use std::str::FromStr;
use tiny_http::{Header, Response};
use crate::domain;
use crate::domain::ingredient::types::Lang;
use crate::repo::ingredient::StaticIngredientRepo;
use crate::rest::types::{QueryParams, Url};
#[derive(Default, Debug)]
struct FetchIngredientsOpts<'a> {
lang: Option<&'a str>,
keys: Option<Vec<&'a str>>,
}
impl<'a> From<QueryParams<'a>> 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<FetchIngredientsOpts<'_>> 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 }
}
}
pub fn fetch_list(url: &Url) -> Response<Cursor<Vec<u8>>> {
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();
Response::from_string(data)
.with_header(Header::from_str("content-type: application/json").unwrap())
}

1
api/src/rest/ctrl/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod ingredient;

3
api/src/rest/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod ctrl;
pub mod server;
pub mod types;

34
api/src/rest/server.rs Normal file
View File

@ -0,0 +1,34 @@
use std::sync::Arc;
use std::thread;
use tiny_http::{Response, Server};
use crate::rest;
use crate::rest::types::Url;
pub fn start() {
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()[..] {
["api", "ingredients"] => {
let res = rest::ctrl::ingredient::fetch_list(&url);
rq.respond(res)
}
_ => rq.respond(Response::from_string("Not found")),
};
}
}));
}
for h in handles {
h.join().unwrap();
}
}

40
api/src/rest/types.rs Normal file
View File

@ -0,0 +1,40 @@
#[derive(Debug)]
pub struct Url<'a> {
path: &'a str,
query: Option<&'a str>,
}
pub type QueryParam<'a> = (&'a str, &'a str);
pub type QueryParams<'a> = Vec<QueryParam<'a>>;
impl Url<'_> {
pub fn parse(url: &str) -> Url {
let mut parts = url.splitn(2, '?');
let path = parts.next().unwrap_or_default().trim_matches('/');
let query = parts.next();
Url { path, query }
}
pub fn path_segments(&self) -> Vec<&str> {
self.path.split('/').collect()
}
pub fn query_params(&self) -> Vec<QueryParam> {
self.query
.map(|q| {
q.split('&')
.filter_map(|part| {
if let [key, val] = part.splitn(2, '=').collect::<Vec<&str>>()[..] {
Some((key, val))
} else {
None
}
})
.collect()
})
.unwrap_or_default()
}
}

View File

@ -1,122 +0,0 @@
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<Vec<&'a str>>,
}
impl<'a> From<QueryParams<'a>> 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<FetchIngredientsOpts<'_>> 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<QueryParam<'a>>;
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<QueryParam> {
self.query
.map(|q| {
q.split('&')
.filter_map(|part| {
if let [key, val] = part.splitn(2, '=').collect::<Vec<&str>>()[..] {
Some((key, val))
} else {
None
}
})
.collect()
})
.unwrap_or_default()
}
}