From 14234590ee7ecaa30c4a5a65564bd73c52e217e2 Mon Sep 17 00:00:00 2001 From: serayuzgur Date: Thu, 20 Apr 2017 20:06:40 +0300 Subject: [PATCH] Query parsing and tests #11 #14 --- src/configuration.rs | 2 - src/main.rs | 31 ++----- src/server.rs | 4 +- src/{rest_service.rs => service/mod.rs} | 114 +++++++----------------- src/service/query.rs | 72 +++++++++++++++ src/service/utils.rs | 70 +++++++++++++++ src/weld.rs | 16 ++++ tests/main.rs | 13 --- 8 files changed, 199 insertions(+), 123 deletions(-) rename src/{rest_service.rs => service/mod.rs} (55%) create mode 100644 src/service/query.rs create mode 100644 src/service/utils.rs create mode 100644 src/weld.rs delete mode 100644 tests/main.rs diff --git a/src/configuration.rs b/src/configuration.rs index ffd4296..426daaf 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -3,8 +3,6 @@ extern crate serde_json; use std::fs::File; use std::io::prelude::*; use weld; -use std::path::Path; - #[derive(Serialize, Deserialize)] #[derive(Debug,Clone)] diff --git a/src/main.rs b/src/main.rs index 1e650b6..cfec4ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ extern crate futures_cpupool; #[macro_use] extern crate serde_derive; -extern crate serde_json; +extern crate serde_json; #[macro_use] extern crate slog; @@ -22,43 +22,26 @@ extern crate slog_async; extern crate lazy_static; extern crate time; -mod rest_service; +mod service; mod server; mod configuration; mod database; +mod weld; + use server::Server; use configuration::Configuration; use std::env::args; -/// Holds the shared variables of the application. -//TODO: Is is the right way? -pub mod weld { - //TODO: take this to a seperate file later. - use slog; - use slog_term; - use slog_async; - use slog::Drain; - use std::sync::Arc; - use configuration::Configuration; - use configuration; - use database::Database; - use std::sync::Mutex; - lazy_static! { - pub static ref ROOT_LOGGER: slog::Logger = slog::Logger::root(Arc::new(slog_async::Async::new(slog_term::CompactFormat::new(slog_term::TermDecorator::new().build()).build().fuse()).build().fuse()), o!()); - pub static ref CONFIGURATION : Mutex = Mutex::new(Configuration::new("")); - pub static ref DATABASE : Mutex = Mutex::new(Database::new(&configuration::Database{path:"".to_string()})); - } -} fn main() { info!(weld::ROOT_LOGGER, "Application started";"started_at" => format!("{}", time::now().rfc3339()), "version" => env!("CARGO_PKG_VERSION")); - let mut configuration = weld::CONFIGURATION.lock().unwrap(); + let mut configuration = weld::CONFIGURATION.lock().unwrap(); match args().nth(1) { Some(path) => configuration.load(path.as_str()), None => { - info!(weld::ROOT_LOGGER,"Program arguments not found."); + info!(weld::ROOT_LOGGER, "Program arguments not found."); configuration.load("weld.json"); } } @@ -67,7 +50,7 @@ fn main() { Server::new(&configuration.server).start(); } -fn load_db(configuration: &Configuration){ +fn load_db(configuration: &Configuration) { let mut database = weld::DATABASE.lock().unwrap(); database.set_configuration(&configuration.database); database.open(); diff --git a/src/server.rs b/src/server.rs index 38dea53..ca0b8ed 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,5 @@ use configuration; -use rest_service; +use service; use slog; use weld; use hyper::server::Http; @@ -26,7 +26,7 @@ impl<'a> Server<'a> { Http::new() .bind(&endpoint, move || { - Ok(rest_service::RestService { + Ok(service::RestService { logger: weld::ROOT_LOGGER.new(o!()), thread_pool: thread_pool.clone(), }) diff --git a/src/rest_service.rs b/src/service/mod.rs similarity index 55% rename from src/rest_service.rs rename to src/service/mod.rs index a11c848..280170e 100644 --- a/src/rest_service.rs +++ b/src/service/mod.rs @@ -1,86 +1,36 @@ -extern crate serde_json; -extern crate futures; -extern crate futures_cpupool; - -use std::vec::Vec; +pub mod utils; +pub mod query; use weld; use slog; use hyper::{Get, Post, Put, Delete, StatusCode}; use hyper::server::{Service, Request, Response}; use hyper; -use futures::Stream; -use futures::Future; -use futures::future::ok; -use hyper::header::ContentType; - +use futures::{Stream, Future, BoxFuture}; +use futures_cpupool::CpuPool; +use serde_json; use database::Errors::{NotFound, BadData, Duplicate}; pub struct RestService { pub logger: slog::Logger, - pub thread_pool: futures_cpupool::CpuPool, + pub thread_pool: CpuPool, } impl RestService { - /// Prepares an error response , logs it, wraps to BoxFuture. - pub fn error(response: Response, - code: StatusCode, - message: &str) - -> futures::BoxFuture { - // error!(self.logger, "{}",&message); - - return ok(response.with_header(ContentType::plaintext()) - .with_status(code) - .with_body(message.to_string())) - .boxed(); - } - - /// Prepares an success response, wraps to BoxFuture. - pub fn success(response: Response, - code: StatusCode, - value: &serde_json::Value) - -> futures::BoxFuture { - return ok(response.with_header(ContentType::json()) - .with_status(code) - .with_body(serde_json::to_vec(&value).unwrap())) - .boxed(); - } - - /// Splits '/' and filters empty strings - fn split_path(path: String) -> Vec { - path.split("/").filter(|x| !x.is_empty()).map(String::from).collect::>() - } - - /// Helps to decide id value. - fn decide_id(part: Option<&String>) -> Result { - match part { - Some(val) => { - if !val.is_empty() { - match i64::from_str_radix(val, 10) { - Ok(parsed) => Ok(parsed), - Err(e) => return Err(format!("Non parsable id Error: {}", e)), - } - } else { - Ok(-1) - } - } - None => Ok(-1), - } - } - + #[inline] /// Gets records or spesific record from db and returns as a result. fn get(table: String, id: i64, response: Response) - -> futures::BoxFuture { + -> BoxFuture { let mut db = weld::DATABASE.lock().unwrap(); match db.read(table.as_str(), &id) { - Ok(record) => return Self::success(response, StatusCode::Ok, &record), + Ok(record) => return utils::success(response, StatusCode::Ok, &record), Err(error) => { match error { - NotFound(msg) => Self::error(response, StatusCode::NotFound, msg.as_str()), - _ => Self::error(response, StatusCode::InternalServerError, "Server Error"), + NotFound(msg) => utils::error(response, StatusCode::NotFound, msg.as_str()), + _ => utils::error(response, StatusCode::InternalServerError, "Server Error"), } } } @@ -91,7 +41,7 @@ impl RestService { fn post(req: Request, table: String, response: Response) - -> futures::BoxFuture { + -> BoxFuture { req.body() .concat() .and_then(move |body| { @@ -101,18 +51,18 @@ impl RestService { match db.insert(table.as_str(), payload.as_object_mut().unwrap()) { Ok(record) => { db.flush(); - return Self::success(response, StatusCode::Created, &record); + return utils::success(response, StatusCode::Created, &record); } Err(error) => { match error { NotFound(msg) => { - Self::error(response, StatusCode::NotFound, msg.as_str()) + utils::error(response, StatusCode::NotFound, msg.as_str()) } Duplicate(msg) => { - Self::error(response, StatusCode::Conflict, msg.as_str()) + utils::error(response, StatusCode::Conflict, msg.as_str()) } _ => { - Self::error(response, + utils::error(response, StatusCode::InternalServerError, "Server Error") } @@ -130,7 +80,7 @@ impl RestService { table: String, id: i64, response: Response) - -> futures::BoxFuture { + -> BoxFuture { // TODO:: use path id when updating req.body() .concat() @@ -141,18 +91,18 @@ impl RestService { match db.update(table.as_str(), payload.as_object().unwrap().clone()) { Ok(record) => { db.flush(); - return Self::success(response, StatusCode::Ok, &record); + return utils::success(response, StatusCode::Ok, &record); } Err(error) => { match error { NotFound(msg) => { - Self::error(response, StatusCode::NotFound, msg.as_str()) + utils::error(response, StatusCode::NotFound, msg.as_str()) } BadData(msg) => { - Self::error(response, StatusCode::Conflict, msg.as_str()) + utils::error(response, StatusCode::Conflict, msg.as_str()) } _ => { - Self::error(response, + utils::error(response, StatusCode::InternalServerError, "Server Error") } @@ -168,19 +118,19 @@ impl RestService { fn delete(table: String, id: i64, response: Response) - -> futures::BoxFuture { + -> BoxFuture { let mut db = weld::DATABASE.lock().unwrap(); match db.delete(table.as_str(), &id) { Ok(record) => { db.flush(); - return Self::success(response, StatusCode::Ok, &record); + return utils::success(response, StatusCode::Ok, &record); } Err(error) => { match error { NotFound(msg) => { - return Self::error(response, StatusCode::NotFound, msg.as_str()); + return utils::error(response, StatusCode::NotFound, msg.as_str()); } - _ => Self::error(response, StatusCode::NotFound, "Server Error"), + _ => utils::error(response, StatusCode::NotFound, "Server Error"), } } } @@ -191,27 +141,27 @@ impl Service for RestService { type Request = Request; type Response = Response; type Error = hyper::Error; - type Future = futures::BoxFuture; + type Future = BoxFuture; fn call(&self, req: Request) -> Self::Future { - let parts = Self::split_path(req.path().to_string()); + let parts = utils::split_path(req.path().to_string()); let response = Response::new(); match parts.len() { // Table list 0 => { let db = weld::DATABASE.lock().unwrap(); - Self::success(response, + utils::success(response, StatusCode::Ok, &serde_json::to_value(&db.tables()).unwrap()) } 1 | 2 => { // Record list or record let table = parts.get(0).unwrap().clone(); - let id = match Self::decide_id(parts.get(1)) { + let id = match utils::decide_id(parts.get(1)) { Ok(result) => result, Err(e) => { - return Self::error(response, StatusCode::PreconditionFailed, e.as_str()); + return utils::error(response, StatusCode::PreconditionFailed, e.as_str()); } }; @@ -220,11 +170,11 @@ impl Service for RestService { &Post => Self::post(req, table, response), &Put => Self::put(req, table, id, response), &Delete => Self::delete(table, id, response), - _ => Self::error(response, StatusCode::MethodNotAllowed, "Method Not Allowed"), + _ => utils::error(response, StatusCode::MethodNotAllowed, "Method Not Allowed"), } } _ => { - return Self::error(response, + return utils::error(response, StatusCode::InternalServerError, "Nested structures are not implemented yet."); } diff --git a/src/service/query.rs b/src/service/query.rs new file mode 100644 index 0000000..6dbbe54 --- /dev/null +++ b/src/service/query.rs @@ -0,0 +1,72 @@ +use std::cmp::{PartialEq, Eq}; + +/// Splits query params +pub fn split_query(query: Option<&str>) -> Vec { + match query { + Some(params) => { + // params.split("&").filter(|x| !x.is_empty()).map(String::from).collect::>() + let mut queries = Vec::::new(); + for param in params.split("&") { + if param.is_empty() { + continue; + } + let parts = param.split("=").collect::>(); + if parts.get(0).is_none() || parts.get(1).is_none() { + continue; + } + let key = parts.get(0).unwrap().to_string(); + let value = parts.get(1).unwrap().to_string(); + let op = match key.split("_").collect::>().get(1) { + Some(v) => v.to_string(), + None => "=".to_string(), + }; + + queries.push(Query { + key: key, + value: value, + op: op, + }); + } + queries + } + None => Vec::::new(), + } +} + +#[derive(Eq)] +#[derive(Debug)] +pub struct Query { + pub key: String, + pub value: String, + pub op: String, +} +impl Query { + pub fn new(key: String, op: String, value: String) -> Query { + Query { + key: key, + op: op, + value: value, + } + } +} +impl PartialEq for Query { + #[inline] + fn eq(&self, other: &Query) -> bool { + self.key == other.key && self.value == other.value && self.op == other.op + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn split_query_test() { + assert_eq!(split_query(None), Vec::::new()); + assert_eq!(split_query(Some("")), Vec::::new()); + assert_eq!(split_query(Some("&&")), Vec::::new()); + assert_eq!(split_query(Some("a=1&b=2&c=3")), + vec![Query::new("a".to_string(), "=".to_string(), "1".to_string()), + Query::new("b".to_string(), "=".to_string(), "2".to_string()), + Query::new("c".to_string(), "=".to_string(), "3".to_string())]); + } +} \ No newline at end of file diff --git a/src/service/utils.rs b/src/service/utils.rs new file mode 100644 index 0000000..781e080 --- /dev/null +++ b/src/service/utils.rs @@ -0,0 +1,70 @@ +use hyper::StatusCode; +use hyper::server::Response; +use hyper; +use futures::{Future, BoxFuture}; +use futures::future::ok; +use hyper::header::ContentType; +use serde_json; + +/// Prepares an error response , logs it, wraps to BoxFuture. +pub fn error(response: Response, + code: StatusCode, + message: &str) + -> BoxFuture { + return ok(response.with_header(ContentType::plaintext()) + .with_status(code) + .with_body(message.to_string())) + .boxed(); +} + +/// Prepares an success response, wraps to BoxFuture. +pub fn success(response: Response, + code: StatusCode, + value: &serde_json::Value) + -> BoxFuture { + return ok(response.with_header(ContentType::json()) + .with_status(code) + .with_body(serde_json::to_vec(&value).unwrap())) + .boxed(); +} + +/// Splits '/' and filters empty strings +pub fn split_path(path: String) -> Vec { + path.split("/").filter(|x| !x.is_empty()).map(String::from).collect::>() +} + + + +/// Helps to decide id value. +pub fn decide_id(part: Option<&String>) -> Result { + match part { + Some(val) => { + if !val.is_empty() { + match i64::from_str_radix(val, 10) { + Ok(parsed) => Ok(parsed), + Err(e) => return Err(format!("Non parsable id Error: {}", e)), + } + } else { + Ok(-1) + } + } + None => Ok(-1), + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn split_path_test() { + // split_path("as/saß".to_string()); + assert_eq!(split_path("".to_string()),Vec::::new(),"Empty string must return empty vector."); + assert_eq!(split_path("/".to_string()),Vec::::new(),"Empty string must return empty vector."); + assert_eq!(split_path("//".to_string()),Vec::::new(),"Empty string must return empty vector."); + assert_eq!(split_path("/posts".to_string()),vec!("posts")); + assert_eq!(split_path("/posts/".to_string()),vec!("posts")); + assert_eq!(split_path("/posts/1".to_string()),vec!("posts","1")); + } +} \ No newline at end of file diff --git a/src/weld.rs b/src/weld.rs new file mode 100644 index 0000000..af184ee --- /dev/null +++ b/src/weld.rs @@ -0,0 +1,16 @@ +/// Holds the shared variables of the application. +use slog; +use slog_term; +use slog_async; +use slog::Drain; +use std::sync::Arc; +use configuration::Configuration; +use configuration; +use database::Database; +use std::sync::Mutex; + +lazy_static! { + pub static ref ROOT_LOGGER: slog::Logger = slog::Logger::root(Arc::new(slog_async::Async::new(slog_term::CompactFormat::new(slog_term::TermDecorator::new().build()).build().fuse()).build().fuse()), o!()); + pub static ref CONFIGURATION : Mutex = Mutex::new(Configuration::new("")); + pub static ref DATABASE : Mutex = Mutex::new(Database::new(&configuration::Database{path:"".to_string()})); +} diff --git a/tests/main.rs b/tests/main.rs deleted file mode 100644 index 31211ce..0000000 --- a/tests/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Conditionally compile `main` only when the test-suite is *not* being run. -#[cfg(not(test))] -fn main() { - println!("If you see this, the tests were not compiled nor ran!"); -} - - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} \ No newline at end of file