├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── basic.rs └── http_server.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.lock 3 | *.swp 4 | tags* 5 | build/ 6 | build-tests/ 7 | target/ 8 | *~ 9 | /.idea 10 | tests/tmp* 11 | src/tmp* 12 | *.sublime-* 13 | .project -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: rust 4 | rust: 5 | - nightly -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_rpc" 3 | version = "0.2.0" 4 | authors = ["bcndanos "] 5 | description = "JSON-RPC 2.0 Implementation" 6 | repository = "https://github.com/bcndanos/json_rpc" 7 | readme = "README.md" 8 | license = "Apache-2.0 / MIT" 9 | keywords = ["json", "rpc"] 10 | 11 | [dependencies] 12 | asynchronous = "*" 13 | rustc-serialize = "0.3.15" 14 | 15 | [dev-dependencies] 16 | hyper = "*" 17 | 18 | [[example]] 19 | name = "basic" 20 | path = "examples/basic.rs" 21 | test = false 22 | bench = false 23 | 24 | [[example]] 25 | name = "http_server" 26 | path = "examples/http_server.rs" 27 | test = false 28 | bench = false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 bcndanos 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json rpc 2 | 3 | **[JSON-RPC 2.0 Implementation](http://www.jsonrpc.org/specification) in Rust** 4 | 5 | |Crate|Travis| 6 | |:------:|:-------:| 7 | |[![](http://meritbadge.herokuapp.com/json_rpc)](https://crates.io/crates/json_rpc)|[![Build Status](https://travis-ci.org/bcndanos/json_rpc.svg?branch=master)](https://travis-ci.org/bcndanos/json_rpc)| 8 | 9 | #Overview 10 | 11 | Currently in development. Look at the examples in ./examples for more information. 12 | 13 | ##Todo Items 14 | 15 | ## [0.2.0] 16 | - [X] Server side 17 | 18 | ## [0.3.0] 19 | - [ ] Client side 20 | - [ ] Refactor server code 21 | 22 | ## [0.4.0] 23 | - [ ] Allow batch requests 24 | - [ ] Parallel or series execution of batch requests 25 | 26 | ## [1.0.0] 27 | - [ ] Full documentation and examples 28 | 29 | ## [2.0.0] 30 | - [ ] json-rpc 1.0 31 | 32 | #License 33 | 34 | Dual-licensed to be compatible with the Rust project. 35 | 36 | Licensed under the Apache License, Version 2.0 37 | http://www.apache.org/licenses/LICENSE-2.0 or the MIT license 38 | http://opensource.org/licenses/MIT, at your 39 | option. This file may not be copied, modified, or distributed 40 | except according to those terms. 41 | 42 | # Examples 43 | 44 | This is a basic example with two methods: 45 | 46 | ```rust 47 | #[macro_use(rpc_method)] 48 | extern crate json_rpc; 49 | use json_rpc::{Server, Json, Error}; 50 | 51 | fn main() { 52 | let mut rpc_server = Server::new(); 53 | 54 | // Registers a Rpc Method named "Subtract" with two parameter "by Name". 55 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 56 | Ok(Json::U64(oper1 - oper2)) 57 | }); 58 | 59 | // Registers a Rpc Method named "Multiply" with N parameteres "by Position". 60 | rpc_method!(rpc_server, Multiply, values[u64], { 61 | let mut r = 1; 62 | for v in values { r *= v } 63 | Ok(Json::U64(r)) 64 | }); 65 | 66 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Subtract\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":2}".to_string(); 67 | match rpc_server.request(str_request) { 68 | Some(str_response) => assert_eq!(str_response, "{\"id\":2,\"jsonrpc\":\"2.0\",\"result\":19}") , 69 | None => unreachable!(), 70 | }; 71 | 72 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Multiply\", \"params\":[5, 6, 7], \"id\":3}".to_string(); 73 | match rpc_server.request(str_request) { 74 | Some(str_response) => assert_eq!(str_response, "{\"id\":3,\"jsonrpc\":\"2.0\",\"result\":210}") , 75 | None => unreachable!(), 76 | }; 77 | } 78 | 79 | ``` 80 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | #[macro_use(rpc_method, rpc_method_no_params, rpc_params)] 2 | extern crate json_rpc; 3 | 4 | use json_rpc::{Server, Json, Error}; 5 | use json_rpc::serialize::json::ToJson; 6 | use std::thread; 7 | use std::collections::BTreeMap; 8 | 9 | fn main() { 10 | println!("Running Example ..."); 11 | 12 | let mut rpc_server = Server::new(); 13 | 14 | // Registers a Rpc Method named "Subtract" with two parameter "by Name". 15 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 16 | Ok(Json::U64(oper1 - oper2)) 17 | }); 18 | 19 | // Registers a Rpc Method named "Multiply" with N parameteres "by Position". 20 | rpc_method!(rpc_server, Multiply, values[u64], { 21 | let mut r = 1; 22 | for v in values { r *= v } 23 | Ok(Json::U64(r)) 24 | }); 25 | 26 | // This method can return an error 27 | rpc_method!(rpc_server, Division, oper1;oper2, { 28 | if oper2 == 0f64 { 29 | Err(Error::custom(1, "Division by zero", Some(Json::F64(oper1)))) 30 | } else { 31 | Ok(Json::F64(oper1 / oper2)) 32 | } 33 | }); 34 | 35 | // Registers a method that returns an Array 36 | rpc_method!(rpc_server, Sequence, start;step;iterations, { 37 | let mut value = start as f64; 38 | let mut res = Vec::new(); 39 | for _ in 0..iterations { 40 | res.push(Json::F64(value)); 41 | value += step; 42 | } 43 | Ok(Json::Array(res)) 44 | }); 45 | 46 | struct Info { 47 | amount: u32, 48 | price: f64, 49 | description: String, 50 | } 51 | impl ToJson for Info { 52 | fn to_json(&self) -> Json { 53 | let mut d = BTreeMap::new(); 54 | d.insert("amount".to_string(), self.amount.to_json()); 55 | d.insert("price".to_string(), self.price.to_json()); 56 | d.insert("description".to_string(), self.description.to_json()); 57 | Json::Object(d) 58 | } 59 | } 60 | 61 | // Registers a method that returns an Object 62 | rpc_method_no_params!(rpc_server, GetInfo, { 63 | let info = Info { amount : 15, price: 2.33, description: "Apples".to_string() }; 64 | Ok(info.to_json()) 65 | }); 66 | 67 | 68 | // Register a Rpc manually without macros 69 | rpc_server.register_method("Add", |json_params| { // json_params: String 70 | // It uses a macro for parse the String into a Struct. rpc_params : { oper1:u64, oper2:u64 } 71 | let rpc_params = rpc_params!(json_params, oper1;oper2 ); 72 | println!("Rpc Params en add: {:?}", rpc_params); 73 | thread::sleep_ms(1000); 74 | let result = Json::U64(rpc_params.oper1 + rpc_params.oper2); 75 | Ok(result) 76 | }); 77 | 78 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Add\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":1}".to_string(); 79 | new_request(&rpc_server, str_request); 80 | 81 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Subtract\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":2}".to_string(); 82 | new_request(&rpc_server, str_request); 83 | 84 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Multiply\", \"params\":[5, 6, 7], \"id\":3}".to_string(); 85 | new_request(&rpc_server, str_request); 86 | 87 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Multiply\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":33}".to_string(); 88 | new_request(&rpc_server, str_request); 89 | 90 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Division\", \"params\":{\"oper1\":23, \"oper2\":0}, \"id\":4}".to_string(); 91 | new_request(&rpc_server, str_request); 92 | 93 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Division\", \"params\":{\"oper1\":30, \"oper2\":7}, \"id\":5}".to_string(); 94 | new_request(&rpc_server, str_request); 95 | 96 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Sequence\", \"params\":{\"start\":7, \"step\":0.33, \"iterations\":4}, \"id\":1234}".to_string(); 97 | new_request(&rpc_server, str_request); 98 | 99 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"GetInfo\", \"id\":1234}".to_string(); 100 | new_request(&rpc_server, str_request); 101 | 102 | // This operation is Notification. It doesn't include 'id' and doesn't return anything. 103 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Division\", \"params\":{\"oper1\":30, \"oper2\":7}}".to_string(); 104 | new_request(&rpc_server, str_request); 105 | 106 | thread::sleep_ms(2000); 107 | println!("End Example"); 108 | } 109 | 110 | fn new_request(rpc_server:&Server, str_request: String) { 111 | rpc_server.request(str_request.clone(), move |str_response| { 112 | println!("Executed: \n request = {},\n response = {}", str_request, str_response); 113 | }); 114 | } -------------------------------------------------------------------------------- /examples/http_server.rs: -------------------------------------------------------------------------------- 1 | #[macro_use(rpc_method, rpc_method_no_params, rpc_params)] 2 | extern crate json_rpc; 3 | extern crate hyper; 4 | 5 | use json_rpc::Server as RpcServer; 6 | use json_rpc::{Json, Error}; 7 | use json_rpc::serialize::json::ToJson; 8 | use std::io::Read; 9 | use std::io::Write; 10 | use std::collections::BTreeMap; 11 | use hyper::Server as ServerHttp; 12 | use hyper::server::{Request,Response}; 13 | 14 | 15 | fn main() { 16 | println!("Started server on 127.0.0.1:8080"); 17 | 18 | let mut rpc_server = RpcServer::new(); 19 | 20 | register_methods(&mut rpc_server); 21 | 22 | ServerHttp::http(move |mut req:Request, mut res:Response| { 23 | match req.method { 24 | hyper::Post => { 25 | let mut str_req = String::new(); 26 | req.read_to_string(&mut str_req).unwrap(); 27 | let mut res = res.start().unwrap(); 28 | match rpc_server.request(str_req) { 29 | Some(str_res) => res.write_all(str_res.as_bytes()).unwrap() , 30 | None => (), 31 | }; 32 | res.end().unwrap(); 33 | }, 34 | _ => *res.status_mut() = hyper::status::StatusCode::MethodNotAllowed 35 | } 36 | }).listen("127.0.0.1:8080").unwrap(); 37 | 38 | println!("Stopped server!"); 39 | 40 | } 41 | 42 | 43 | fn register_methods(rpc_server:&mut RpcServer) { 44 | // Registers a Rpc Method named "Subtract" with two parameter "by Name". 45 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 46 | Ok(Json::U64(oper1 - oper2)) 47 | }); 48 | 49 | // Registers a Rpc Method named "Multiply" with N parameteres "by Position". 50 | rpc_method!(rpc_server, Multiply, values[u64], { 51 | let mut r = 1; 52 | for v in values { r *= v } 53 | Ok(Json::U64(r)) 54 | }); 55 | 56 | // This method can return an error 57 | rpc_method!(rpc_server, Division, oper1;oper2, { 58 | if oper2 == 0f64 { 59 | Err(Error::custom(1, "Division by zero", Some(Json::F64(oper1)))) 60 | } else { 61 | Ok(Json::F64(oper1 / oper2)) 62 | } 63 | }); 64 | 65 | // Registers a method that returns an Array 66 | rpc_method!(rpc_server, Sequence, start;step;iterations, { 67 | let mut value = start as f64; 68 | let mut res = Vec::new(); 69 | for _ in 0..iterations { 70 | res.push(Json::F64(value)); 71 | value += step; 72 | } 73 | Ok(Json::Array(res)) 74 | }); 75 | 76 | struct Info { 77 | amount: u32, 78 | price: f64, 79 | description: String, 80 | } 81 | impl ToJson for Info { 82 | fn to_json(&self) -> Json { 83 | let mut d = BTreeMap::new(); 84 | d.insert("amount".to_string(), self.amount.to_json()); 85 | d.insert("price".to_string(), self.price.to_json()); 86 | d.insert("description".to_string(), self.description.to_json()); 87 | Json::Object(d) 88 | } 89 | } 90 | 91 | // Registers a method that returns an Object 92 | rpc_method_no_params!(rpc_server, GetInfo, { 93 | let info = Info { amount : 15, price: 2.33, description: "Apples".to_string() }; 94 | Ok(info.to_json()) 95 | }); 96 | 97 | 98 | // Register a Rpc manually without macros 99 | rpc_server.register_method("Add", |json_params| { // json_params: String 100 | // It uses a macro for parse the String into a Struct. rpc_params : { oper1:u64, oper2:u64 } 101 | let rpc_params = rpc_params!(json_params, oper1;oper2 ); 102 | let result = Json::U64(rpc_params.oper1 + rpc_params.oper2); 103 | Ok(result) 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # json rpc 3 | 4 | **[JSON-RPC 2.0 Implementation](http://www.jsonrpc.org/specification) in Rust** 5 | 6 | |Crate|Travis| 7 | |:------:|:-------:| 8 | |[![](http://meritbadge.herokuapp.com/json_rpc)](https://crates.io/crates/json_rpc)|[![Build Status](https://travis-ci.org/bcndanos/json_rpc.svg?branch=master)](https://travis-ci.org/bcndanos/json_rpc)| 9 | 10 | #Overview 11 | 12 | Currently in development. 13 | 14 | #License 15 | 16 | Dual-licensed to be compatible with the Rust project. 17 | 18 | Licensed under the Apache License, Version 2.0 19 | http://www.apache.org/licenses/LICENSE-2.0 or the MIT license 20 | http://opensource.org/licenses/MIT, at your 21 | option. This file may not be copied, modified, or distributed 22 | except according to those terms. 23 | 24 | # Examples 25 | 26 | This is a basic example with two methods: 27 | 28 | ```rust 29 | #[macro_use(rpc_method)] 30 | extern crate json_rpc; 31 | use json_rpc::{Server, Json, Error}; 32 | 33 | fn main() { 34 | let mut rpc_server = Server::new(); 35 | 36 | // Registers a Rpc Method named "Subtract" with two parameter "by Name". 37 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 38 | Ok(Json::U64(oper1 - oper2)) 39 | }); 40 | 41 | // Registers a Rpc Method named "Multiply" with N parameteres "by Position". 42 | rpc_method!(rpc_server, Multiply, values[u64], { 43 | let mut r = 1; 44 | for v in values { r *= v } 45 | Ok(Json::U64(r)) 46 | }); 47 | 48 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Subtract\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":2}".to_string(); 49 | match rpc_server.request(str_request) { 50 | Some(str_response) => assert_eq!(str_response, "{\"id\":2,\"jsonrpc\":\"2.0\",\"result\":19}") , 51 | None => unreachable!(), 52 | }; 53 | 54 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Multiply\", \"params\":[5, 6, 7], \"id\":3}".to_string(); 55 | match rpc_server.request(str_request) { 56 | Some(str_response) => assert_eq!(str_response, "{\"id\":3,\"jsonrpc\":\"2.0\",\"result\":210}") , 57 | None => unreachable!(), 58 | }; 59 | } 60 | 61 | ``` 62 | 63 | */ 64 | 65 | extern crate asynchronous; 66 | pub extern crate rustc_serialize as serialize; 67 | 68 | use asynchronous::{Deferred, Promise}; 69 | use std::collections::BTreeMap; 70 | use std::sync::Arc; 71 | pub use serialize::json::Json; 72 | 73 | pub struct Error { 74 | code : i64, 75 | message : String, 76 | data : Option, 77 | } 78 | 79 | impl Error { 80 | pub fn custom(code:i64, message: &str, data: Option) -> Error { 81 | if code >= -32768 && code <= -32000 { 82 | panic!("You cannot assign a pre-defined error."); 83 | } 84 | Error { 85 | code: code, message: message.to_string(), data: data 86 | } 87 | } 88 | 89 | pub fn predefined(code:i64, data: Option) -> Error { 90 | Error { 91 | code: code, 92 | message: match code { 93 | -32700 => "Parse error".to_string(), 94 | -32600 => "Invalid Request".to_string(), 95 | -32601 => "Method not found".to_string(), 96 | -32602 => "Invalid params".to_string(), 97 | -32603 => "Internal error".to_string(), 98 | -32099 ... -32000 => "Server error".to_string(), 99 | _ => panic!("Predefined error code incorrect.") 100 | }, 101 | data: data 102 | } 103 | } 104 | 105 | fn as_object(&self) -> Json { 106 | let mut error_object = BTreeMap::new(); 107 | error_object.insert("code".to_string(), Json::I64(self.code)); 108 | error_object.insert("message".to_string(), Json::String(self.message.to_string())); 109 | match self.data { 110 | Some(ref v) => { error_object.insert("data".to_string(), v.clone()); } , 111 | None => (), 112 | } 113 | Json::Object(error_object) 114 | } 115 | } 116 | 117 | pub struct Server { 118 | methods: BTreeMap Result + 'static + Send + Sync >>> 119 | } 120 | 121 | impl Server { 122 | pub fn new() -> Server { 123 | Server { 124 | methods : BTreeMap::new() 125 | } 126 | } 127 | 128 | pub fn register_method(&mut self, method:&str, f:F) where F: Fn(Json) -> Result + 'static + Send + Sync { 129 | self.methods.insert(method.to_string(), Arc::new(Box::new(f))); 130 | } 131 | 132 | pub fn request(&self, str_request:String) -> Option { 133 | let data = match Json::from_str(&str_request) { 134 | Ok(o) => o, 135 | Err(_) => return Some(Server::response_error(-32700)) 136 | }; 137 | let obj = match data.as_object() { 138 | Some(s) => s, 139 | None => return Some(Server::response_error(-32600)) 140 | }; 141 | match obj.get("jsonrpc") { 142 | Some(o) => match o.as_string() { 143 | Some(s) => if s!="2.0" { return Some(Server::response_error(-32600)) }, 144 | None => return Some(Server::response_error(-32600)) 145 | }, 146 | None => return Some(Server::response_error(-32600)) 147 | }; 148 | let str_method = match obj.get("method") { 149 | Some(o) => match o.as_string() { 150 | Some(s) => s, 151 | None => return Some(Server::response_error(-32600)) 152 | }, 153 | None => return Some(Server::response_error(-32600)) 154 | }; 155 | let params = match obj.get("params") { 156 | Some(o) => match *o { 157 | Json::Array(ref v) => Json::Array(v.clone()), 158 | Json::Object(ref v) => Json::Object(v.clone()), 159 | _ => return Some(Server::response_error(-32600)) 160 | }, 161 | None => Json::Null 162 | }; 163 | let id:Option = match obj.get("id") { 164 | Some(o) => match *o { 165 | Json::String(ref v) => Some(Json::String(v.clone())), 166 | Json::I64(ref v) => Some(Json::I64(v.clone())), 167 | Json::U64(ref v) => Some(Json::U64(v.clone())), 168 | Json::F64(ref v) => Some(Json::F64(v.clone())), 169 | Json::Null => None, 170 | _ => return Some(Server::response_error(-32600)) 171 | }, 172 | None => None 173 | }; 174 | let f = match self.methods.get(str_method) { 175 | Some(o) => o.clone(), 176 | None => return Some(Server::response_error(-32601)) 177 | }; 178 | if id.is_some() { 179 | let res = f(params); 180 | let mut resp_object = BTreeMap::new(); 181 | resp_object.insert("jsonrpc".to_string(), Json::String("2.0".to_string())); 182 | resp_object.insert("id".to_string(), id.unwrap()); 183 | match res { 184 | Ok(v) => { resp_object.insert("result".to_string(), v); } , 185 | Err(e) => { resp_object.insert("error".to_string(), e.as_object()); } 186 | } 187 | Some(Json::Object(resp_object).to_string()) 188 | } else { 189 | Promise::new(move || { f(params) }); 190 | None 191 | } 192 | } 193 | 194 | pub fn request_async(&self, str_request:String, f_response:F) where F: FnOnce(String) + Send + 'static { 195 | let data = match Json::from_str(&str_request) { 196 | Ok(o) => o, 197 | Err(_) => return f_response(Server::response_error(-32700)) 198 | }; 199 | let obj = match data.as_object() { 200 | Some(s) => s, 201 | None => return f_response(Server::response_error(-32600)) 202 | }; 203 | match obj.get("jsonrpc") { 204 | Some(o) => match o.as_string() { 205 | Some(s) => if s!="2.0" { return f_response(Server::response_error(-32600)) }, 206 | None => return f_response(Server::response_error(-32600)) 207 | }, 208 | None => return f_response(Server::response_error(-32600)) 209 | }; 210 | let str_method = match obj.get("method") { 211 | Some(o) => match o.as_string() { 212 | Some(s) => s, 213 | None => return f_response(Server::response_error(-32600)) 214 | }, 215 | None => return f_response(Server::response_error(-32600)) 216 | }; 217 | let params = match obj.get("params") { 218 | Some(o) => match *o { 219 | Json::Array(ref v) => Json::Array(v.clone()), 220 | Json::Object(ref v) => Json::Object(v.clone()), 221 | _ => return f_response(Server::response_error(-32600)) 222 | }, 223 | None => Json::Null 224 | }; 225 | let id:Option = match obj.get("id") { 226 | Some(o) => match *o { 227 | Json::String(ref v) => Some(Json::String(v.clone())), 228 | Json::I64(ref v) => Some(Json::I64(v.clone())), 229 | Json::U64(ref v) => Some(Json::U64(v.clone())), 230 | Json::F64(ref v) => Some(Json::F64(v.clone())), 231 | Json::Null => None, 232 | _ => return f_response(Server::response_error(-32600)) 233 | }, 234 | None => None 235 | }; 236 | let f = match self.methods.get(str_method) { 237 | Some(o) => o.clone(), 238 | None => return f_response(Server::response_error(-32601)) 239 | }; 240 | Deferred::new(move ||{ 241 | f(params) 242 | }).finally(move |res| { 243 | if id.is_some() { 244 | let mut resp_object = BTreeMap::new(); 245 | resp_object.insert("jsonrpc".to_string(), Json::String("2.0".to_string())); 246 | resp_object.insert("id".to_string(), id.unwrap()); 247 | match res { 248 | Ok(v) => { resp_object.insert("result".to_string(), v); } , 249 | Err(e) => { resp_object.insert("error".to_string(), e.as_object()); } 250 | } 251 | f_response(Json::Object(resp_object).to_string()); 252 | } 253 | }); 254 | } 255 | 256 | fn response_error(code:i64) -> String { 257 | let mut resp_object = BTreeMap::new(); 258 | resp_object.insert("jsonrpc".to_string(), Json::String("2.0".to_string())); 259 | resp_object.insert("error".to_string(), Error::predefined(code, None).as_object()); 260 | resp_object.insert("id".to_string(), Json::Null); 261 | Json::Object(resp_object).to_string() 262 | } 263 | 264 | } 265 | 266 | #[macro_export] 267 | macro_rules! rpc_method { 268 | ( $rpc_struct:expr, $rpc_method:expr, $($n:ident<$t:ty>);+ , $rpc_block:block ) => { 269 | $rpc_struct.register_method(stringify!($rpc_method), |json_params| { 270 | #[derive(Debug)] 271 | struct Params { $( $n:$t, ) + } 272 | impl $crate::serialize::Decodable for Params { 273 | fn decode(d: &mut D) -> ::std::result::Result { 274 | d.read_struct("Params", 0usize, |_d| -> _ { 275 | ::std::result::Result::Ok(Params{ 276 | $( 277 | $n: match _d.read_struct_field(stringify!($n), 0usize, $crate::serialize::Decodable::decode) { 278 | ::std::result::Result::Ok(v) => v, 279 | ::std::result::Result::Err(v) => return ::std::result::Result::Err(v), 280 | }, 281 | ) + 282 | }) 283 | }) 284 | } 285 | } 286 | let mut decoder = $crate::serialize::json::Decoder::new(json_params); 287 | let rpc_params:Params = match $crate::serialize::Decodable::decode(&mut decoder) { 288 | Ok(p) => p, 289 | Err(_) => return Err(Error::predefined(-32602, None)) 290 | }; 291 | $( let $n:$t = rpc_params.$n; ) + 292 | 293 | $rpc_block 294 | }) 295 | }; 296 | ( $rpc_struct:expr, $rpc_method:expr, $n:ident[$t:ty], $rpc_block:block ) => { 297 | $rpc_struct.register_method(stringify!($rpc_method), |json_params| { 298 | let mut $n:Vec<$t> = Vec::new(); 299 | match json_params { 300 | $crate::serialize::json::Json::Array(a) => { 301 | for v in a { 302 | let mut decoder = $crate::serialize::json::Decoder::new(v); 303 | let val:$t = match $crate::serialize::Decodable::decode(&mut decoder) { 304 | Ok(p) => p, 305 | Err(_) => return Err(Error::predefined(-32602, None)) 306 | }; 307 | $n.push(val); 308 | } 309 | }, 310 | _ => return Err(Error::predefined(-32602, None)) 311 | } 312 | $rpc_block 313 | }) 314 | }; 315 | ( $rpc_struct:expr, $rpc_method:expr, $n:ident, $rpc_block:block ) => { 316 | $rpc_struct.register_method(stringify!($rpc_method), |json_params| { 317 | let $n:Json = json_params; 318 | $rpc_block 319 | }) 320 | }; 321 | } 322 | 323 | #[macro_export] 324 | macro_rules! rpc_method_no_params { 325 | ( $rpc_struct:expr, $rpc_method:expr, $rpc_block:block ) => { 326 | $rpc_struct.register_method(stringify!($rpc_method), |_| { 327 | $rpc_block 328 | }) 329 | }; 330 | } 331 | 332 | #[macro_export] 333 | macro_rules! rpc_params { 334 | ( $p:expr, $($n:ident<$t:ty>);+ ) => { 335 | { 336 | #[derive(Debug)] 337 | struct Params { $( $n:$t, ) + } 338 | impl $crate::serialize::Decodable for Params { 339 | fn decode(d: &mut D) -> ::std::result::Result { 340 | d.read_struct("Params", 0usize, |_d| -> _ { 341 | ::std::result::Result::Ok(Params{ 342 | $( 343 | $n: match _d.read_struct_field(stringify!($n), 0usize, $crate::serialize::Decodable::decode) { 344 | ::std::result::Result::Ok(v) => v, 345 | ::std::result::Result::Err(v) => return ::std::result::Result::Err(v), 346 | }, 347 | ) + 348 | }) 349 | }) 350 | } 351 | } 352 | let mut decoder = $crate::serialize::json::Decoder::new($p); 353 | let rpc_params:Params = match $crate::serialize::Decodable::decode(&mut decoder) { 354 | Ok(p) => p, 355 | Err(_) => return Err(Error::predefined(-32602, None)) 356 | }; 357 | rpc_params 358 | } 359 | }; 360 | } 361 | 362 | #[cfg(test)] 363 | mod test { 364 | use super::{Server,Error,Json}; 365 | use super::serialize::json::ToJson; 366 | use std::collections::BTreeMap; 367 | use std::thread; 368 | 369 | #[test] 370 | fn test_method_by_name() { 371 | let mut rpc_server = Server::new(); 372 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 373 | Ok(Json::U64(oper1 - oper2)) 374 | }); 375 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Subtract\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":1234}".to_string(); 376 | match rpc_server.request(str_request) { 377 | Some(str_response) => { 378 | let data = Json::from_str(&str_response).unwrap(); 379 | assert!(data.is_object()); 380 | let obj = data.as_object().unwrap(); 381 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 382 | assert_eq!(obj.get("id").unwrap().as_u64().unwrap(), 1234); 383 | assert_eq!(obj.get("result").unwrap().as_u64().unwrap(), 19); 384 | }, 385 | None => unreachable!(), 386 | }; 387 | thread::sleep_ms(300); 388 | } 389 | 390 | #[test] 391 | fn test_method_by_position() { 392 | let mut rpc_server = Server::new(); 393 | rpc_method!(rpc_server, Multiply, values[u64], { 394 | let mut r = 1; 395 | for v in values { r *= v } 396 | Ok(Json::U64(r)) 397 | }); 398 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Multiply\", \"params\":[5, 6, 7], \"id\":\"SEQ456\"}".to_string(); 399 | match rpc_server.request(str_request) { 400 | Some(str_response) => { 401 | let data = Json::from_str(&str_response).unwrap(); 402 | assert!(data.is_object()); 403 | let obj = data.as_object().unwrap(); 404 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 405 | assert_eq!(obj.get("id").unwrap().as_string().unwrap(), "SEQ456"); 406 | assert_eq!(obj.get("result").unwrap().as_u64().unwrap(), 210); 407 | }, 408 | None => unreachable!(), 409 | }; 410 | thread::sleep_ms(300); 411 | } 412 | 413 | #[test] 414 | fn test_manual_register() { 415 | let mut rpc_server = Server::new(); 416 | rpc_server.register_method("Add", |json_params| { // json_params: String 417 | // It uses a macro for parse the String into a Struct. rpc_params : { oper1:u64, oper2:u64 } 418 | let rpc_params = rpc_params!(json_params, oper1;oper2 ); 419 | assert_eq!(rpc_params.oper1, 23u64); 420 | assert_eq!(rpc_params.oper2, 4u64); 421 | let result = Json::U64(rpc_params.oper1 + rpc_params.oper2); 422 | Ok(result) 423 | }); 424 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Add\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":-1.4788}".to_string(); 425 | match rpc_server.request(str_request) { 426 | Some(str_response) => { 427 | let data = Json::from_str(&str_response).unwrap(); 428 | assert!(data.is_object()); 429 | let obj = data.as_object().unwrap(); 430 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 431 | assert_eq!(obj.get("id").unwrap().as_f64().unwrap(), -1.4788f64); 432 | assert_eq!(obj.get("result").unwrap().as_u64().unwrap(), 27); 433 | }, 434 | None => unreachable!(), 435 | }; 436 | thread::sleep_ms(300); 437 | } 438 | 439 | #[test] 440 | fn test_method_returns_array() { 441 | let mut rpc_server = Server::new(); 442 | rpc_method!(rpc_server, Sequence, start;step;iterations, { 443 | let mut value = start as f64; 444 | let mut res = Vec::new(); 445 | for _ in 0..iterations { 446 | res.push(Json::F64(value)); 447 | value += step; 448 | } 449 | Ok(Json::Array(res)) 450 | }); 451 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Sequence\", \"params\":{\"start\":7, \"step\":0.33, \"iterations\":4}, \"id\":1234}".to_string(); 452 | match rpc_server.request(str_request) { 453 | Some(str_response) => { 454 | let data = Json::from_str(&str_response).unwrap(); 455 | assert!(data.is_object()); 456 | let obj = data.as_object().unwrap(); 457 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 458 | assert_eq!(obj.get("id").unwrap().as_u64().unwrap(), 1234); 459 | let arr = obj.get("result").unwrap().as_array().unwrap(); 460 | assert_eq!(arr.len(), 4); 461 | assert_eq!((arr[0].as_f64().unwrap() * 100f64).round(), 700f64); 462 | assert_eq!((arr[1].as_f64().unwrap() * 100f64).round(), 733f64); 463 | assert_eq!((arr[2].as_f64().unwrap() * 100f64).round(), 766f64); 464 | assert_eq!((arr[3].as_f64().unwrap() * 100f64).round(), 799f64); 465 | }, 466 | None => unreachable!(), 467 | }; 468 | thread::sleep_ms(300); 469 | } 470 | 471 | #[test] 472 | fn test_method_returns_object() { 473 | struct Info { 474 | amount: u32, 475 | price: f64, 476 | description: String, 477 | } 478 | impl ToJson for Info { 479 | fn to_json(&self) -> Json { 480 | let mut d = BTreeMap::new(); 481 | d.insert("amount".to_string(), self.amount.to_json()); 482 | d.insert("price".to_string(), self.price.to_json()); 483 | d.insert("description".to_string(), self.description.to_json()); 484 | Json::Object(d) 485 | } 486 | } 487 | 488 | let mut rpc_server = Server::new(); 489 | rpc_method_no_params!(rpc_server, GetInfo, { 490 | let info = Info { amount : 15, price: 2.33, description: "Apples".to_string() }; 491 | Ok(info.to_json()) 492 | }); 493 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"GetInfo\", \"id\":1234}".to_string(); 494 | match rpc_server.request(str_request) { 495 | Some(str_response) => { 496 | let data = Json::from_str(&str_response).unwrap(); 497 | assert!(data.is_object()); 498 | let obj = data.as_object().unwrap(); 499 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 500 | assert_eq!(obj.get("id").unwrap().as_u64().unwrap(), 1234); 501 | let ret = obj.get("result").unwrap().as_object().unwrap(); 502 | assert_eq!(ret.get("amount").unwrap().as_u64().unwrap(), 15); 503 | assert_eq!((ret.get("price").unwrap().as_f64().unwrap() * 100f64).round(), 233f64); 504 | assert_eq!(ret.get("description").unwrap().as_string().unwrap(), "Apples"); 505 | }, 506 | None => unreachable!(), 507 | }; 508 | thread::sleep_ms(300); 509 | } 510 | 511 | 512 | #[test] 513 | fn test_custom_error() { 514 | let mut rpc_server = Server::new(); 515 | rpc_method!(rpc_server, Division, oper1;oper2, { 516 | if oper2 == 0f64 { 517 | Err(Error::custom(784, "Division by zero", Some(Json::F64(oper1)))) 518 | } else { 519 | Ok(Json::F64(oper1 / oper2)) 520 | } 521 | }); 522 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Division\", \"params\":{\"oper1\":23, \"oper2\":0}, \"id\":4}".to_string(); 523 | match rpc_server.request(str_request) { 524 | Some(str_response) => { 525 | let data = Json::from_str(&str_response).unwrap(); 526 | assert!(data.is_object()); 527 | let obj = data.as_object().unwrap(); 528 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 529 | assert_eq!(obj.get("id").unwrap().as_u64().unwrap(), 4); 530 | assert!(obj.get("error").unwrap().is_object()); 531 | let err = obj.get("error").unwrap().as_object().unwrap(); 532 | assert_eq!(err.get("code").unwrap().as_i64().unwrap(), 784); 533 | assert_eq!(err.get("message").unwrap().as_string().unwrap(), "Division by zero"); 534 | assert_eq!(err.get("data").unwrap().as_f64().unwrap(), 23f64); 535 | }, 536 | None => unreachable!(), 537 | }; 538 | thread::sleep_ms(300); 539 | } 540 | 541 | #[test] 542 | fn test_error_method_not_found() { 543 | let mut rpc_server = Server::new(); 544 | rpc_method!(rpc_server, Subtract, oper1;oper2, { 545 | Ok(Json::U64(oper1 - oper2)) 546 | }); 547 | let str_request = "{\"jsonrpc\":\"2.0\",\"method\":\"Add\", \"params\":{\"oper1\":23, \"oper2\":4}, \"id\":1234}".to_string(); 548 | match rpc_server.request(str_request) { 549 | Some(str_response) => { 550 | let data = Json::from_str(&str_response).unwrap(); 551 | assert!(data.is_object()); 552 | let obj = data.as_object().unwrap(); 553 | assert_eq!(obj.get("jsonrpc").unwrap().as_string().unwrap(), "2.0"); 554 | assert_eq!(obj.get("id").unwrap().as_null().unwrap(), ()); 555 | assert!(obj.get("error").unwrap().is_object()); 556 | let err = obj.get("error").unwrap().as_object().unwrap(); 557 | assert_eq!(err.get("code").unwrap().as_i64().unwrap(), -32601); 558 | assert_eq!(err.get("message").unwrap().as_string().unwrap(), "Method not found"); 559 | }, 560 | None => unreachable!(), 561 | }; 562 | thread::sleep_ms(300); 563 | } 564 | } --------------------------------------------------------------------------------