├── FUNDING.yml ├── .gitignore ├── weld.json ├── src ├── database │ ├── query_api │ │ ├── mod.rs │ │ ├── q.rs │ │ ├── fields.rs │ │ ├── paginate.rs │ │ ├── sort.rs │ │ └── filter.rs │ ├── errors.rs │ ├── read.rs │ ├── delete.rs │ ├── update.rs │ ├── insert.rs │ └── mod.rs ├── weld.rs ├── server.rs ├── main.rs ├── service │ ├── query_api │ │ ├── sort.rs │ │ ├── page.rs │ │ ├── query.rs │ │ └── mod.rs │ ├── utils.rs │ └── mod.rs └── configuration.rs ├── CHANGELOG.md ├── Cargo.toml ├── .travis.yml ├── LICENSE ├── db.json ├── README.md └── Cargo.lock /FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: serayuzgur 2 | github: serayuzgur -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | 4 | .DS_Store 5 | .vscode/ 6 | .idea/ -------------------------------------------------------------------------------- /weld.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "host": "127.0.0.1", 4 | "port": 8080 5 | }, 6 | "database": { 7 | "path": "db.json", 8 | "default_pk":"id" 9 | } 10 | } -------------------------------------------------------------------------------- /src/database/query_api/mod.rs: -------------------------------------------------------------------------------- 1 | //! # query_api 2 | //! All necessery functions for appliying query api to json results. 3 | pub mod filter; 4 | pub mod q; 5 | pub mod sort; 6 | pub mod fields; 7 | pub mod paginate; 8 | 9 | // TODO: 10 | // Get the result 11 | // If it is List than do the ops 12 | // filter & operations & full text 13 | // Sort 14 | // Paginate 15 | // fields 16 | -------------------------------------------------------------------------------- /src/database/errors.rs: -------------------------------------------------------------------------------- 1 | //! # errors 2 | // This module includes error enum and utility fuctions. 3 | use slog::Logger; 4 | use serde_json::Value; 5 | 6 | /// Error types which can occur on database aperation. 7 | #[derive(Clone)] 8 | #[derive(Debug)] 9 | pub enum Errors { 10 | /// Record not found 11 | NotFound(String), 12 | /// Record conflicts with another record. 13 | Conflict(String), 14 | } 15 | 16 | /// Takes the error, logs it and wraps inside a Result 17 | pub fn log_n_wrap(logger: &Logger, error: Errors) -> Result { 18 | error!(logger, "{:?}", error); 19 | return Err(error); 20 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | #### 0.0.1-alpha.5 4 | * FutureResult usage removed. Now Box> is in use. 5 | * Blocking post and put bug fixed. 6 | #### 0.0.1-alpha.4 7 | * Dependencies updated. 8 | * All warnings cleared. There are no deprecated usages now. 9 | 10 | #### 0.0.1-alpha.3 11 | * Dependencies updated. 12 | * Renamed because of crates.io (weld -> weldmock) 13 | 14 | #### 0.0.1-alpha.2 15 | * Migrated to hyper. Works as expected. 16 | * Dependencies cleared. 17 | * Logs cleared and switched to Compact log mode. 18 | * Nested json structure support. 19 | 20 | #### v0.0.1-alpha.1 21 | * added some information to **Cargo.toml** file. 22 | * added example main.rs test. 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weldmock" 3 | version = "0.0.1-alpha.5" 4 | license = "MIT/Apache-2.0" 5 | authors = ["serayuzgur "] 6 | description = "Full fake REST API generator." 7 | homepage = "https://github.com/serayuzgur/weld" 8 | repository = "https://github.com/serayuzgur/weld" 9 | readme = "README.md" 10 | keywords = [ "json", "mock", "server","fake","api" ] 11 | exclude = [ 12 | ".gitignore", 13 | "tests/**/*", 14 | ".travis.yml", 15 | "db.json" 16 | ] 17 | 18 | [dependencies] 19 | # Serde for JSON 20 | serde="1.0.27" 21 | serde_derive="1.0.27" 22 | serde_json="1.0.9" 23 | 24 | bytes = "1.2.1" 25 | futures = "0.1.31" 26 | hyper = "0.11.19" 27 | futures-cpupool = "0.1.8" 28 | rand = "0.8.5" 29 | 30 | # Logger 31 | slog= "2.1.1" 32 | slog-term= "2.3.0" 33 | slog-async= "2.2.0" 34 | 35 | time="0.1.39" 36 | lazy_static="1.4.0" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | cache: cargo 9 | 10 | matrix: 11 | allow_failures: 12 | - rust: nightly 13 | 14 | before_install: 15 | - sudo apt-get update 16 | 17 | addons: 18 | apt: 19 | packages: 20 | - libcurl4-openssl-dev 21 | - libelf-dev 22 | - libdw-dev 23 | - cmake 24 | - gcc 25 | - binutils-dev 26 | 27 | after_success: | 28 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 29 | tar xzf master.tar.gz && 30 | cd kcov-master && 31 | mkdir build && 32 | cd build && 33 | cmake .. && 34 | make && 35 | sudo make install && 36 | cd ../.. && 37 | rm -rf kcov-master && 38 | for file in target/debug/weld-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 39 | bash <(curl -s https://codecov.io/bash) && 40 | echo "Uploaded code coverage" 41 | 42 | -------------------------------------------------------------------------------- /src/weld.rs: -------------------------------------------------------------------------------- 1 | //! # weld 2 | //! This module holds the static shared variables of the application. All of them could be used without fear of concurrency. 3 | use slog::Logger; 4 | use slog_term::{CompactFormat, TermDecorator}; 5 | use slog_async::Async; 6 | use slog::Drain; 7 | use std::sync::Arc; 8 | use configuration::Configuration; 9 | use database::Database; 10 | use std::sync::Mutex; 11 | 12 | //Initializes lazy statics. 13 | lazy_static! { 14 | /// Root logger for the application. Helps to manage log output from one place. All loggers will use this. 15 | pub static ref ROOT_LOGGER: Logger = Logger::root(Arc::new(Async::new(CompactFormat::new(TermDecorator::new().build()).build().fuse()).build().fuse()), o!()); 16 | 17 | /// Configuration of the application. Provides mutable synchronised configuration access. 18 | pub static ref CONFIGURATION : Mutex = Mutex::new(Configuration::new()); 19 | 20 | /// Database of the application. Provides mutable synchronised db access. 21 | pub static ref DATABASE : Mutex = Mutex::new(Database::new()); 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Seray Uzgur 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/database/read.rs: -------------------------------------------------------------------------------- 1 | use database::Database; 2 | use database::Errors; 3 | use std::vec::Vec; 4 | use serde_json::Value; 5 | use serde_json; 6 | use service::query_api::Queries; 7 | use database::query_api; 8 | 9 | impl Database { 10 | /// Retuns the list of the tables (outmost keys) from the database. 11 | pub fn tables(&self) -> Vec<&String> { 12 | let map: &serde_json::Map = self.data.as_object().expect( 13 | "Database is invalid. You can't mock API with it. Terminating...", 14 | ); 15 | let mut keys = Vec::new(); 16 | keys.extend(map.keys()); 17 | keys 18 | } 19 | 20 | /// Reads the desired result with the given path. 21 | pub fn read( 22 | &mut self, 23 | keys: &mut Vec, 24 | queries: Option, 25 | ) -> Result { 26 | let data = &mut self.data; 27 | match Self::get_object(keys, data) { 28 | Ok(obj) => { 29 | println!("{:?}", queries); 30 | if let Some(q) = queries { 31 | let clone = &mut obj.clone(); 32 | query_api::filter::apply(clone, &q); 33 | query_api::q::apply(clone, &q); 34 | query_api::sort::apply(clone, &q); 35 | query_api::fields::apply(clone, &q); 36 | query_api::paginate::apply(clone, &q); 37 | return Ok(clone.clone()); 38 | } 39 | return Ok(obj.clone()); 40 | } 41 | Err(ref msg) => Err(msg.clone()), 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! # server 2 | //! A simple module for managing server easily. 3 | 4 | use configuration; 5 | use futures_cpupool::CpuPool; 6 | use hyper::server::Http; 7 | use service; 8 | use slog; 9 | use weld; 10 | 11 | /// Holds server configuration and logger 12 | pub struct Server<'a> { 13 | //Configuration of the server for future access. 14 | configuration: &'a configuration::Server, 15 | //Logger for the server. All services should create loggers from this. 16 | logger: slog::Logger, 17 | } 18 | 19 | impl<'a> Server<'a> { 20 | /// Creates a new instance of Server 21 | pub fn new(config: &'a configuration::Server) -> Server<'a> { 22 | Server { 23 | configuration: config, 24 | logger: weld::ROOT_LOGGER.new(o!()), 25 | } 26 | } 27 | 28 | /// Starts the server. **Server event loop will run on the same thread with the thread this function called. Be careful.** 29 | pub fn start(&self) { 30 | let endpoint = format!("{}:{}", self.configuration.host, self.configuration.port) 31 | .parse() 32 | .unwrap(); 33 | info!(self.logger, "Server - Started ! {}", &endpoint); 34 | let thread_pool = CpuPool::new_num_cpus(); 35 | 36 | let http_results = Http::new().bind(&endpoint, move || { 37 | Ok(service::RestService { 38 | logger: weld::ROOT_LOGGER.new(o!()), 39 | thread_pool: thread_pool.clone(), 40 | }) 41 | }); 42 | 43 | let http = match http_results { 44 | Ok(http) => http, 45 | Err(error) => panic!("Problem starting Server: {:?}", error), 46 | }; 47 | 48 | let http_run_results = http.run(); 49 | 50 | let _http_run = match http_run_results { 51 | Ok(http_run) => http_run, 52 | Err(error) => panic!("Problem starting Server: {:?}", error), 53 | }; 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "owner": { 3 | "name": "seray", 4 | "surname": "uzgur" 5 | }, 6 | "posts": [ 7 | { 8 | "author": { 9 | "name": "seray", 10 | "surname": "uzgur" 11 | }, 12 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 13 | "id": 1, 14 | "tags": [ 15 | { 16 | "id": 1, 17 | "name": "tech" 18 | }, 19 | { 20 | "id": 2, 21 | "name": "web" 22 | } 23 | ], 24 | "title": "Rust Rocks!" 25 | }, 26 | { 27 | "author": { 28 | "name": "kamil", 29 | "surname": "bukum" 30 | }, 31 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 32 | "id": 2, 33 | "tags": [ 34 | { 35 | "id": 1, 36 | "name": "tech" 37 | }, 38 | { 39 | "id": 2, 40 | "name": "web" 41 | } 42 | ], 43 | "title": "TypeScript is Awesome" 44 | }, 45 | { 46 | "author": { 47 | "name": "hasan", 48 | "surname": "mumin" 49 | }, 50 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 51 | "id": 3, 52 | "tags": [ 53 | { 54 | "id": 1, 55 | "name": "tech" 56 | }, 57 | { 58 | "id": 2, 59 | "name": "web" 60 | } 61 | ], 62 | "title": "KendoUI Rocks!" 63 | } 64 | ], 65 | "version": "10.1.1" 66 | } -------------------------------------------------------------------------------- /src/database/delete.rs: -------------------------------------------------------------------------------- 1 | use database::Database; 2 | use database::Errors; 3 | use database::errors::log_n_wrap; 4 | use database::Errors::NotFound; 5 | use std::vec::Vec; 6 | use serde_json::Value; 7 | 8 | impl Database { 9 | /// Deletes the record with the given id. 10 | pub fn delete(&mut self, keys: &mut Vec) -> Result { 11 | let data = &mut self.data; 12 | let last_idx = keys.len() - 1; 13 | let last_item = keys.remove(last_idx); 14 | let key = last_item.as_str(); 15 | if let Ok(obj) = Self::get_object(keys, data) { 16 | match obj { 17 | &mut Value::Object(ref mut map) => { 18 | if let Some(deleted) = map.remove(key) { 19 | Ok(deleted.clone()) 20 | } else { 21 | log_n_wrap(&self.logger, NotFound(format!("Table not found {}", &key))) 22 | } 23 | } 24 | &mut Value::Array(ref mut array) => { 25 | let id = Self::decide_id(&key.to_string()); 26 | if let Some(idx) = Database::find_index(array, &id) { 27 | let value = array.remove(idx); 28 | info!(&self.logger, "Delete - Ok id: {:?}", &id); 29 | debug!(&self.logger, "Delete - Value {}", &value); 30 | Ok(value.clone()) 31 | } else { 32 | log_n_wrap(&self.logger, 33 | NotFound(format!("Delete - Error id: {:?}", &id))) 34 | } 35 | } 36 | _ => { 37 | log_n_wrap(&self.logger, 38 | NotFound(format!("Delete - Error path: {:?}", &key))) 39 | } 40 | } 41 | } else { 42 | log_n_wrap(&self.logger, 43 | NotFound(format!("Delete - Error path: {:?}", &key))) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | //! # Weld 3 | //! Weld is a fake API generator for easily mocking back-end services. 4 | //! It is heavily inspired from [JSON Server](https://github.com/typicode/json-server). 5 | //! Weld also claims that you can create APIs with **zero coding** in less than **30 seconds**. 6 | //! Providing a `db.json` file will generate crud operations, cascading manipulation and query parametric filter support. 7 | 8 | extern crate hyper; 9 | extern crate rand; 10 | extern crate futures; 11 | extern crate futures_cpupool; 12 | #[macro_use] 13 | extern crate serde_derive; 14 | extern crate serde_json; 15 | #[macro_use] 16 | extern crate slog; 17 | extern crate slog_term; 18 | extern crate slog_async; 19 | #[macro_use] 20 | extern crate lazy_static; 21 | extern crate time; 22 | 23 | pub mod service; 24 | pub mod server; 25 | pub mod configuration; 26 | pub mod database; 27 | pub mod weld; 28 | 29 | use server::Server; 30 | use std::env::args; 31 | 32 | fn main() { 33 | info!(weld::ROOT_LOGGER, "Application started"; 34 | "started_at" => format!("{}", time::now().rfc3339()), "version" => env!("CARGO_PKG_VERSION")); 35 | 36 | load_config(); 37 | 38 | load_db(); 39 | 40 | start_server(); 41 | } 42 | 43 | /// Loads configuration. 44 | fn load_config() { 45 | let mut configuration = weld::CONFIGURATION.lock() 46 | .expect("Configuration is not accesible. Terminating..."); 47 | 48 | //No arg ? so use default. 49 | if let Some(path) = args().nth(1) { 50 | configuration.load(path.as_str()) 51 | } else { 52 | info!(weld::ROOT_LOGGER, "Program arguments not found."); 53 | configuration.load("weld.json"); 54 | } 55 | } 56 | 57 | /// Loads database. 58 | fn load_db() { 59 | let mut db = weld::DATABASE.lock().expect("Database is not accesible. Terminating..."); 60 | db.load(&weld::CONFIGURATION.lock().unwrap().database); 61 | } 62 | 63 | /// Starts server. 64 | fn start_server() { 65 | let config = weld::CONFIGURATION.lock().unwrap().server.clone(); 66 | Server::new(&config).start(); 67 | } -------------------------------------------------------------------------------- /src/service/query_api/sort.rs: -------------------------------------------------------------------------------- 1 | //! # sort 2 | //! All sorting related codes contained under this module. 3 | use std::cmp::PartialEq; 4 | 5 | /// An enum to hold sort parameters with the direction. 6 | #[derive(Debug,Clone)] 7 | pub enum Sort { 8 | /// Ascending sorting. 9 | ASC(String), 10 | /// Descendinf sorting. 11 | DSC(String), 12 | } 13 | 14 | impl PartialEq for Sort { 15 | #[inline] 16 | fn eq(&self, other: &Sort) -> bool { 17 | match self { 18 | &Sort::ASC(ref s_name) => { 19 | match other { 20 | &Sort::ASC(ref o_name) => s_name == o_name, 21 | &Sort::DSC(_) => false, 22 | } 23 | } 24 | &Sort::DSC(ref s_name) => { 25 | match other { 26 | &Sort::ASC(_) => false, 27 | &Sort::DSC(ref o_name) => s_name == o_name, 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl<'a> From<&'a str> for Sort { 35 | fn from(s: &'a str) -> Sort { 36 | if let Some(sort) = parse(s) { 37 | return sort; 38 | } else { 39 | return Sort::ASC(s.to_string()); 40 | } 41 | } 42 | } 43 | 44 | /// Parses parameter to a Sort enum 45 | pub fn parse>(param: S) -> Option { 46 | let mut param_mut = param.into().to_string(); 47 | match param_mut.pop() { 48 | Some(t) => { 49 | match t { 50 | '+' => Some(Sort::ASC(param_mut)), 51 | '-' => Some(Sort::DSC(param_mut)), 52 | _ => None, 53 | } 54 | } 55 | None => None, 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | #[test] 63 | fn parse_test() { 64 | assert_eq!(parse(""), None); 65 | assert_eq!(parse("name"), None); 66 | assert_eq!(parse("name+"), Some(Sort::ASC("name".to_string()))); 67 | assert_eq!(parse("name-"), Some(Sort::DSC("name".to_string()))); 68 | 69 | assert_ne!(parse("name+"), Some(Sort::DSC("name".to_string()))); 70 | assert_ne!(parse("name-"), Some(Sort::ASC("name".to_string()))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/service/utils.rs: -------------------------------------------------------------------------------- 1 | //! # utils 2 | //! Holds utility functions in order to help service layer. 3 | 4 | use hyper::StatusCode; 5 | use hyper::server::Response; 6 | use hyper::Error; 7 | use hyper::header::AccessControlAllowOrigin; 8 | use futures; 9 | use futures::future::ok; 10 | use hyper::header::ContentType; 11 | use serde_json; 12 | 13 | type FutureBox = Box>; 14 | 15 | 16 | /// Prepares an error response , logs it, wraps to BoxFuture. 17 | pub fn error( 18 | response: Response, 19 | code: StatusCode, 20 | message: &str, 21 | ) -> FutureBox { 22 | Box::new(ok(response 23 | .with_header(AccessControlAllowOrigin::Any) 24 | .with_header(ContentType::plaintext()) 25 | .with_status(code) 26 | .with_body(message.to_string()))) 27 | } 28 | 29 | /// Prepares an success response, wraps to BoxFuture. 30 | pub fn success( 31 | response: Response, 32 | code: StatusCode, 33 | value: &serde_json::Value, 34 | ) -> FutureBox { 35 | Box::new(ok(response 36 | .with_header(AccessControlAllowOrigin::Any) 37 | .with_header(ContentType::json()) 38 | .with_status(code) 39 | .with_body(serde_json::to_vec(&value).unwrap()))) 40 | } 41 | 42 | /// Splits '/' and filters empty strings 43 | pub fn split_path(path: String) -> Vec { 44 | path.split("/") 45 | .filter(|x| !x.is_empty()) 46 | .map(String::from) 47 | .collect::>() 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn split_path_test() { 56 | // split_path("as/saß".to_string()); 57 | assert_eq!( 58 | split_path("".to_string()), 59 | Vec::::new(), 60 | "Empty string must return empty vector." 61 | ); 62 | assert_eq!( 63 | split_path("/".to_string()), 64 | Vec::::new(), 65 | "Empty string must return empty vector." 66 | ); 67 | assert_eq!( 68 | split_path("//".to_string()), 69 | Vec::::new(), 70 | "Empty string must return empty vector." 71 | ); 72 | assert_eq!(split_path("/posts".to_string()), vec!["posts"]); 73 | assert_eq!(split_path("/posts/".to_string()), vec!["posts"]); 74 | assert_eq!(split_path("/posts/1".to_string()), vec!["posts", "1"]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/database/update.rs: -------------------------------------------------------------------------------- 1 | use database::Database; 2 | use database::Errors; 3 | use database::errors::log_n_wrap; 4 | use database::Errors::{Conflict, NotFound}; 5 | use std::vec::Vec; 6 | use serde_json::Value; 7 | 8 | impl Database { 9 | /// Updates the record with the given path. 10 | pub fn update(&mut self, keys: &mut Vec, value: Value) -> Result { 11 | let data = &mut self.data; 12 | if let Ok(obj) = Self::get_object(keys, data) { 13 | match obj { 14 | &mut Value::Object(ref mut map) => { 15 | // let id = map.get("id").clone(); 16 | if let Value::Object(value_map) = value { 17 | for key in value_map.keys() { 18 | if key != "id" { 19 | map.insert(key.to_string(), value_map.get(key).unwrap().clone()); 20 | } 21 | } 22 | } 23 | info!(&self.logger, "Updated - Ok id: {:?}", &map.get("id")); 24 | debug!(&self.logger, "Updated - Value {:?}", &map); 25 | } 26 | &mut Value::String(ref mut s) => if let Value::String(value_s) = value { 27 | *s = value_s.clone(); 28 | }, 29 | &mut Value::Number(ref mut n) => if let Value::Number(value_n) = value { 30 | *n = value_n; 31 | }, 32 | &mut Value::Bool(ref mut b) => if let Value::Bool(value_s) = value { 33 | *b = value_s.clone(); 34 | }, 35 | _ => { 36 | //TODO:: Check error message for the case 37 | return log_n_wrap( 38 | &self.logger, 39 | Conflict(format!( 40 | "Update - Error already has an object \ 41 | with the given key: {:?}", 42 | keys 43 | )), 44 | ); 45 | } 46 | } 47 | return Ok(obj.clone()); 48 | } else { 49 | log_n_wrap( 50 | &self.logger, 51 | NotFound(format!( 52 | "Update - Error {:?}. No record with the given path:", 53 | keys 54 | )), 55 | ) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/service/query_api/page.rs: -------------------------------------------------------------------------------- 1 | //! # Page 2 | //! All paging related codes contained under this module. 3 | use service::query_api::query::Query; 4 | /// An enum to hold sort parameters with the direction. 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum Page { 7 | /// Ascending sorting. 8 | OFFSET(u8), 9 | /// Descending sorting. 10 | LIMIT(u8), 11 | } 12 | 13 | impl PartialEq for Page { 14 | #[inline] 15 | fn eq(&self, other: &Page) -> bool { 16 | match self { 17 | &Page::OFFSET(s_size) => { 18 | match other { 19 | &Page::OFFSET(o_size) => s_size == o_size, 20 | &Page::LIMIT(_) => false, 21 | } 22 | } 23 | &Page::LIMIT(s_size) => { 24 | match other { 25 | &Page::OFFSET(_) => false, 26 | &Page::LIMIT(o_size) => s_size == o_size, 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | /// Parses parameter to a Sort enum 34 | pub fn parse(param: Query) -> Option { 35 | if let Ok(size) = u8::from_str_radix(¶m.value, 10) { 36 | match param.key.as_str() { 37 | "_offset" => Some(Page::OFFSET(size)), 38 | "_limit" => Some(Page::LIMIT(size)), 39 | _ => None, 40 | } 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | #[test] 51 | fn parse_test() { 52 | assert_eq!(parse(Query::new("", "", "")), None); 53 | assert_eq!(parse(Query::new("", "_", "")), None); 54 | assert_eq!(parse(Query::new("", "=", "")), None); 55 | assert_eq!(parse(Query::new("", "=", "Abc")), None); 56 | assert_eq!(parse(Query::new("", "=", "123")), None); 57 | assert_eq!(parse(Query::new("Abc", "=", "123")), None); 58 | assert_eq!(parse(Query::new("_offset", "=", "")), None); 59 | assert_eq!(parse(Query::new("_limit", "=", "")), None); 60 | assert_eq!( 61 | parse(Query::new("_offset", "=", "123")), 62 | Some(Page::OFFSET(123)) 63 | ); 64 | assert_eq!( 65 | parse(Query::new("_limit", "=", "123")), 66 | Some(Page::LIMIT(123)) 67 | ); 68 | 69 | assert_ne!( 70 | parse(Query::new("_offset", "=", "123")), 71 | Some(Page::LIMIT(123)) 72 | ); 73 | assert_ne!( 74 | parse(Query::new("_offset", "=", "123")), 75 | Some(Page::LIMIT(123)) 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/database/insert.rs: -------------------------------------------------------------------------------- 1 | use database::Database; 2 | use database::Errors; 3 | use database::errors::log_n_wrap; 4 | use database::Errors::{NotFound, Conflict}; 5 | use std::vec::Vec; 6 | use serde_json::Value; 7 | use serde_json; 8 | use rand; 9 | 10 | impl Database { 11 | /// Inserts the record to the given path. 12 | pub fn insert(&mut self, keys: &mut Vec, value: Value) -> Result { 13 | let data = &mut self.data; 14 | if let Ok(obj) = Self::get_object(keys, data) { 15 | // Path Found. It should be an array to accomplish an operation. Otherwise it must be an update not insert. 16 | if let Some(ref mut array) = obj.as_array_mut() { 17 | let mut id = rand::random(); 18 | // If id comes with the record use it. 19 | if let Some(id_value) = value.get("id") { 20 | if let Some(parsed) = id_value.as_i64() { 21 | id = parsed; 22 | } 23 | } 24 | let value_with_id = &mut value.clone(); 25 | if let Some(obj_id) = value_with_id.as_object_mut() { 26 | obj_id.insert("id".to_string(), serde_json::to_value(id).unwrap()); 27 | } 28 | // TODO: random id conflict must be resolved. 29 | if let Some(idx) = Database::find_index(array, &id) { 30 | log_n_wrap(&self.logger, 31 | Conflict(format!("Insert - Error {:?}. \"id\" duplicates \ 32 | record at index: {:?}", 33 | &value_with_id, 34 | idx))) 35 | } else { 36 | array.push(value_with_id.clone()); 37 | info!(&self.logger, "Insert - Ok id: {:?}", &id); 38 | debug!(&self.logger, "Insert - Value {}", &value_with_id); 39 | Ok(value_with_id.clone()) 40 | } 41 | } else { 42 | log_n_wrap(&self.logger, 43 | Conflict(format!("Insert - Error already has an object with the \ 44 | given key: {:?}", 45 | keys))) 46 | } 47 | } else { 48 | log_n_wrap(&self.logger, 49 | NotFound(format!("Insert - Error {:?}. No record with the given path:", 50 | keys))) 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/configuration.rs: -------------------------------------------------------------------------------- 1 | //! # configuration 2 | //! This module holds all necessary structs and fn implementations for reading and parsing configuration JSON's. 3 | extern crate serde_json; 4 | 5 | use std::fs::File; 6 | use std::io::prelude::*; 7 | use weld; 8 | 9 | /// Root struct. All application configuration must be defined in here. 10 | #[derive(Serialize, Deserialize)] 11 | #[derive(Debug,Clone)] 12 | pub struct Configuration { 13 | /// Server configuration 14 | pub server: Server, 15 | 16 | /// Database configuration 17 | pub database: Database, 18 | } 19 | 20 | impl Configuration { 21 | /// Creates an instance with default configuration. 22 | pub fn new() -> Configuration { 23 | Configuration { 24 | database: Database { 25 | path: "db.json".to_string(), 26 | default_pk: "id".to_string(), 27 | }, 28 | server: Server { 29 | port: 8080, 30 | host: "127.0.0.1".to_string(), 31 | }, 32 | } 33 | } 34 | 35 | /// Loads the file at the given path to the instance. 36 | pub fn load(&mut self, path: &str) { 37 | info!(weld::ROOT_LOGGER, 38 | "Configuration - Reading Path: {:?}", 39 | &path); 40 | let mut file = File::open(path) 41 | .expect("Configuration - Error Can't read provided configuration. Terminating..."); 42 | let mut contents = String::new(); 43 | match file.read_to_string(&mut contents) { 44 | Ok(size) => { 45 | if size == 0 { 46 | panic!("Configuration - Error Empty File Terminating..."); 47 | } 48 | let config: Configuration = serde_json::from_str(&contents) 49 | .expect("Configuration - Error Invalid JSON format. Terminating..."); 50 | info!(weld::ROOT_LOGGER, "Configuration - Ok"); 51 | debug!(weld::ROOT_LOGGER, "{:?}", &config); 52 | self.server = config.server; 53 | self.database = config.database; 54 | } 55 | Err(e) => { 56 | error!(weld::ROOT_LOGGER, "Configuration - Error : {}", e); 57 | panic!("Configuration - Error Terminating..."); 58 | } 59 | } 60 | 61 | 62 | } 63 | } 64 | 65 | 66 | /// Server configuration. All server configuration must be defined in here. 67 | #[derive(Serialize, Deserialize)] 68 | #[derive(Debug,Clone)] 69 | pub struct Server { 70 | /// Host/IP address to listen 71 | pub host: String, 72 | 73 | /// Port to listen 74 | pub port: i16, 75 | } 76 | 77 | /// Database configuration. All database configuration must be defined in here. 78 | #[derive(Serialize, Deserialize)] 79 | #[derive(Debug,Clone)] 80 | pub struct Database { 81 | /// Path of the desired database json file. It could be both relative or absolute. 82 | pub path: String, 83 | 84 | /// Default key to use as the PK(Primary Key) for the tables. 85 | pub default_pk: String, 86 | } 87 | -------------------------------------------------------------------------------- /src/database/query_api/q.rs: -------------------------------------------------------------------------------- 1 | //! # q 2 | //! All necessery functions for appliying full-text search to json results. 3 | use serde_json::Value; 4 | use service::query_api::Queries; 5 | 6 | /// full-text search on all String fields according to the query api 7 | pub fn apply(obj: &mut Value, queries: &Queries) { 8 | if let Some(ref q) = queries.q { 9 | if let &mut Value::Array(ref mut arr) = obj { 10 | let mut size = arr.len(); 11 | let mut i = 0; 12 | while i < size { 13 | let mut valid = false; 14 | if let Some(item) = arr.get(i) { 15 | // get item field list 16 | match item { 17 | &Value::Object(ref map) => { 18 | for key in map.keys() { 19 | if let Some(field) = item.get(key) { 20 | if let &Value::String(ref f_str) = field { 21 | valid = f_str.contains(q); 22 | if valid { 23 | break; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | &Value::String(ref f_val) => { 30 | valid = f_val == q; 31 | if !valid { 32 | break; 33 | } 34 | } 35 | _ => {} 36 | } 37 | } else { 38 | break; 39 | } 40 | if !valid { 41 | arr.remove(i); 42 | size -= 1; 43 | } else { 44 | i += 1; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | use serde_json; 55 | fn get_json() -> Value { 56 | let json_string = r#"[ 57 | { 58 | "name":"seray", 59 | "age":31, 60 | "active":true, 61 | "password":"123" 62 | }, 63 | { 64 | "name":"kamil", 65 | "age":900, 66 | "active":false, 67 | "password":"333" 68 | }, 69 | { 70 | "name":"hasan", 71 | "age":25, 72 | "active":true, 73 | "password":"3212" 74 | } 75 | ]"#; 76 | serde_json::from_str(&json_string).unwrap() 77 | } 78 | 79 | #[test] 80 | fn apply_q_test() { 81 | let mut queries = Queries::new(); 82 | { 83 | queries.q = Some("12".to_string()); 84 | } 85 | let expected: Value = serde_json::from_str(&r#"[ 86 | { 87 | "name":"seray", 88 | "age":31, 89 | "active":true, 90 | "password":"123" 91 | }, 92 | { 93 | "name":"hasan", 94 | "age":25, 95 | "active":true, 96 | "password":"3212" 97 | }]"#) 98 | .unwrap(); 99 | let json = &mut get_json(); 100 | apply(json, &queries); 101 | assert_eq!(json.clone(), expected); 102 | } 103 | } -------------------------------------------------------------------------------- /src/database/query_api/fields.rs: -------------------------------------------------------------------------------- 1 | //! # fields 2 | //! All necessery functions for appliying fields to json results. 3 | use serde_json::Value; 4 | use serde_json; 5 | use service::query_api::Queries; 6 | 7 | /// let only named fields array according to the query api 8 | pub fn apply(obj: &mut Value, queries: &Queries) { 9 | let ref fields = queries.fields; 10 | if fields.len() == 0 { 11 | return; 12 | } 13 | match obj { 14 | &mut Value::Array(ref mut arr) => { 15 | let mut i = 0usize; 16 | let size = (&arr.len()).clone(); 17 | let remove_keys = match arr.get(0) { 18 | Some(item) => { 19 | if let &Value::Object(ref map) = item { 20 | diff_left(map.keys(), fields) 21 | } else { 22 | Vec::::new() 23 | } 24 | } 25 | None => Vec::::new(), 26 | }; 27 | while i < size { 28 | let map_val = arr.get_mut(i).unwrap(); 29 | if let &mut Value::Object(ref mut obj) = map_val { 30 | for key in &remove_keys { 31 | obj.remove(key); 32 | } 33 | } 34 | i += 1; 35 | } 36 | 37 | } 38 | &mut Value::Object(ref mut map) => { 39 | let remove_keys = diff_left(map.keys(), fields); 40 | for key in &remove_keys { 41 | map.remove(key); 42 | } 43 | } 44 | _ => { 45 | //No need to handle other types 46 | } 47 | } 48 | } 49 | 50 | fn diff_left(a: serde_json::map::Keys, b: &Vec) -> Vec { 51 | let mut diff = Vec::::new(); 52 | 'outer: for a_item in a { 53 | for b_item in b { 54 | if a_item == b_item { 55 | continue 'outer; 56 | } 57 | } 58 | diff.push(a_item.clone()); 59 | } 60 | diff 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | fn get_json() -> Value { 68 | let json_string = r#"[ 69 | { 70 | "name":"seray", 71 | "age":31, 72 | "active":true, 73 | "password":"123" 74 | }, 75 | { 76 | "name":"kamil", 77 | "age":900, 78 | "active":false, 79 | "password":"333" 80 | }, 81 | { 82 | "name":"hasan", 83 | "age":25, 84 | "active":true, 85 | "password":"321" 86 | } 87 | ]"#; 88 | serde_json::from_str(&json_string).unwrap() 89 | } 90 | 91 | #[test] 92 | fn apply_test() { 93 | let mut queries = Queries::new(); 94 | { 95 | let fields = &mut queries.fields; 96 | fields.push("name".to_string()); 97 | fields.push("active".to_string()); 98 | } 99 | let expected: Value = serde_json::from_str(&r#"[ 100 | { 101 | "name":"seray", 102 | "active":true 103 | }, 104 | { 105 | "name":"kamil", 106 | "active":false 107 | }, 108 | { 109 | "name":"hasan", 110 | "active":true 111 | } 112 | ]"#) 113 | .unwrap(); 114 | let json = &mut get_json(); 115 | apply(json, &queries); 116 | assert_eq!(json.clone(), expected); 117 | } 118 | 119 | #[test] 120 | fn diff_left_test() { 121 | let mut a = serde_json::Map::::new(); 122 | a.insert("a".to_string(), Value::Null); 123 | a.insert("b".to_string(), Value::Null); 124 | a.insert("c".to_string(), Value::Null); 125 | let b = vec!["b".to_string(), "c".to_string()]; 126 | let r = diff_left(a.keys(), &b); 127 | assert_eq!(r, vec!["a".to_string()]); 128 | } 129 | } -------------------------------------------------------------------------------- /src/service/query_api/query.rs: -------------------------------------------------------------------------------- 1 | //! # query_param 2 | //! This module includes necessary structs and functions to parse query patameters in a spesific way. 3 | use std::cmp::PartialEq; 4 | 5 | /// A simple struct to hold necessary information about a query parameter. 6 | #[derive(Debug,Clone,Eq)] 7 | pub struct Query { 8 | /// key of the parameter. It holds pure key without any `_eq` etc. 9 | pub key: String, 10 | /// value of the parameter. 11 | pub value: String, 12 | /// operation of the parameter. =, eq,neq,gtw,let,like 13 | pub op: String, 14 | } 15 | impl Query { 16 | /// Creates new instance for the query 17 | #[allow(dead_code)] 18 | pub fn new>(key: S, op: S, value: S) -> Query { 19 | Query { 20 | key: key.into(), 21 | op: op.into(), 22 | value: value.into(), 23 | } 24 | } 25 | } 26 | impl PartialEq for Query { 27 | #[inline] 28 | fn eq(&self, other: &Query) -> bool { 29 | self.key == other.key && self.value == other.value && self.op == other.op 30 | } 31 | } 32 | 33 | impl<'a> From<&'a str> for Query { 34 | fn from(s: &'a str) -> Query { 35 | if let Some(query) = parse(s) { 36 | return query; 37 | } else { 38 | return Query::new("", "", ""); 39 | } 40 | } 41 | } 42 | 43 | /// Parses parameter to a Sort enum 44 | pub fn parse>(param: S) -> Option { 45 | // add all ops =,!=,<,<=,>,>=,~=,|= 46 | let mut param_s = param.into(); 47 | 48 | let indexes = find_indexes(param_s.clone()); 49 | 50 | if indexes.0 != -1i8 && indexes.1 != -1i8 { 51 | let mut op = param_s.split_off(indexes.0 as usize); 52 | let val = op.split_off(indexes.1 as usize); 53 | Some(Query::new(param_s.to_string(), op.clone(), val.clone())) 54 | } else { 55 | None 56 | } 57 | } 58 | 59 | fn find_indexes(param: String) -> (i8, i8) { 60 | let mut key_end = -1i8; 61 | 62 | let chars = param.chars(); 63 | let mut idx = -1i8; 64 | let mut pre_c = ' '; 65 | for c in chars { 66 | idx += 1; 67 | if key_end == -1i8 { 68 | match c { 69 | '=' | '!' | '<' | '>' | '~' | '|' => { 70 | key_end = idx; 71 | pre_c = c; 72 | } 73 | _ => {} 74 | } 75 | } else { 76 | match c { 77 | '=' => return (key_end, 2), 78 | _ => { 79 | match pre_c { 80 | '=' | '<' | '>' => return (key_end, 1), 81 | _ => return (-1, -1), 82 | } 83 | } 84 | } 85 | } 86 | } 87 | (key_end, -1) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | #[test] 94 | fn parse_test() { 95 | assert_eq!(parse(""), None); 96 | assert_eq!(parse("a|b"), None); 97 | assert_eq!(parse("aaa|*bbb"), None); 98 | assert_eq!(parse("aaa|09*bbb"), None); 99 | 100 | assert_eq!(parse("a=b"), Some(Query::new("a", "=", "b"))); 101 | assert_eq!(parse("aaa=bbb"), Some(Query::new("aaa", "=", "bbb"))); 102 | 103 | assert_eq!(parse("a!=b"), Some(Query::new("a", "!=", "b"))); 104 | assert_eq!(parse("aaa!=bbb"), Some(Query::new("aaa", "!=", "bbb"))); 105 | 106 | assert_eq!(parse("ab"), Some(Query::new("a", ">", "b"))); 112 | assert_eq!(parse("aaa>bbb"), Some(Query::new("aaa", ">", "bbb"))); 113 | assert_eq!(parse("a>=b"), Some(Query::new("a", ">=", "b"))); 114 | assert_eq!(parse("aaa>=bbb"), Some(Query::new("aaa", ">=", "bbb"))); 115 | 116 | 117 | assert_eq!(parse("a~=b"), Some(Query::new("a", "~=", "b"))); 118 | assert_eq!(parse("aaa~=bbb"), Some(Query::new("aaa", "~=", "bbb"))); 119 | 120 | assert_eq!(parse("a|=b"), Some(Query::new("a", "|=", "b"))); 121 | assert_eq!(parse("aaa|=bbb"), Some(Query::new("aaa", "|=", "bbb"))); 122 | 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/database/query_api/paginate.rs: -------------------------------------------------------------------------------- 1 | //! # paginate 2 | //! All necessery functions for appliying pagination to json results. 3 | use serde_json::Value; 4 | use service::query_api::Queries; 5 | use service::query_api::Page; 6 | use std::vec::Vec; 7 | 8 | /// let only records within the given limits return. 9 | pub fn apply(obj: &mut Value, queries: &Queries) { 10 | let ref _offset = queries.paginate.0; 11 | let ref _limit = queries.paginate.1; 12 | 13 | match obj { 14 | &mut Value::Array(ref mut arr) => { 15 | let offset = match _offset { 16 | &Page::OFFSET(ref index) => index.clone(), 17 | _ => 0u8, 18 | }; 19 | let limit = match _limit { 20 | &Page::LIMIT(ref index) => index.clone(), 21 | _ => arr.len().clone() as u8, 22 | }; 23 | let o: usize = offset.clone() as usize; 24 | let l: usize = limit.clone() as usize; 25 | 26 | let mut temp = clone_slice(arr, o, l); 27 | 28 | arr.clear(); 29 | arr.append(&mut temp); 30 | } 31 | _ => { 32 | //No need to handle other types 33 | } 34 | } 35 | } 36 | /// copies array within the given indexes. 37 | fn clone_slice(arr: &mut Vec, offset: usize, limit: usize) -> Vec { 38 | let mut temp = Vec::::new(); 39 | let mut i = 0; 40 | let upperlimit = offset + limit; 41 | for val in arr { 42 | if i >= offset && i < upperlimit { 43 | temp.push(val.clone()); 44 | } 45 | i += 1; 46 | } 47 | temp 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use serde_json; 54 | 55 | fn get_json() -> Value { 56 | let json_string = r#"[ 57 | { 58 | "name":"seray", 59 | "age":31, 60 | "active":true, 61 | "password":"123" 62 | }, 63 | { 64 | "name":"kamil", 65 | "age":900, 66 | "active":false, 67 | "password":"333" 68 | }, 69 | { 70 | "name":"hasan", 71 | "age":25, 72 | "active":true, 73 | "password":"321" 74 | } 75 | ]"#; 76 | serde_json::from_str(&json_string).unwrap() 77 | } 78 | 79 | #[test] 80 | fn apply_test_middle() { 81 | let mut queries = Queries::new(); 82 | { 83 | let paginate = &mut queries.paginate; 84 | paginate.0 = Page::OFFSET(1); 85 | paginate.1 = Page::LIMIT(1); 86 | } 87 | let expected: Value = serde_json::from_str( 88 | &r#"[ 89 | { 90 | "name":"kamil", 91 | "age":900, 92 | "active":false, 93 | "password":"333" 94 | } 95 | ]"#, 96 | ).unwrap(); 97 | let json = &mut get_json(); 98 | apply(json, &queries); 99 | assert_eq!(json.clone(), expected); 100 | } 101 | #[test] 102 | fn apply_test_start() { 103 | let mut queries = Queries::new(); 104 | { 105 | let paginate = &mut queries.paginate; 106 | paginate.0 = Page::OFFSET(0); 107 | paginate.1 = Page::LIMIT(1); 108 | } 109 | let expected: Value = serde_json::from_str( 110 | &r#"[ 111 | { 112 | "name":"seray", 113 | "age":31, 114 | "active":true, 115 | "password":"123" 116 | } 117 | ]"#, 118 | ).unwrap(); 119 | let json = &mut get_json(); 120 | apply(json, &queries); 121 | assert_eq!(json.clone(), expected); 122 | } 123 | #[test] 124 | fn apply_test_end() { 125 | let mut queries = Queries::new(); 126 | { 127 | let paginate = &mut queries.paginate; 128 | paginate.0 = Page::OFFSET(2); 129 | paginate.1 = Page::LIMIT(1); 130 | } 131 | let expected: Value = serde_json::from_str( 132 | &r#"[ 133 | { 134 | "name":"hasan", 135 | "age":25, 136 | "active":true, 137 | "password":"321" 138 | } 139 | ]"#, 140 | ).unwrap(); 141 | let json = &mut get_json(); 142 | apply(json, &queries); 143 | assert_eq!(json.clone(), expected); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/database/query_api/sort.rs: -------------------------------------------------------------------------------- 1 | //! # sort 2 | //! All necessery functions for appliying sorting to json results. 3 | use serde_json::Value; 4 | use service::query_api::Queries; 5 | use service::query_api::Sort; 6 | use serde_json::Map; 7 | use std::cmp::Ordering; 8 | 9 | fn compare(a: &Map, b: &Map, sort_key: &String) -> Ordering { 10 | if let Some(a_val) = a.get(sort_key) { 11 | if let Some(b_val) = b.get(sort_key) { 12 | match a_val { 13 | &Value::Array(ref a_arr) => { 14 | if let &Value::Array(ref b_arr) = b_val { 15 | return a_arr.len().cmp(&b_arr.len()); 16 | } else { 17 | return Ordering::Greater; 18 | } 19 | } 20 | &Value::Object(ref a_obj) => { 21 | if let &Value::Object(ref b_obj) = b_val { 22 | return a_obj.len().cmp(&b_obj.len()); 23 | } else { 24 | return Ordering::Greater; 25 | } 26 | } 27 | &Value::Number(ref a_num) => { 28 | if let &Value::Number(ref b_num) = b_val { 29 | // TODO: clear unwraps 30 | return a_num.as_f64() 31 | .unwrap() 32 | .partial_cmp(&b_num.as_f64().unwrap()) 33 | .unwrap(); 34 | } else { 35 | return Ordering::Greater; 36 | } 37 | } 38 | &Value::String(ref a_str) => { 39 | if let &Value::String(ref b_str) = b_val { 40 | return a_str.cmp(&b_str); 41 | } else { 42 | return Ordering::Greater; 43 | } 44 | } 45 | &Value::Bool(a_bool) => { 46 | if let &Value::Bool(b_bool) = b_val { 47 | return a_bool.cmp(&b_bool); 48 | } else { 49 | return Ordering::Greater; 50 | } 51 | } 52 | &Value::Null => { 53 | if let &Value::Null = b_val { 54 | return Ordering::Equal; 55 | } else { 56 | return Ordering::Less; 57 | } 58 | } 59 | } 60 | } else { 61 | Ordering::Greater 62 | } 63 | } else { 64 | if let Some(_) = b.get(sort_key) { 65 | Ordering::Less 66 | } else { 67 | Ordering::Equal 68 | } 69 | } 70 | } 71 | 72 | /// sort on all desired fields fields according to the query api 73 | pub fn apply(obj: &mut Value, queries: &Queries) { 74 | if queries.sort.len() == 0 { 75 | return; 76 | } 77 | if let &mut Value::Array(ref mut arr) = obj { 78 | let ref sorts = queries.sort; 79 | arr.sort_by(|a: &Value, b: &Value| { 80 | match a { 81 | &Value::Object(ref map_a) => { 82 | if let &Value::Object(ref map_b) = b { 83 | let mut result = Ordering::Equal; 84 | for sort in sorts { 85 | result = match sort { 86 | &Sort::ASC(ref sort_key) => compare(&map_a, &map_b, &sort_key), 87 | &Sort::DSC(ref sort_key) => compare(&map_b, &map_a, &sort_key), 88 | }; 89 | if result != Ordering::Equal { 90 | return result; 91 | } 92 | } 93 | return result; 94 | } else { 95 | Ordering::Greater 96 | } 97 | } 98 | _ => Ordering::Less, 99 | } 100 | }); 101 | }; 102 | } 103 | 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::*; 108 | use serde_json; 109 | use service::query_api::Sort; 110 | 111 | fn get_json() -> Value { 112 | let json_string = r#"[ 113 | { 114 | "name":"seray", 115 | "age":31, 116 | "active":true, 117 | "password":"123" 118 | }, 119 | { 120 | "name":"kamil", 121 | "age":900, 122 | "active":false, 123 | "password":"333" 124 | }, 125 | { 126 | "name":"recep", 127 | "age":25, 128 | "active":true, 129 | "password":"3212" 130 | }, 131 | { 132 | "name":"hasan", 133 | "age":25, 134 | "active":true, 135 | "password":"3212" 136 | } 137 | ]"#; 138 | serde_json::from_str(&json_string).unwrap() 139 | } 140 | 141 | #[test] 142 | fn apply_sort_test() { 143 | let mut queries = Queries::new(); 144 | { 145 | queries.sort.push(Sort::ASC("age".to_string())); 146 | queries.sort.push(Sort::ASC("name".to_string())); 147 | } 148 | let expected: Value = serde_json::from_str(&r#"[ 149 | { 150 | "name":"hasan", 151 | "age":25, 152 | "active":true, 153 | "password":"3212" 154 | }, 155 | { 156 | "name":"recep", 157 | "age":25, 158 | "active":true, 159 | "password":"3212" 160 | }, 161 | { 162 | "name":"seray", 163 | "age":31, 164 | "active":true, 165 | "password":"123" 166 | }, 167 | { 168 | "name":"kamil", 169 | "age":900, 170 | "active":false, 171 | "password":"333" 172 | } 173 | ]"#) 174 | .unwrap(); 175 | let json = &mut get_json(); 176 | apply(json, &queries); 177 | assert_eq!(json.clone(), expected); 178 | } 179 | } -------------------------------------------------------------------------------- /src/service/query_api/mod.rs: -------------------------------------------------------------------------------- 1 | //! # query 2 | //! This module includes necessary structs and functions to parse query patameters in a spesific way. 3 | mod query; 4 | mod sort; 5 | mod page; 6 | 7 | pub use self::query::Query; 8 | pub use self::sort::Sort; 9 | pub use self::page::Page; 10 | 11 | /// parse query params. For duplicate parameters only last one will be used. 12 | pub fn parse(query: Option<&str>) -> Option { 13 | match query { 14 | Some(params) => { 15 | let mut queries = Queries::new(); 16 | for param in params.split("&") { 17 | if param.is_empty() { 18 | continue; 19 | } 20 | // wellcome, now we can start the real parsing 21 | let parts = param.splitn(2, "=").collect::>(); 22 | if parts.get(0).is_none() || parts.get(1).is_none() { 23 | continue; 24 | } 25 | // so we got a real parameter 26 | let key = parts.get(0).unwrap().to_string(); 27 | let value = parts.get(1).unwrap().to_string(); 28 | if key.starts_with("_") { 29 | // fields,offset,limit,sort,filter,q 30 | set_where_it_belongs( 31 | &mut queries, 32 | Query { 33 | key: key, 34 | value: value, 35 | op: "=".to_string(), 36 | }, 37 | ); 38 | } 39 | } 40 | Some(queries) 41 | } 42 | None => None, 43 | } 44 | } 45 | fn set_where_it_belongs(queries: &mut Queries, q: Query) { 46 | 47 | match q.key.as_str() { 48 | "_fields" => { 49 | let fields_vec = &mut queries.fields; 50 | fields_vec.extend( 51 | q.value 52 | .split(",") 53 | .map(String::from) 54 | .collect::>(), 55 | ); 56 | } 57 | "_offset" | "_limit" => { 58 | if let Some(page) = page::parse(q) { 59 | let mut paging = &mut queries.paginate; 60 | println!("paging {:?}", page); 61 | match page { 62 | page::Page::OFFSET(_) => paging.0 = page, 63 | page::Page::LIMIT(_) => paging.1 = page, 64 | } 65 | } 66 | } 67 | "_sort" => { 68 | let sort_vet = &mut queries.sort; 69 | sort_vet.extend(q.value.split(",").map(Sort::from).collect::>()); 70 | } 71 | "_filter" => { 72 | let filter_vet = &mut queries.filter; 73 | println!("parsing {}", q.value); 74 | filter_vet.extend(q.value.split(",").map(Query::from).collect::>()); 75 | } 76 | "_q" => { 77 | queries.q = Some(q.value); 78 | } 79 | _ => { 80 | // do nothing} 81 | } 82 | } 83 | } 84 | 85 | /// A simple struct to hold query parameters well structured. 86 | #[derive(Debug, Clone)] 87 | pub struct Queries { 88 | /// field names to return 89 | pub fields: Vec, 90 | /// filter and operation parameters 91 | pub filter: Vec, 92 | /// Full text search 93 | pub q: Option, 94 | /// Pagination parameters 95 | pub paginate: (page::Page, page::Page), 96 | /// Slice parameters 97 | pub slice: Vec, 98 | /// Sorting parameters 99 | pub sort: Vec, 100 | } 101 | 102 | impl Queries { 103 | /// Creates a new instance with the empty values. 104 | pub fn new() -> Queries { 105 | Queries { 106 | fields: Vec::::new(), 107 | filter: Vec::::new(), 108 | q: None, 109 | paginate: (page::Page::OFFSET(0), page::Page::LIMIT(10)), 110 | slice: Vec::::new(), 111 | sort: Vec::::new(), 112 | } 113 | } 114 | } 115 | 116 | impl PartialEq for Queries { 117 | #[inline] 118 | fn eq(&self, other: &Queries) -> bool { 119 | self.fields == other.fields && self.filter == other.filter && self.q == other.q && 120 | self.paginate == other.paginate && self.slice == other.slice && 121 | self.sort == other.sort 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | #[test] 129 | fn parse_none_test() { 130 | assert_eq!(parse(None), None); 131 | } 132 | #[test] 133 | fn parse_fields_test() { 134 | let mut queries = Queries::new(); 135 | { 136 | let fields = &mut queries.fields; 137 | fields.push("a".to_string()); 138 | fields.push("b".to_string()); 139 | } 140 | assert_eq!(parse(Some("_fields=a,b")), Some(queries)); 141 | } 142 | 143 | #[test] 144 | fn parse_paginate_test() { 145 | let mut queries = Queries::new(); 146 | { 147 | let paginate = &mut queries.paginate; 148 | paginate.0 = page::Page::OFFSET(10); 149 | paginate.1 = page::Page::LIMIT(5); 150 | } 151 | assert_eq!(parse(Some("_offset=10&_limit=5")), Some(queries)); 152 | } 153 | 154 | #[test] 155 | fn parse_sort_test() { 156 | let mut queries = Queries::new(); 157 | { 158 | let sort = &mut queries.sort; 159 | sort.push(Sort::ASC("a".to_string())); 160 | sort.push(Sort::DSC("b".to_string())); 161 | sort.push(Sort::ASC("c".to_string())); 162 | } 163 | assert_eq!(parse(Some("_sort=a+,b-,c")), Some(queries)); 164 | } 165 | 166 | #[test] 167 | fn parse_filter_test() { 168 | let mut queries = Queries::new(); 169 | { 170 | let filter = &mut queries.filter; 171 | filter.push(Query::new("name", "=", "seray")); 172 | filter.push(Query::new("active", "=", "true")); 173 | } 174 | assert_eq!(parse(Some("_filter=name=seray,active=true")), Some(queries)); 175 | } 176 | 177 | #[test] 178 | fn parse_q_test() { 179 | let mut queries = Queries::new(); 180 | { 181 | queries.q = Some("seray".to_string()); 182 | } 183 | assert_eq!(parse(Some("_q=seray")), Some(queries)); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/database/mod.rs: -------------------------------------------------------------------------------- 1 | //! # database 2 | //! This module holds necessary structs and functions to accomplish database tasks. 3 | 4 | mod read; 5 | mod insert; 6 | mod update; 7 | mod delete; 8 | pub mod query_api; 9 | 10 | pub mod errors; 11 | 12 | use serde_json; 13 | use slog::Logger; 14 | use configuration; 15 | use weld::ROOT_LOGGER; 16 | use std::vec::Vec; 17 | use std::fs::File; 18 | use std::io::Read; 19 | use std::io::Write; 20 | use std::fs::OpenOptions; 21 | use serde_json::Value; 22 | use serde_json::Value::{Array, Object}; 23 | use self::errors::Errors; 24 | 25 | /// This is a very simple database struct for a json db. 26 | /// It works really simple. Loads all data to memory. 27 | /// Does all the operations in the memory and writes the final object to the file at the end. 28 | // TODO: Use serde better for indexed updates over file. 29 | #[derive(Debug)] 30 | pub struct Database { 31 | logger: Logger, 32 | configuration: Option, 33 | data: serde_json::Value, 34 | } 35 | 36 | impl Database { 37 | /// Creates an instance of the Database. 38 | pub fn new() -> Database { 39 | Database { 40 | logger: ROOT_LOGGER.new(o!()), 41 | configuration: None, 42 | data: serde_json::Value::Null, 43 | } 44 | } 45 | 46 | /// Loads the database according to the given configuration. 47 | pub fn load(&mut self, configuration: &configuration::Database) { 48 | self.configuration = Some(configuration.clone()); 49 | self.open(); 50 | } 51 | /// Parses the file and loads in to the memory. 52 | /// You have to call this before doing any set of operations. 53 | /// All failed operations results with panic because there is no meaning to continue without a proper db. 54 | pub fn open(&mut self) { 55 | let path = self.configuration.clone().unwrap().path; 56 | info!(self.logger, "Database - Connecting : {:?}", path); 57 | let mut file = File::open(&path).expect("Database - Error Can't read. Terminating..."); 58 | let mut contents = String::new(); 59 | match file.read_to_string(&mut contents) { 60 | Ok(usize) => { 61 | if usize == 0 { 62 | panic!( 63 | "Database - Error It is empty. You can't mock API with it. \ 64 | Terminating..." 65 | ); 66 | } 67 | } 68 | Err(e) => panic!( 69 | "Database - Error You can't mock API with it. Terminating...{}", 70 | e 71 | ), 72 | } 73 | let new_data: serde_json::Value = serde_json::from_str(&contents) 74 | .expect("Invalid JSON format. Check provided db. Terminating..."); 75 | self.data = new_data; 76 | info!(self.logger, "Database - Ok : {:?}", path); 77 | } 78 | 79 | /// A simple function to parse id or return -1. 80 | pub fn decide_id(val: &String) -> i64 { 81 | match i64::from_str_radix(val.as_str(), 10) { 82 | Ok(parsed) => parsed, 83 | Err(_) => -1, 84 | } 85 | } 86 | 87 | /// This is the main access function to reach the desired data from the whole database. 88 | /// Tries to find the keys provided in the database recursively. 89 | /// Returns mutable references to allow manipulation. 90 | pub fn get_object<'per_req>( 91 | keys: &mut Vec, 92 | json_object: &'per_req mut Value, 93 | ) -> Result<&'per_req mut Value, Errors> { 94 | if keys.len() == 0 { 95 | return Ok(json_object); 96 | } 97 | let key = keys.remove(0); 98 | match json_object { 99 | &mut Array(ref mut array) => { 100 | let id = Self::decide_id(&key); 101 | if let Some(idx) = Database::find_index(&array, &id) { 102 | if let Some(obj) = array.get_mut(idx) { 103 | return Self::get_object(keys, obj); 104 | } else { 105 | return Err(Errors::NotFound(format!("Read - Error path: {:?} ", &key))); 106 | } 107 | } else { 108 | return Err(Errors::NotFound(format!("Read - Error path: {:?} ", &key))); 109 | } 110 | } 111 | &mut Object(ref mut obj) => { 112 | if let Some(obj) = obj.get_mut(key.as_str()) { 113 | return Self::get_object(keys, obj); 114 | } else { 115 | return Err(Errors::NotFound(format!("Read - Error path: {:?} ", &key))); 116 | } 117 | } 118 | _ => { 119 | return Err(Errors::NotFound(format!("Read - Error path: {:?} ", &key))); 120 | } 121 | }; 122 | } 123 | 124 | /// Flush all the changes to the file. 125 | pub fn flush(&mut self) { 126 | let new_db = &serde_json::to_string(&self.data).unwrap(); 127 | debug!(&self.logger, "Flush - Started"); 128 | let bytes = new_db.as_bytes(); 129 | let mut file = OpenOptions::new() 130 | .read(true) 131 | .write(true) 132 | .open(&self.configuration.clone().unwrap().path) 133 | .unwrap(); 134 | match file.set_len(0) { 135 | Ok(_) => match file.write_all(bytes) { 136 | Ok(_) => { 137 | let result = file.sync_all(); 138 | info!( 139 | &self.logger, 140 | "Flush - Ok File {:?} Result: {:?}", &file, &result 141 | ); 142 | } 143 | Err(e) => error!( 144 | &self.logger, 145 | "Flush - Error Can't write file File: {:?} Error: {:?}", &file, e 146 | ), 147 | }, 148 | Err(e) => error!( 149 | &self.logger, 150 | "Flush - Error Can't set file size File: {:?} Error {:?}", &file, e 151 | ), 152 | } 153 | } 154 | 155 | /// Find the index of the element with the given target id. 156 | fn find_index(vec: &Vec, target: &i64) -> Option { 157 | let mut index = 0; 158 | for value in vec.iter() { 159 | let map = value.as_object().unwrap(); 160 | let id = map.get("id").unwrap().as_i64().unwrap(); 161 | if id.eq(target) { 162 | return Some(index); 163 | } 164 | index += 1; 165 | } 166 | None 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/service/mod.rs: -------------------------------------------------------------------------------- 1 | //! # service 2 | //! This is the service layer of the application. 3 | //! All requests taken by the server will be consumed by the services under this module. 4 | pub mod query_api; 5 | pub mod utils; 6 | 7 | use hyper; 8 | use hyper::server::{Request, Response, Service}; 9 | use hyper::{Delete, Error, Get, Post, Put, StatusCode}; 10 | use slog; 11 | use weld; 12 | 13 | use database::errors::Errors::{Conflict, NotFound}; 14 | use futures::{Future, Stream}; 15 | use futures_cpupool::CpuPool; 16 | use serde_json::{from_slice, to_value}; 17 | 18 | use self::query_api::Queries; 19 | 20 | type FutureBox = Box>; 21 | 22 | /// A Simple struct to represent rest service. 23 | pub struct RestService { 24 | /// Logger of the service. 25 | pub logger: slog::Logger, 26 | /// Thread pool sent from the Server in order to manage threading. 27 | pub thread_pool: CpuPool, 28 | } 29 | 30 | impl RestService { 31 | #[inline] 32 | /// Gets the desired data from the path and returns. 33 | /// To reach service Http Method must be GET. 34 | /// It works in acceptor thread. Since it is fast for small databases it is ok to work like this. 35 | /// Later all services must be handled under a new thread. 36 | fn get(paths: Vec, queries: Option, response: Response) -> FutureBox { 37 | let mut db = weld::DATABASE.lock().unwrap(); 38 | match db.read(&mut paths.clone(), queries) { 39 | Ok(record) => return utils::success(response, StatusCode::Ok, &record), 40 | Err(error) => match error { 41 | NotFound(msg) => utils::error(response, StatusCode::NotFound, msg.as_str()), 42 | _ => utils::error(response, StatusCode::InternalServerError, "Server Error"), 43 | }, 44 | } 45 | } 46 | 47 | /// Creates the resource to the desired path and returns. 48 | /// To reach service Http Method must be POST. 49 | /// It reads request in acceptor thread. Does all other operations at a differend thread. 50 | #[inline] 51 | fn post(req: Request, paths: Vec, response: Response) -> FutureBox { 52 | Box::new(req.body().concat2().and_then(move |body| { 53 | let mut db = weld::DATABASE.lock().unwrap(); 54 | match from_slice(body.to_vec().as_slice()) { 55 | Ok(payload) => match db.insert(&mut paths.clone(), payload) { 56 | Ok(record) => { 57 | db.flush(); 58 | utils::success(response, StatusCode::Created, &record) 59 | } 60 | Err(error) => match error { 61 | NotFound(msg) => utils::error(response, StatusCode::NotFound, msg.as_str()), 62 | Conflict(msg) => utils::error(response, StatusCode::Conflict, msg.as_str()), 63 | }, 64 | }, 65 | Err(_) => utils::error( 66 | response, 67 | StatusCode::BadRequest, 68 | "Request body must be a valid json.", 69 | ), 70 | } 71 | })) 72 | } 73 | 74 | /// Updates the resource at the desired path and returns. 75 | /// To reach service Http Method must be PUT. 76 | /// It reads request in acceptor thread. Does all other operations at a differend thread. 77 | #[inline] 78 | fn put(req: Request, paths: Vec, response: Response) -> FutureBox { 79 | Box::new(req.body().concat2().and_then(move |body| { 80 | let mut db = weld::DATABASE.lock().unwrap(); 81 | match from_slice(body.to_vec().as_slice()) { 82 | Ok(payload) => match db.update(&mut paths.clone(), payload) { 83 | Ok(record) => { 84 | db.flush(); 85 | info!(weld::ROOT_LOGGER, "PUT: DB Event"); 86 | return utils::success(response, StatusCode::Ok, &record); 87 | } 88 | Err(error) => { 89 | if let NotFound(msg) = error { 90 | utils::error(response, StatusCode::NotFound, msg.as_str()) 91 | } else { 92 | utils::error(response, StatusCode::InternalServerError, "Server Error") 93 | } 94 | } 95 | }, 96 | Err(_) => utils::error( 97 | response, 98 | StatusCode::BadRequest, 99 | "Request body must be a valid json.", 100 | ), 101 | } 102 | })) 103 | } 104 | 105 | /// Deletes the resource at the desired path and returns. 106 | /// To reach service Http Method must be DELETE. 107 | /// It reads request in acceptor thread. Does all other operations at a differend thread. 108 | #[inline] 109 | fn delete(paths: Vec, response: Response) -> FutureBox { 110 | let mut db = weld::DATABASE.lock().unwrap(); 111 | match db.delete(&mut paths.clone()) { 112 | Ok(record) => { 113 | db.flush(); 114 | return utils::success(response, StatusCode::Ok, &record); 115 | } 116 | Err(error) => { 117 | if let NotFound(msg) = error { 118 | return utils::error(response, StatusCode::NotFound, msg.as_str()); 119 | } else { 120 | utils::error(response, StatusCode::NotFound, "Server Error") 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | /// Service implementation for the RestService. It is required by tokio to make it work with our service. 128 | impl Service for RestService { 129 | /// Type of the request 130 | type Request = Request; 131 | /// Type of the response 132 | type Response = Response; 133 | /// Type of the error 134 | type Error = hyper::Error; 135 | 136 | type Future = FutureBox; 137 | /// Type of the future 138 | 139 | /// Entry point of the service. Pases path nad method and redirect to the correct function. 140 | fn call(&self, req: Request) -> FutureBox { 141 | let path_parts = utils::split_path(req.path().to_string()); 142 | let response = Response::new(); 143 | // Table list 144 | if let 0 = path_parts.len() { 145 | // TODO: return as homepage with links 146 | let db = weld::DATABASE.lock().unwrap(); 147 | utils::success(response, StatusCode::Ok, &to_value(&db.tables()).unwrap()) 148 | } else { 149 | // Record list or record 150 | match req.method() { 151 | &Get => { 152 | let queries = query_api::parse(req.query()); 153 | Self::get(path_parts, queries, response) 154 | } 155 | &Post => Self::post(req, path_parts, response), 156 | &Put => Self::put(req, path_parts, response), 157 | &Delete => Self::delete(path_parts, response), 158 | _ => utils::error(response, StatusCode::MethodNotAllowed, "Method Not Allowed"), 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/database/query_api/filter.rs: -------------------------------------------------------------------------------- 1 | //! # filter 2 | //! All necessery functions for appliying filtering to json results. 3 | use serde_json; 4 | use serde_json::{Error, Value}; 5 | //use serde_json::error::ErrorCode::Message; 6 | use service::query_api::Queries; 7 | 8 | /// filter array according to the query api 9 | pub fn apply(obj: &mut Value, queries: &Queries) { 10 | let ref filters = queries.filter; 11 | if let &mut Value::Array(ref mut arr) = obj { 12 | let mut size = arr.len(); 13 | let mut i = 0; 14 | while i < size { 15 | let mut valid = true; 16 | if let Some(item) = arr.get(i) { 17 | for q in filters { 18 | if let Some(field) = item.get(&q.key) { 19 | valid = is_valid(&q.op, field, &q.value); 20 | if !valid { 21 | break; 22 | } 23 | } 24 | } 25 | } else { 26 | break; 27 | } 28 | if !valid { 29 | arr.remove(i); 30 | size -= 1; 31 | } else { 32 | i += 1; 33 | } 34 | } 35 | } else { 36 | } 37 | } 38 | 39 | fn convert_2_same_type(field_value: &Value, query_value: &str) -> Result { 40 | match field_value { 41 | &Value::Bool(_) => return serde_json::to_value(query_value == "true"), 42 | &Value::Number(_) => { 43 | return serde_json::to_value(i64::from_str_radix(query_value, 10).ok()) 44 | } 45 | &Value::String(_) => return serde_json::to_value(query_value), 46 | &Value::Null => return serde_json::to_value(query_value), 47 | &Value::Array(_) => return serde_json::to_value(query_value), 48 | &Value::Object(_) => return serde_json::to_value(query_value), 49 | } 50 | } 51 | 52 | fn is_valid(op: &str, field_value: &Value, query_value: &str) -> bool { 53 | let mut valid = true; 54 | let parsed_q = convert_2_same_type(field_value, query_value); 55 | if let Ok(qval) = parsed_q { 56 | match op { 57 | "=" => valid = field_value == &qval, 58 | "!=" => valid = field_value != &qval, 59 | ">" => { 60 | if let Some(num_f) = field_value.as_i64() { 61 | if let Some(num_q) = qval.as_i64() { 62 | valid = num_f > num_q; 63 | } 64 | } 65 | } 66 | ">=" => { 67 | if let Some(num_f) = field_value.as_i64() { 68 | if let Some(num_q) = qval.as_i64() { 69 | valid = num_f >= num_q; 70 | } 71 | } 72 | } 73 | "<" => { 74 | if let Some(num_f) = field_value.as_i64() { 75 | if let Some(num_q) = qval.as_i64() { 76 | valid = num_f < num_q; 77 | } 78 | } 79 | } 80 | "<=" => { 81 | if let Some(num_f) = field_value.as_i64() { 82 | if let Some(num_q) = qval.as_i64() { 83 | valid = num_f <= num_q; 84 | } 85 | } 86 | } 87 | "~=" => { 88 | println!("checking {:?}~={:?}", field_value, &qval); 89 | if let Some(str_f) = field_value.as_str() { 90 | if let Some(str_q) = qval.as_str() { 91 | valid = str_f.starts_with(str_q); 92 | } 93 | } 94 | } 95 | "|=" => { 96 | let parts = query_value.split("|"); 97 | for part in parts { 98 | valid = false; 99 | if let Ok(qval) = convert_2_same_type(field_value, part) { 100 | println!("checking {:?}|={:?}", field_value, &qval); 101 | if field_value == &qval { 102 | valid = true; 103 | break; 104 | } 105 | } 106 | } 107 | } 108 | _ => {} 109 | } 110 | } 111 | valid 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | use service::query_api::Query; 118 | 119 | fn get_json() -> Value { 120 | let json_string = r#"[ 121 | { 122 | "name":"seray", 123 | "age":31, 124 | "active":true, 125 | "password":"123" 126 | }, 127 | { 128 | "name":"kamil", 129 | "age":900, 130 | "active":false, 131 | "password":"333" 132 | }, 133 | { 134 | "name":"hasan", 135 | "age":25, 136 | "active":true, 137 | "password":"321" 138 | } 139 | ]"#; 140 | serde_json::from_str(&json_string).unwrap() 141 | } 142 | 143 | #[test] 144 | fn apply_eq_test() { 145 | let mut queries = Queries::new(); 146 | { 147 | let filter = &mut queries.filter; 148 | filter.push(Query::new("name", "=", "seray")); 149 | filter.push(Query::new("active", "=", "true")); 150 | } 151 | let expected: Value = serde_json::from_str( 152 | &r#"[ 153 | { 154 | "name":"seray", 155 | "age":31, 156 | "active":true, 157 | "password":"123" 158 | }]"#, 159 | ) 160 | .unwrap(); 161 | let json = &mut get_json(); 162 | apply(json, &queries); 163 | assert_eq!(json.clone(), expected); 164 | } 165 | #[test] 166 | fn apply_ne_test() { 167 | let mut queries = Queries::new(); 168 | { 169 | let filter = &mut queries.filter; 170 | filter.push(Query::new("name", "!=", "seray")); 171 | filter.push(Query::new("active", "!=", "true")); 172 | } 173 | let expected: Value = serde_json::from_str( 174 | &r#"[ 175 | { 176 | "name":"kamil", 177 | "age":900, 178 | "active":false, 179 | "password":"333" 180 | }]"#, 181 | ) 182 | .unwrap(); 183 | let json = &mut get_json(); 184 | apply(json, &queries); 185 | assert_eq!(json.clone(), expected); 186 | } 187 | 188 | #[test] 189 | fn apply_gt_lt_test() { 190 | let mut queries = Queries::new(); 191 | { 192 | let filter = &mut queries.filter; 193 | filter.push(Query::new("age", "<", "500")); 194 | filter.push(Query::new("age", ">", "26")); 195 | } 196 | let expected: Value = serde_json::from_str( 197 | &r#"[ 198 | { 199 | "name":"seray", 200 | "age":31, 201 | "active":true, 202 | "password":"123" 203 | }]"#, 204 | ) 205 | .unwrap(); 206 | let json = &mut get_json(); 207 | apply(json, &queries); 208 | assert_eq!(json.clone(), expected); 209 | } 210 | #[test] 211 | fn apply_gte_lte_test() { 212 | let mut queries = Queries::new(); 213 | { 214 | let filter = &mut queries.filter; 215 | filter.push(Query::new("age", "<=", "31")); 216 | filter.push(Query::new("age", ">=", "31")); 217 | } 218 | let expected: Value = serde_json::from_str( 219 | &r#"[ 220 | { 221 | "name":"seray", 222 | "age":31, 223 | "active":true, 224 | "password":"123" 225 | }]"#, 226 | ) 227 | .unwrap(); 228 | let json = &mut get_json(); 229 | apply(json, &queries); 230 | assert_eq!(json.clone(), expected); 231 | } 232 | #[test] 233 | fn apply_like_test() { 234 | let mut queries = Queries::new(); 235 | { 236 | let filter = &mut queries.filter; 237 | filter.push(Query::new("password", "~=", "3")); 238 | } 239 | let expected: Value = serde_json::from_str( 240 | &r#"[ 241 | { 242 | "name":"kamil", 243 | "age":900, 244 | "active":false, 245 | "password":"333" 246 | }, 247 | { 248 | "name":"hasan", 249 | "age":25, 250 | "active":true, 251 | "password":"321" 252 | } 253 | ]"#, 254 | ) 255 | .unwrap(); 256 | let json = &mut get_json(); 257 | apply(json, &queries); 258 | assert_eq!(json.clone(), expected); 259 | } 260 | 261 | #[test] 262 | fn apply_in_test() { 263 | let mut queries = Queries::new(); 264 | { 265 | let filter = &mut queries.filter; 266 | filter.push(Query::new("name", "|=", "kamil|hasan")); 267 | } 268 | let expected: Value = serde_json::from_str( 269 | &r#"[ 270 | { 271 | "name":"kamil", 272 | "age":900, 273 | "active":false, 274 | "password":"333" 275 | }, 276 | { 277 | "name":"hasan", 278 | "age":25, 279 | "active":true, 280 | "password":"321" 281 | } 282 | ]"#, 283 | ) 284 | .unwrap(); 285 | let json = &mut get_json(); 286 | apply(json, &queries); 287 | assert_eq!(json.clone(), expected); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weld 2 | Full fake REST API generator. 3 | 4 | [![weldmock' current version badge](https://img.shields.io/crates/v/weldmock.svg)](https://crates.io/crates/weldmock) 5 | [![Build Status](https://travis-ci.org/serayuzgur/weld.svg?branch=master)](https://travis-ci.org/serayuzgur/weld) 6 | [![codecov](https://codecov.io/gh/serayuzgur/weld/branch/master/graph/badge.svg)](https://codecov.io/gh/serayuzgur/weld) 7 | 8 | 9 | This project is heavily inspired by [json-server](https://github.com/typicode/json-server), written with rust. 10 | 11 | ## Synopsis 12 | Our first aim is to generate a fake api from the given data source (JSON). 13 | It may have bugs, missing features but if you contribute they all will be fixed. 14 | 15 | ## Version [CHANGELOG](./CHANGELOG.md) 16 | 17 | ## Techs 18 | * [**Serde**](https://github.com/serde-rs/serde) for json parsing. 19 | * [**Hyper**](https://github.com/hyperium/hyper) for serving. 20 | * [**slog**](https://github.com/slog-rs/slog) for logging. 21 | 22 | 23 | ## Installation 24 | 1. Download and install **Rust** from [here](https://www.rust-lang.org/en-US/downloads.html) 25 | 2. Download and install **Cargo** from [here](http://doc.crates.io/) 26 | 3. Clone and run the project. 27 | 28 | ```bash 29 | git clone https://github.com/serayuzgur/weld.git 30 | cd weld 31 | cargo run 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Running 37 | Executable can take configuration path otherwise it will use default `./weld.json`. If we take project folder as root, commands should look like one of these. If you you use `cargo build --release` version change `debug` with `release`. 38 | 39 | ```bash 40 | ./target/debug/weld 41 | ./target/debug/weld weld.json 42 | ./target/debug/weld .json 43 | 44 | ./target/release/weld 45 | ./target/release/weld weld.json 46 | ./target/release/weld .json 47 | ``` 48 | 49 | ### Configuration 50 | Configuration file is a very simple json file which is responsible to hold server and database properties. 51 | 52 | ```json 53 | { 54 | "server": { 55 | "host": "127.0.0.1", 56 | "port": 8080 57 | }, 58 | "database": { 59 | "path": "db.json" 60 | } 61 | } 62 | ``` 63 | 64 | ### Database 65 | Database is a simple json file. 66 | 67 | ```json 68 | { 69 | "owner": { 70 | "name": "seray", 71 | "surname": "uzgur" 72 | }, 73 | "posts": [ 74 | { 75 | "author": { 76 | "name": "seray", 77 | "surname": "uzgur" 78 | }, 79 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 80 | "id": 1, 81 | "tags": [ 82 | { 83 | "id": 1, 84 | "name": "tech" 85 | }, 86 | { 87 | "id": 2, 88 | "name": "web" 89 | } 90 | ], 91 | "title": "Rust Rocks!" 92 | }, 93 | { 94 | "author": { 95 | "name": "kamil", 96 | "surname": "bukum" 97 | }, 98 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 99 | "id": 2, 100 | "tags": [ 101 | { 102 | "id": 1, 103 | "name": "tech" 104 | }, 105 | { 106 | "id": 2, 107 | "name": "web" 108 | } 109 | ], 110 | "title": "TypeScript is Awesome" 111 | } 112 | ], 113 | "version": "10.1.1" 114 | } 115 | ``` 116 | 117 | Here the `owner` and `posts` are tables. They can be empty arrays/objects but they must exist as is. 118 | 119 | **NOTE :** `id`: Column is a must, all parsing uses it. 120 | 121 | ### API 122 | Api usage is pretty simple. For now it does not support filters one other query params. Here is the list of the calls you can make with examples. 123 | 124 | * Get List \:\/\ GET 125 | * Get Record \:\/\/\ GET 126 | * Insert Record \:\/\ POST 127 | * Update Record \:\/\/\ PUT 128 | * Delete Record \:\/\/\ DELETE 129 | * Get Nested \:\/\/\/\... GET 130 | 131 | #### Get List 132 | ```json 133 | url: http://127.0.0.1:8080/posts 134 | method: GET 135 | body: empty 136 | 137 | response: 138 | [ 139 | { 140 | "author": "serayuzgur", 141 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 142 | "id": 1, 143 | "title": "Rust Rocks!" 144 | }, 145 | { 146 | "author": "kamilbukum", 147 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 148 | "id": 2, 149 | "title": "TypeScript is Awesome" 150 | } 151 | ] 152 | ``` 153 | #### Get Record 154 | ```json 155 | url: http://127.0.0.1:8080/posts/1 156 | method: GET 157 | body: empty 158 | 159 | response: 160 | { 161 | "author": { 162 | "name": "seray", 163 | "surname": "uzgur" 164 | }, 165 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 166 | "id": 1, 167 | "tags": [ 168 | { 169 | "id": 1, 170 | "name": "tech" 171 | }, 172 | { 173 | "id": 2, 174 | "name": "web" 175 | } 176 | ], 177 | "title": "Rust Rocks!" 178 | } 179 | ``` 180 | #### Insert Record 181 | ```json 182 | url: http://127.0.0.1:8080/posts 183 | method: POST 184 | body: 185 | { 186 | "author": { 187 | "name": "hasan", 188 | "surname": "mumin" 189 | }, 190 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 191 | "id": 3, 192 | "tags": [ 193 | { 194 | "id": 1, 195 | "name": "tech" 196 | }, 197 | { 198 | "id": 2, 199 | "name": "web" 200 | } 201 | ], 202 | "title": "KendoUI Rocks!" 203 | } 204 | 205 | response: 206 | { 207 | "author": { 208 | "name": "hasan", 209 | "surname": "mumin" 210 | }, 211 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 212 | "id": 3, 213 | "tags": [ 214 | { 215 | "id": 1, 216 | "name": "tech" 217 | }, 218 | { 219 | "id": 2, 220 | "name": "web" 221 | } 222 | ], 223 | "title": "KendoUI Rocks!" 224 | } 225 | ``` 226 | #### Update Record 227 | ```json 228 | url: http://127.0.0.1:8080/posts/3 229 | method: PUT 230 | body: 231 | { 232 | "author": { 233 | "name": "hasan", 234 | "surname": "mumin" 235 | }, 236 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 237 | "id": 3, 238 | "tags": [ 239 | { 240 | "id": 1, 241 | "name": "tech" 242 | }, 243 | { 244 | "id": 2, 245 | "name": "web" 246 | } 247 | ], 248 | "title": "KendoUI Rocks!" 249 | } 250 | 251 | response: 252 | { 253 | "author": { 254 | "name": "hasan", 255 | "surname": "mumin" 256 | }, 257 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 258 | "id": 3, 259 | "tags": [ 260 | { 261 | "id": 1, 262 | "name": "tech" 263 | }, 264 | { 265 | "id": 2, 266 | "name": "web" 267 | } 268 | ], 269 | "title": "Angular Rocks!" 270 | } 271 | ``` 272 | 273 | #### Delete Record 274 | ```json 275 | url: http://127.0.0.1:8080/posts/3 276 | method: DELETE 277 | body: empty 278 | 279 | response: 280 | { 281 | "author": { 282 | "name": "hasan", 283 | "surname": "mumin" 284 | }, 285 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero.", 286 | "id": 3, 287 | "tags": [ 288 | { 289 | "id": 1, 290 | "name": "tech" 291 | }, 292 | { 293 | "id": 2, 294 | "name": "web" 295 | } 296 | ], 297 | "title": "KendoUI Rocks!" 298 | } 299 | ``` 300 | #### Get Nested 301 | ```json 302 | url: http://127.0.0.1:8080/posts/1/author 303 | method: GET 304 | body: empty 305 | 306 | response: 307 | { 308 | "name": "seray", 309 | "surname": "uzgur" 310 | } 311 | ``` 312 | ```json 313 | url: http://127.0.0.1:8080/posts/1/author/name 314 | method: GET 315 | body: empty 316 | 317 | response: 318 | "seray" 319 | ``` 320 | ```json 321 | url: http://127.0.0.1:8080/posts/1/tags/1 322 | method: GET 323 | body: empty 324 | 325 | response: 326 | { 327 | "id": 1, 328 | "name": "tech" 329 | } 330 | ``` 331 | 332 | #### Query API 333 | ##### Field selection 334 | Give the API consumer the ability to choose returned fields. This will also reduce the network traffic and speed up the usage of the API. 335 | 336 | ``` 337 | GET /cars?fields=manufacturer,model,id,color 338 | ``` 339 | 340 | ##### Paging 341 | 342 | ``` 343 | GET /cars?_offset=10&_limit=5 344 | ``` 345 | * Add _offset and _limit (an X-Total-Count header is included in the response). 346 | 347 | * To send the total entries back to the user use the custom HTTP header: X-Total-Count. 348 | 349 | * Content-Range offset – limit / count. 350 | 351 | * offset: Index of the first element returned by the request. 352 | 353 | * limit: Index of the last element returned by the request. 354 | 355 | * count: Total number of elements in the collection. 356 | 357 | * Accept-Range resource max. 358 | 359 | * resource: The type of pagination. Must remind of the resource in use, e.g: client, order, restaurant, … 360 | 361 | * max : Maximum number of elements that can be returned in a single request. 362 | 363 | ##### Sorting 364 | 365 | * Allow ascending and descending sorting over multiple fields. 366 | * Use sort with underscore as `_sort`. 367 | * In code, descending describe as ` - `, ascending describe as ` + `. 368 | 369 | ```GET /cars?_sort=-manufactorer,+model``` 370 | 371 | ##### Operators 372 | * Add `_filter` query parameter and continue with field names,operations and values separated by `,`. 373 | * Pattern `_filter=`. 374 | * Supported operations. 375 | * `=` equal 376 | * `!=` not equal 377 | * `<` less 378 | * `<=` less or equals 379 | * `>` greater 380 | * `>=` greater or equals 381 | * `~=` like 382 | * `|=` in (values must be separated with `|` 383 | 384 | ```GET http://127.0.0.1:8080/robe/users?_filter=name=seray,active=true``` 385 | 386 | ##### Full-text search 387 | 388 | * Add `_q`. 389 | 390 | ```GET /cars?_q=nissan``` 391 | 392 | ## License 393 | 394 | The MIT License (MIT) Copyright (c) 2017 Seray Uzgur 395 | 396 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 397 | 398 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 399 | 400 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi 0.3.9", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "base64" 24 | version = "0.9.3" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" 27 | dependencies = [ 28 | "byteorder", 29 | "safemem", 30 | ] 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "1.3.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 37 | 38 | [[package]] 39 | name = "byteorder" 40 | version = "1.4.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 43 | 44 | [[package]] 45 | name = "bytes" 46 | version = "0.4.12" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 49 | dependencies = [ 50 | "byteorder", 51 | "iovec", 52 | ] 53 | 54 | [[package]] 55 | name = "bytes" 56 | version = "1.2.1" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 59 | 60 | [[package]] 61 | name = "cfg-if" 62 | version = "0.1.10" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 65 | 66 | [[package]] 67 | name = "cfg-if" 68 | version = "1.0.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 71 | 72 | [[package]] 73 | name = "cloudabi" 74 | version = "0.0.3" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 77 | dependencies = [ 78 | "bitflags", 79 | ] 80 | 81 | [[package]] 82 | name = "crossbeam-channel" 83 | version = "0.5.6" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 86 | dependencies = [ 87 | "cfg-if 1.0.0", 88 | "crossbeam-utils 0.8.12", 89 | ] 90 | 91 | [[package]] 92 | name = "crossbeam-deque" 93 | version = "0.7.4" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" 96 | dependencies = [ 97 | "crossbeam-epoch", 98 | "crossbeam-utils 0.7.2", 99 | "maybe-uninit", 100 | ] 101 | 102 | [[package]] 103 | name = "crossbeam-epoch" 104 | version = "0.8.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 107 | dependencies = [ 108 | "autocfg", 109 | "cfg-if 0.1.10", 110 | "crossbeam-utils 0.7.2", 111 | "lazy_static", 112 | "maybe-uninit", 113 | "memoffset", 114 | "scopeguard", 115 | ] 116 | 117 | [[package]] 118 | name = "crossbeam-queue" 119 | version = "0.2.3" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 122 | dependencies = [ 123 | "cfg-if 0.1.10", 124 | "crossbeam-utils 0.7.2", 125 | "maybe-uninit", 126 | ] 127 | 128 | [[package]] 129 | name = "crossbeam-utils" 130 | version = "0.7.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 133 | dependencies = [ 134 | "autocfg", 135 | "cfg-if 0.1.10", 136 | "lazy_static", 137 | ] 138 | 139 | [[package]] 140 | name = "crossbeam-utils" 141 | version = "0.8.12" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" 144 | dependencies = [ 145 | "cfg-if 1.0.0", 146 | ] 147 | 148 | [[package]] 149 | name = "dirs-next" 150 | version = "2.0.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 153 | dependencies = [ 154 | "cfg-if 1.0.0", 155 | "dirs-sys-next", 156 | ] 157 | 158 | [[package]] 159 | name = "dirs-sys-next" 160 | version = "0.1.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 163 | dependencies = [ 164 | "libc", 165 | "redox_users", 166 | "winapi 0.3.9", 167 | ] 168 | 169 | [[package]] 170 | name = "fnv" 171 | version = "1.0.7" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 174 | 175 | [[package]] 176 | name = "fuchsia-cprng" 177 | version = "0.1.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 180 | 181 | [[package]] 182 | name = "fuchsia-zircon" 183 | version = "0.3.3" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 186 | dependencies = [ 187 | "bitflags", 188 | "fuchsia-zircon-sys", 189 | ] 190 | 191 | [[package]] 192 | name = "fuchsia-zircon-sys" 193 | version = "0.3.3" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 196 | 197 | [[package]] 198 | name = "futures" 199 | version = "0.1.31" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" 202 | 203 | [[package]] 204 | name = "futures-cpupool" 205 | version = "0.1.8" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 208 | dependencies = [ 209 | "futures", 210 | "num_cpus", 211 | ] 212 | 213 | [[package]] 214 | name = "getrandom" 215 | version = "0.2.8" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 218 | dependencies = [ 219 | "cfg-if 1.0.0", 220 | "libc", 221 | "wasi 0.11.0+wasi-snapshot-preview1", 222 | ] 223 | 224 | [[package]] 225 | name = "hermit-abi" 226 | version = "0.1.19" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 229 | dependencies = [ 230 | "libc", 231 | ] 232 | 233 | [[package]] 234 | name = "httparse" 235 | version = "1.8.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 238 | 239 | [[package]] 240 | name = "hyper" 241 | version = "0.11.27" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "34a590ca09d341e94cddf8e5af0bbccde205d5fbc2fa3c09dd67c7f85cea59d7" 244 | dependencies = [ 245 | "base64", 246 | "bytes 0.4.12", 247 | "futures", 248 | "futures-cpupool", 249 | "httparse", 250 | "iovec", 251 | "language-tags", 252 | "log 0.4.17", 253 | "mime", 254 | "net2", 255 | "percent-encoding", 256 | "relay", 257 | "time 0.1.44", 258 | "tokio-core", 259 | "tokio-io", 260 | "tokio-proto", 261 | "tokio-service", 262 | "unicase", 263 | "want", 264 | ] 265 | 266 | [[package]] 267 | name = "iovec" 268 | version = "0.1.4" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 271 | dependencies = [ 272 | "libc", 273 | ] 274 | 275 | [[package]] 276 | name = "itoa" 277 | version = "1.0.4" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 280 | 281 | [[package]] 282 | name = "kernel32-sys" 283 | version = "0.2.2" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 286 | dependencies = [ 287 | "winapi 0.2.8", 288 | "winapi-build", 289 | ] 290 | 291 | [[package]] 292 | name = "language-tags" 293 | version = "0.2.2" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 296 | 297 | [[package]] 298 | name = "lazy_static" 299 | version = "1.4.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 302 | 303 | [[package]] 304 | name = "libc" 305 | version = "0.2.135" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" 308 | 309 | [[package]] 310 | name = "lock_api" 311 | version = "0.3.4" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 314 | dependencies = [ 315 | "scopeguard", 316 | ] 317 | 318 | [[package]] 319 | name = "log" 320 | version = "0.3.9" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 323 | dependencies = [ 324 | "log 0.4.17", 325 | ] 326 | 327 | [[package]] 328 | name = "log" 329 | version = "0.4.17" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 332 | dependencies = [ 333 | "cfg-if 1.0.0", 334 | ] 335 | 336 | [[package]] 337 | name = "maybe-uninit" 338 | version = "2.0.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 341 | 342 | [[package]] 343 | name = "memoffset" 344 | version = "0.5.6" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" 347 | dependencies = [ 348 | "autocfg", 349 | ] 350 | 351 | [[package]] 352 | name = "mime" 353 | version = "0.3.16" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 356 | 357 | [[package]] 358 | name = "mio" 359 | version = "0.6.23" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 362 | dependencies = [ 363 | "cfg-if 0.1.10", 364 | "fuchsia-zircon", 365 | "fuchsia-zircon-sys", 366 | "iovec", 367 | "kernel32-sys", 368 | "libc", 369 | "log 0.4.17", 370 | "miow", 371 | "net2", 372 | "slab 0.4.7", 373 | "winapi 0.2.8", 374 | ] 375 | 376 | [[package]] 377 | name = "mio-uds" 378 | version = "0.6.8" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 381 | dependencies = [ 382 | "iovec", 383 | "libc", 384 | "mio", 385 | ] 386 | 387 | [[package]] 388 | name = "miow" 389 | version = "0.2.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 392 | dependencies = [ 393 | "kernel32-sys", 394 | "net2", 395 | "winapi 0.2.8", 396 | "ws2_32-sys", 397 | ] 398 | 399 | [[package]] 400 | name = "net2" 401 | version = "0.2.38" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" 404 | dependencies = [ 405 | "cfg-if 0.1.10", 406 | "libc", 407 | "winapi 0.3.9", 408 | ] 409 | 410 | [[package]] 411 | name = "num_cpus" 412 | version = "1.13.1" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 415 | dependencies = [ 416 | "hermit-abi", 417 | "libc", 418 | ] 419 | 420 | [[package]] 421 | name = "num_threads" 422 | version = "0.1.6" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 425 | dependencies = [ 426 | "libc", 427 | ] 428 | 429 | [[package]] 430 | name = "once_cell" 431 | version = "1.15.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 434 | 435 | [[package]] 436 | name = "parking_lot" 437 | version = "0.9.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" 440 | dependencies = [ 441 | "lock_api", 442 | "parking_lot_core", 443 | "rustc_version", 444 | ] 445 | 446 | [[package]] 447 | name = "parking_lot_core" 448 | version = "0.6.2" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" 451 | dependencies = [ 452 | "cfg-if 0.1.10", 453 | "cloudabi", 454 | "libc", 455 | "redox_syscall 0.1.57", 456 | "rustc_version", 457 | "smallvec 0.6.14", 458 | "winapi 0.3.9", 459 | ] 460 | 461 | [[package]] 462 | name = "percent-encoding" 463 | version = "1.0.1" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 466 | 467 | [[package]] 468 | name = "ppv-lite86" 469 | version = "0.2.16" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 472 | 473 | [[package]] 474 | name = "proc-macro2" 475 | version = "1.0.47" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 478 | dependencies = [ 479 | "unicode-ident", 480 | ] 481 | 482 | [[package]] 483 | name = "quote" 484 | version = "1.0.21" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 487 | dependencies = [ 488 | "proc-macro2", 489 | ] 490 | 491 | [[package]] 492 | name = "rand" 493 | version = "0.3.23" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 496 | dependencies = [ 497 | "libc", 498 | "rand 0.4.6", 499 | ] 500 | 501 | [[package]] 502 | name = "rand" 503 | version = "0.4.6" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 506 | dependencies = [ 507 | "fuchsia-cprng", 508 | "libc", 509 | "rand_core 0.3.1", 510 | "rdrand", 511 | "winapi 0.3.9", 512 | ] 513 | 514 | [[package]] 515 | name = "rand" 516 | version = "0.8.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 519 | dependencies = [ 520 | "libc", 521 | "rand_chacha", 522 | "rand_core 0.6.4", 523 | ] 524 | 525 | [[package]] 526 | name = "rand_chacha" 527 | version = "0.3.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 530 | dependencies = [ 531 | "ppv-lite86", 532 | "rand_core 0.6.4", 533 | ] 534 | 535 | [[package]] 536 | name = "rand_core" 537 | version = "0.3.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 540 | dependencies = [ 541 | "rand_core 0.4.2", 542 | ] 543 | 544 | [[package]] 545 | name = "rand_core" 546 | version = "0.4.2" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 549 | 550 | [[package]] 551 | name = "rand_core" 552 | version = "0.6.4" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 555 | dependencies = [ 556 | "getrandom", 557 | ] 558 | 559 | [[package]] 560 | name = "rdrand" 561 | version = "0.4.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 564 | dependencies = [ 565 | "rand_core 0.3.1", 566 | ] 567 | 568 | [[package]] 569 | name = "redox_syscall" 570 | version = "0.1.57" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 573 | 574 | [[package]] 575 | name = "redox_syscall" 576 | version = "0.2.16" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 579 | dependencies = [ 580 | "bitflags", 581 | ] 582 | 583 | [[package]] 584 | name = "redox_users" 585 | version = "0.4.3" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 588 | dependencies = [ 589 | "getrandom", 590 | "redox_syscall 0.2.16", 591 | "thiserror", 592 | ] 593 | 594 | [[package]] 595 | name = "relay" 596 | version = "0.1.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" 599 | dependencies = [ 600 | "futures", 601 | ] 602 | 603 | [[package]] 604 | name = "rustc_version" 605 | version = "0.2.3" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 608 | dependencies = [ 609 | "semver", 610 | ] 611 | 612 | [[package]] 613 | name = "rustversion" 614 | version = "1.0.9" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 617 | 618 | [[package]] 619 | name = "ryu" 620 | version = "1.0.11" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 623 | 624 | [[package]] 625 | name = "safemem" 626 | version = "0.3.3" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 629 | 630 | [[package]] 631 | name = "scoped-tls" 632 | version = "0.1.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" 635 | 636 | [[package]] 637 | name = "scopeguard" 638 | version = "1.1.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 641 | 642 | [[package]] 643 | name = "semver" 644 | version = "0.9.0" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 647 | dependencies = [ 648 | "semver-parser", 649 | ] 650 | 651 | [[package]] 652 | name = "semver-parser" 653 | version = "0.7.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 656 | 657 | [[package]] 658 | name = "serde" 659 | version = "1.0.145" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 662 | 663 | [[package]] 664 | name = "serde_derive" 665 | version = "1.0.145" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" 668 | dependencies = [ 669 | "proc-macro2", 670 | "quote", 671 | "syn", 672 | ] 673 | 674 | [[package]] 675 | name = "serde_json" 676 | version = "1.0.87" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" 679 | dependencies = [ 680 | "itoa", 681 | "ryu", 682 | "serde", 683 | ] 684 | 685 | [[package]] 686 | name = "slab" 687 | version = "0.3.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 690 | 691 | [[package]] 692 | name = "slab" 693 | version = "0.4.7" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 696 | dependencies = [ 697 | "autocfg", 698 | ] 699 | 700 | [[package]] 701 | name = "slog" 702 | version = "2.7.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" 705 | 706 | [[package]] 707 | name = "slog-async" 708 | version = "2.7.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "766c59b252e62a34651412870ff55d8c4e6d04df19b43eecb2703e417b097ffe" 711 | dependencies = [ 712 | "crossbeam-channel", 713 | "slog", 714 | "take_mut", 715 | "thread_local", 716 | ] 717 | 718 | [[package]] 719 | name = "slog-term" 720 | version = "2.9.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "87d29185c55b7b258b4f120eab00f48557d4d9bc814f41713f449d35b0f8977c" 723 | dependencies = [ 724 | "atty", 725 | "slog", 726 | "term", 727 | "thread_local", 728 | "time 0.3.15", 729 | ] 730 | 731 | [[package]] 732 | name = "smallvec" 733 | version = "0.2.1" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" 736 | 737 | [[package]] 738 | name = "smallvec" 739 | version = "0.6.14" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" 742 | dependencies = [ 743 | "maybe-uninit", 744 | ] 745 | 746 | [[package]] 747 | name = "syn" 748 | version = "1.0.103" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 751 | dependencies = [ 752 | "proc-macro2", 753 | "quote", 754 | "unicode-ident", 755 | ] 756 | 757 | [[package]] 758 | name = "take" 759 | version = "0.1.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" 762 | 763 | [[package]] 764 | name = "take_mut" 765 | version = "0.2.2" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 768 | 769 | [[package]] 770 | name = "term" 771 | version = "0.7.0" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 774 | dependencies = [ 775 | "dirs-next", 776 | "rustversion", 777 | "winapi 0.3.9", 778 | ] 779 | 780 | [[package]] 781 | name = "thiserror" 782 | version = "1.0.37" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 785 | dependencies = [ 786 | "thiserror-impl", 787 | ] 788 | 789 | [[package]] 790 | name = "thiserror-impl" 791 | version = "1.0.37" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 794 | dependencies = [ 795 | "proc-macro2", 796 | "quote", 797 | "syn", 798 | ] 799 | 800 | [[package]] 801 | name = "thread_local" 802 | version = "1.1.4" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 805 | dependencies = [ 806 | "once_cell", 807 | ] 808 | 809 | [[package]] 810 | name = "time" 811 | version = "0.1.44" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 814 | dependencies = [ 815 | "libc", 816 | "wasi 0.10.0+wasi-snapshot-preview1", 817 | "winapi 0.3.9", 818 | ] 819 | 820 | [[package]] 821 | name = "time" 822 | version = "0.3.15" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" 825 | dependencies = [ 826 | "itoa", 827 | "libc", 828 | "num_threads", 829 | "time-macros", 830 | ] 831 | 832 | [[package]] 833 | name = "time-macros" 834 | version = "0.2.4" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 837 | 838 | [[package]] 839 | name = "tokio" 840 | version = "0.1.22" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" 843 | dependencies = [ 844 | "bytes 0.4.12", 845 | "futures", 846 | "mio", 847 | "num_cpus", 848 | "tokio-codec", 849 | "tokio-current-thread", 850 | "tokio-executor", 851 | "tokio-fs", 852 | "tokio-io", 853 | "tokio-reactor", 854 | "tokio-sync", 855 | "tokio-tcp", 856 | "tokio-threadpool", 857 | "tokio-timer", 858 | "tokio-udp", 859 | "tokio-uds", 860 | ] 861 | 862 | [[package]] 863 | name = "tokio-codec" 864 | version = "0.1.2" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" 867 | dependencies = [ 868 | "bytes 0.4.12", 869 | "futures", 870 | "tokio-io", 871 | ] 872 | 873 | [[package]] 874 | name = "tokio-core" 875 | version = "0.1.18" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "87b1395334443abca552f63d4f61d0486f12377c2ba8b368e523f89e828cffd4" 878 | dependencies = [ 879 | "bytes 0.4.12", 880 | "futures", 881 | "iovec", 882 | "log 0.4.17", 883 | "mio", 884 | "scoped-tls", 885 | "tokio", 886 | "tokio-executor", 887 | "tokio-io", 888 | "tokio-reactor", 889 | "tokio-timer", 890 | ] 891 | 892 | [[package]] 893 | name = "tokio-current-thread" 894 | version = "0.1.7" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" 897 | dependencies = [ 898 | "futures", 899 | "tokio-executor", 900 | ] 901 | 902 | [[package]] 903 | name = "tokio-executor" 904 | version = "0.1.10" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" 907 | dependencies = [ 908 | "crossbeam-utils 0.7.2", 909 | "futures", 910 | ] 911 | 912 | [[package]] 913 | name = "tokio-fs" 914 | version = "0.1.7" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" 917 | dependencies = [ 918 | "futures", 919 | "tokio-io", 920 | "tokio-threadpool", 921 | ] 922 | 923 | [[package]] 924 | name = "tokio-io" 925 | version = "0.1.13" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" 928 | dependencies = [ 929 | "bytes 0.4.12", 930 | "futures", 931 | "log 0.4.17", 932 | ] 933 | 934 | [[package]] 935 | name = "tokio-proto" 936 | version = "0.1.1" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" 939 | dependencies = [ 940 | "futures", 941 | "log 0.3.9", 942 | "net2", 943 | "rand 0.3.23", 944 | "slab 0.3.0", 945 | "smallvec 0.2.1", 946 | "take", 947 | "tokio-core", 948 | "tokio-io", 949 | "tokio-service", 950 | ] 951 | 952 | [[package]] 953 | name = "tokio-reactor" 954 | version = "0.1.12" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" 957 | dependencies = [ 958 | "crossbeam-utils 0.7.2", 959 | "futures", 960 | "lazy_static", 961 | "log 0.4.17", 962 | "mio", 963 | "num_cpus", 964 | "parking_lot", 965 | "slab 0.4.7", 966 | "tokio-executor", 967 | "tokio-io", 968 | "tokio-sync", 969 | ] 970 | 971 | [[package]] 972 | name = "tokio-service" 973 | version = "0.1.0" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" 976 | dependencies = [ 977 | "futures", 978 | ] 979 | 980 | [[package]] 981 | name = "tokio-sync" 982 | version = "0.1.8" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" 985 | dependencies = [ 986 | "fnv", 987 | "futures", 988 | ] 989 | 990 | [[package]] 991 | name = "tokio-tcp" 992 | version = "0.1.4" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" 995 | dependencies = [ 996 | "bytes 0.4.12", 997 | "futures", 998 | "iovec", 999 | "mio", 1000 | "tokio-io", 1001 | "tokio-reactor", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "tokio-threadpool" 1006 | version = "0.1.18" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" 1009 | dependencies = [ 1010 | "crossbeam-deque", 1011 | "crossbeam-queue", 1012 | "crossbeam-utils 0.7.2", 1013 | "futures", 1014 | "lazy_static", 1015 | "log 0.4.17", 1016 | "num_cpus", 1017 | "slab 0.4.7", 1018 | "tokio-executor", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "tokio-timer" 1023 | version = "0.2.13" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" 1026 | dependencies = [ 1027 | "crossbeam-utils 0.7.2", 1028 | "futures", 1029 | "slab 0.4.7", 1030 | "tokio-executor", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "tokio-udp" 1035 | version = "0.1.6" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" 1038 | dependencies = [ 1039 | "bytes 0.4.12", 1040 | "futures", 1041 | "log 0.4.17", 1042 | "mio", 1043 | "tokio-codec", 1044 | "tokio-io", 1045 | "tokio-reactor", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "tokio-uds" 1050 | version = "0.2.7" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" 1053 | dependencies = [ 1054 | "bytes 0.4.12", 1055 | "futures", 1056 | "iovec", 1057 | "libc", 1058 | "log 0.4.17", 1059 | "mio", 1060 | "mio-uds", 1061 | "tokio-codec", 1062 | "tokio-io", 1063 | "tokio-reactor", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "try-lock" 1068 | version = "0.1.0" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "ee2aa4715743892880f70885373966c83d73ef1b0838a664ef0c76fffd35e7c2" 1071 | 1072 | [[package]] 1073 | name = "unicase" 1074 | version = "2.6.0" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1077 | dependencies = [ 1078 | "version_check", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "unicode-ident" 1083 | version = "1.0.5" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1086 | 1087 | [[package]] 1088 | name = "version_check" 1089 | version = "0.9.4" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1092 | 1093 | [[package]] 1094 | name = "want" 1095 | version = "0.0.4" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1" 1098 | dependencies = [ 1099 | "futures", 1100 | "log 0.4.17", 1101 | "try-lock", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "wasi" 1106 | version = "0.10.0+wasi-snapshot-preview1" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1109 | 1110 | [[package]] 1111 | name = "wasi" 1112 | version = "0.11.0+wasi-snapshot-preview1" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1115 | 1116 | [[package]] 1117 | name = "weldmock" 1118 | version = "0.0.1-alpha.5" 1119 | dependencies = [ 1120 | "bytes 1.2.1", 1121 | "futures", 1122 | "futures-cpupool", 1123 | "hyper", 1124 | "lazy_static", 1125 | "rand 0.8.5", 1126 | "serde", 1127 | "serde_derive", 1128 | "serde_json", 1129 | "slog", 1130 | "slog-async", 1131 | "slog-term", 1132 | "time 0.1.44", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "winapi" 1137 | version = "0.2.8" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1140 | 1141 | [[package]] 1142 | name = "winapi" 1143 | version = "0.3.9" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1146 | dependencies = [ 1147 | "winapi-i686-pc-windows-gnu", 1148 | "winapi-x86_64-pc-windows-gnu", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "winapi-build" 1153 | version = "0.1.1" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1156 | 1157 | [[package]] 1158 | name = "winapi-i686-pc-windows-gnu" 1159 | version = "0.4.0" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1162 | 1163 | [[package]] 1164 | name = "winapi-x86_64-pc-windows-gnu" 1165 | version = "0.4.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1168 | 1169 | [[package]] 1170 | name = "ws2_32-sys" 1171 | version = "0.2.1" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1174 | dependencies = [ 1175 | "winapi 0.2.8", 1176 | "winapi-build", 1177 | ] 1178 | --------------------------------------------------------------------------------