Skip to content

Commit

Permalink
query API parser #14
Browse files Browse the repository at this point in the history
  • Loading branch information
serayuzgur committed May 4, 2017
1 parent 2623861 commit 63f3174
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 111 deletions.
15 changes: 14 additions & 1 deletion src/database/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use database::Errors;
use std::vec::Vec;
use serde_json::Value;
use serde_json;
use service::query_api::Queries;

impl Database {
/// Retuns the list of the tables (outmost keys) from the database.
Expand All @@ -16,11 +17,23 @@ impl Database {
}

/// Reads the desired result with the given path.
pub fn read(&mut self, keys: &mut Vec<String>) -> Result<Value, Errors> {
pub fn read(&mut self,
keys: &mut Vec<String>,
queries: Option<Queries>)
-> Result<Value, Errors> {
let mut data = &mut self.data;
println!("{:?}", queries);
// TODO: If path is db return db
match Self::get_object(keys, data) {
Ok(obj) => Ok(obj.clone()),
Err(ref msg) => Err(msg.clone()),
}
// TODO:
// Get the result
// If it is List than do the ops
// filter & operations & full text
// Sort
// Paginate
// Slice
}
}
27 changes: 18 additions & 9 deletions src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! This is the service layer of the application.
//! All requests taken by the server will be consumed by the services under this module.
pub mod utils;
pub mod query;
pub mod query_api;

use weld;
use slog;
Expand All @@ -15,6 +15,8 @@ use futures_cpupool::CpuPool;
use serde_json::{from_slice, Value, to_value};
use database::errors::Errors::{NotFound, Conflict};

use self::query_api::Queries;

/// A Simple struct to represent rest service.
pub struct RestService {
/// Logger of the service.
Expand All @@ -29,9 +31,12 @@ impl RestService {
/// To reach service Http Method must be GET.
/// It works in acceptor thread. Since it is fast for small databases it is ok to work like this.
/// Later all services must be handled under a new thread.
fn get(paths: Vec<String>, response: Response) -> BoxFuture<Response, hyper::Error> {
fn get(paths: Vec<String>,
queries: Option<Queries>,
response: Response)
-> BoxFuture<Response, hyper::Error> {
let mut db = weld::DATABASE.lock().unwrap();
match db.read(&mut paths.clone()) {
match db.read(&mut paths.clone(), queries) {
Ok(record) => return utils::success(response, StatusCode::Ok, &record),
Err(error) => {
match error {
Expand Down Expand Up @@ -130,27 +135,31 @@ impl RestService {

/// Service implementation for the RestService. It is required by tokio to make it work with our service.
impl Service for RestService {
///Type of the request
/// Type of the request
type Request = Request;
///Type of the response
/// Type of the response
type Response = Response;
///Type of the error
/// Type of the error
type Error = hyper::Error;
///Type of the future
/// Type of the future
type Future = BoxFuture<Response, hyper::Error>;

///Entry point of the service. Pases path nad method and redirect to the correct function.
/// Entry point of the service. Pases path nad method and redirect to the correct function.
fn call(&self, req: Request) -> Self::Future {
let path_parts = utils::split_path(req.path().to_string());
let response = Response::new();
// Table list
if let 0 = path_parts.len() {
// TODO: return as homepage with links
let db = weld::DATABASE.lock().unwrap();
utils::success(response, StatusCode::Ok, &to_value(&db.tables()).unwrap())
} else {
// Record list or record
match req.method() {
&Get => Self::get(path_parts, response),
&Get => {
let queries = query_api::parse(req.query());
Self::get(path_parts, queries, response)
}
&Post => Self::post(req, path_parts, response),
&Put => Self::put(req, path_parts, response),
&Delete => Self::delete(path_parts, response),
Expand Down
101 changes: 0 additions & 101 deletions src/service/query.rs

This file was deleted.

172 changes: 172 additions & 0 deletions src/service/query_api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//! # query
//! This module includes necessary structs and functions to parse query patameters in a spesific way.
mod query;
mod sort;
mod page;

pub use self::query::Query;
pub use self::sort::Sort;

/// parse query params. For duplicate parameters only last one will be used.
pub fn parse(query: Option<&str>) -> Option<Queries> {
match query {
Some(params) => {
let mut queries = Queries::new();
for param in params.split("&") {
if param.is_empty() {
continue;
}
// wellcome, now we can start the real parsing
let parts = param.splitn(2,"=").collect::<Vec<&str>>();
if parts.get(0).is_none() || parts.get(1).is_none() {
continue;
}
// so we got a real parameter
let key = parts.get(0).unwrap().to_string();
let value = parts.get(1).unwrap().to_string();
if key.starts_with("_") {
// fields,offset,limit,sort,filter,q
set_where_it_belongs(&mut queries,
Query {
key: key,
value: value,
op: "=".to_string(),
});
}
}
Some(queries)
}
None => None,
}
}
fn set_where_it_belongs(queries: &mut Queries, q: Query) {

match q.key.as_str() {
"_fields" => {
let mut fields_vec = &mut queries.fields;
fields_vec.extend(q.value.split(",").map(String::from).collect::<Vec<String>>());
}
"_offset" | "_limit" => {
if let Some(page) = page::parse(q) {
let mut paging = &mut queries.paginate;
println!("paging {:?}", page);
match page {
page::Page::OFFSET(_) => paging.0 = page,
page::Page::LIMIT(_) => paging.1 = page,
}
}
}
"_sort" => {
let mut sort_vet = &mut queries.sort;
sort_vet.extend(q.value.split(",").map(Sort::from).collect::<Vec<Sort>>());
}
"_filter" => {
let mut filter_vet = &mut queries.filter;
println!("parsing {}",q.value);
filter_vet.extend(q.value.split(",").map(Query::from).collect::<Vec<Query>>());
}
"_q" => {
queries.q = Some(q.value);
}
_ => {
// do nothing}
}
}
}

/// A simple struct to hold query parameters well structured.
#[derive(Debug)]
pub struct Queries {
/// field names to return
pub fields: Vec<String>,
/// filter and operation parameters
pub filter: Vec<Query>,
/// Full text search
pub q: Option<String>,
/// Pagination parameters
pub paginate: (page::Page, page::Page),
/// Slice parameters
pub slice: Vec<Query>,
/// Sorting parameters
pub sort: Vec<Sort>,
}

impl Queries {
/// Creates a new instance with the empty values.
pub fn new() -> Queries {
Queries {
fields: Vec::<String>::new(),
filter: Vec::<Query>::new(),
q: None,
paginate: (page::Page::OFFSET(0), page::Page::LIMIT(10)),
slice: Vec::<Query>::new(),
sort: Vec::<Sort>::new(),
}
}
}

impl PartialEq for Queries {
#[inline]
fn eq(&self, other: &Queries) -> bool {
self.fields == other.fields && self.filter == other.filter && self.q == other.q &&
self.paginate == other.paginate && self.slice == other.slice &&
self.sort == other.sort
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_fields_test() {
let mut queries = Queries::new();
{
let fields = &mut queries.fields;
fields.push("a".to_string());
fields.push("b".to_string());
}
assert_eq!(parse(Some("_fields=a,b")), Some(queries));
}

#[test]
fn parse_paginate_test() {
let mut queries = Queries::new();
{
let paginate = &mut queries.paginate;
paginate.0 = page::Page::OFFSET(10);
paginate.1 = page::Page::LIMIT(5);
}
assert_eq!(parse(Some("_offset=10&_limit=5")), Some(queries));
}

#[test]
fn parse_sort_test() {
let mut queries = Queries::new();
{
let sort = &mut queries.sort;
sort.push(Sort::ASC("a".to_string()));
sort.push(Sort::DSC("b".to_string()));
}
assert_eq!(parse(Some("_sort=a+,b-")), Some(queries));
}

#[test]
fn parse_filter_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("name", "=", "seray"));
filter.push(Query::new("active", "=", "true"));
}
assert_eq!(parse(Some("_filter=name=seray,active=true")), Some(queries));
}

#[test]
fn parse_q_test() {
let mut queries = Queries::new();
{
queries.q = Some("seray".to_string());
}
assert_eq!(parse(Some("_q=seray")), Some(queries));
}
}
Loading

0 comments on commit 63f3174

Please sign in to comment.