Skip to content

Commit

Permalink
Query parsing and tests #11 #14
Browse files Browse the repository at this point in the history
  • Loading branch information
serayuzgur committed Apr 20, 2017
1 parent fc01df2 commit 1423459
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 123 deletions.
2 changes: 0 additions & 2 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
31 changes: 7 additions & 24 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Configuration> = Mutex::new(Configuration::new(""));
pub static ref DATABASE : Mutex<Database> = 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");
}
}
Expand All @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use configuration;
use rest_service;
use service;
use slog;
use weld;
use hyper::server::Http;
Expand All @@ -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(),
})
Expand Down
114 changes: 32 additions & 82 deletions src/rest_service.rs → src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Response, hyper::Error> {
// 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<Response, hyper::Error> {
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<String> {
path.split("/").filter(|x| !x.is_empty()).map(String::from).collect::<Vec<String>>()
}

/// Helps to decide id value.
fn decide_id(part: Option<&String>) -> Result<i64, String> {
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<Response, hyper::Error> {
-> BoxFuture<Response, hyper::Error> {
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"),
}
}
}
Expand All @@ -91,7 +41,7 @@ impl RestService {
fn post(req: Request,
table: String,
response: Response)
-> futures::BoxFuture<Response, hyper::Error> {
-> BoxFuture<Response, hyper::Error> {
req.body()
.concat()
.and_then(move |body| {
Expand All @@ -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")
}
Expand All @@ -130,7 +80,7 @@ impl RestService {
table: String,
id: i64,
response: Response)
-> futures::BoxFuture<Response, hyper::Error> {
-> BoxFuture<Response, hyper::Error> {
// TODO:: use path id when updating
req.body()
.concat()
Expand All @@ -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")
}
Expand All @@ -168,19 +118,19 @@ impl RestService {
fn delete(table: String,
id: i64,
response: Response)
-> futures::BoxFuture<Response, hyper::Error> {
-> BoxFuture<Response, hyper::Error> {
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"),
}
}
}
Expand All @@ -191,27 +141,27 @@ impl Service for RestService {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = futures::BoxFuture<Response, hyper::Error>;
type Future = BoxFuture<Response, hyper::Error>;

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());
}
};

Expand All @@ -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.");
}
Expand Down
72 changes: 72 additions & 0 deletions src/service/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::cmp::{PartialEq, Eq};

/// Splits query params
pub fn split_query(query: Option<&str>) -> Vec<Query> {
match query {
Some(params) => {
// params.split("&").filter(|x| !x.is_empty()).map(String::from).collect::<Vec<String>>()
let mut queries = Vec::<Query>::new();
for param in params.split("&") {
if param.is_empty() {
continue;
}
let parts = param.split("=").collect::<Vec<&str>>();
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::<Vec<&str>>().get(1) {
Some(v) => v.to_string(),
None => "=".to_string(),
};

queries.push(Query {
key: key,
value: value,
op: op,
});
}
queries
}
None => Vec::<Query>::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::<Query>::new());
assert_eq!(split_query(Some("")), Vec::<Query>::new());
assert_eq!(split_query(Some("&&")), Vec::<Query>::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())]);
}
}
Loading

0 comments on commit 1423459

Please sign in to comment.