susydev_jsonrpc_test/
lib.rs

1//! An utility package to test susydev-jsonrpc-core based projects.
2//!
3//! ```
4//! use susydev_jsonrpc_derive::rpc;
5//! use susydev_jsonrpc_test as test;
6//!
7//! use susydev_jsonrpc_core::{Result, Error, IoHandler};
8//!
9//! #[rpc]
10//! pub trait Test {
11//! 	#[rpc(name = "rpc_some_method")]
12//!	    fn some_method(&self, _: u64) -> Result<u64>;
13//! }
14//!
15//!
16//! struct Dummy;
17//! impl Test for Dummy {
18//!	  fn some_method(&self, x: u64) -> Result<u64> {
19//!     Ok(x * 2)
20//!	  }
21//! }
22//!
23//! fn main() {
24//!   // Initialize new instance of test environment
25//!   let rpc = test::Rpc::new(Dummy.to_delegate());
26//!
27//!   // make a request and verify the response as a pretty-printed string
28//!   assert_eq!(rpc.request("rpc_some_method", &[5]), r#"10"#);
29//!
30//!   // You can also test RPC created without macros:
31//!   let rpc = {
32//!     let mut io = IoHandler::new();
33//!     io.add_method("rpc_test_method", |_| {
34//!		  Err(Error::internal_error())
35//!		});
36//!     test::Rpc::from(io)
37//!   };
38//!
39//!   assert_eq!(rpc.request("rpc_test_method", &()), r#"{
40//!   "code": -32603,
41//!   "message": "Internal error"
42//! }"#);
43//! }
44//! ```
45
46#[warn(missing_docs)]
47
48extern crate susydev_jsonrpc_core as rpc;
49use serde;
50use serde_json;
51
52use std::collections::HashMap;
53
54/// Test RPC options.
55#[derive(Default, Debug)]
56pub struct Options {
57	/// Disable printing requests and responses.
58	pub no_print: bool,
59}
60
61#[derive(Default, Debug)]
62/// RPC instance.
63pub struct Rpc {
64	/// Underlying `IoHandler`.
65	pub io: rpc::IoHandler,
66	/// Options
67	pub options: Options,
68}
69
70pub enum Encoding {
71	Compact,
72	Pretty,
73}
74
75impl From<rpc::IoHandler> for Rpc {
76	fn from(io: rpc::IoHandler) -> Self {
77		Rpc { io, ..Default::default() }
78	}
79}
80
81impl Rpc {
82	/// Create a new RPC instance from a single delegate.
83	pub fn new<D>(delegate: D) -> Self where
84		D: Into<HashMap<String, rpc::RemoteProcedure<()>>>,
85	{
86		let mut io = rpc::IoHandler::new();
87		io.extend_with(delegate);
88		io.into()
89	}
90
91	/// Perform a single, synchronous method call and return pretty-printed value
92	pub fn request<T>(&self, method: &str, params: &T) -> String where
93		T: serde::Serialize,
94	{
95		self.make_request(method, params, Encoding::Pretty)
96	}
97
98	/// Perform a single, synchronous method call.
99	pub fn make_request<T>(
100		&self,
101		method: &str,
102		params: &T,
103		encoding: Encoding,
104	) -> String where
105		T: serde::Serialize,
106	{
107		use self::rpc::types::response;
108
109		let request = format!(
110			"{{ \"jsonrpc\":\"2.0\", \"id\": 1, \"method\": \"{}\", \"params\": {} }}",
111			method,
112			serde_json::to_string_pretty(params).expect("Serialization should be infallible."),
113		);
114
115		let response = self.io
116			.handle_request_sync(&request)
117			.expect("We are sending a method call not notification.");
118
119		// extract interesting part from the response
120		let extracted = match serde_json::from_str(&response).expect("We will always get a single output.") {
121			response::Output::Success(response::Success { result, .. }) => {
122				match encoding {
123					Encoding::Compact => serde_json::to_string(&result),
124					Encoding::Pretty => serde_json::to_string_pretty(&result),
125				}
126			},
127			response::Output::Failure(response::Failure { error, .. }) => {
128				match encoding {
129					Encoding::Compact => serde_json::to_string(&error),
130					Encoding::Pretty => serde_json::to_string_pretty(&error),
131				}
132			},
133		}.expect("Serialization is infallible; qed");
134
135		println!("\n{}\n --> {}\n", request, extracted);
136
137		extracted
138	}
139}
140
141#[cfg(test)]
142mod tests {
143	use super::*;
144
145	#[test]
146	fn should_test_request_is_pretty() {
147		// given
148		let rpc = {
149			let mut io = rpc::IoHandler::new();
150			io.add_method("test_method", |_| {
151				Ok(rpc::Value::Array(vec![5.into(), 10.into()]))
152			});
153			Rpc::from(io)
154		};
155
156		// when
157		assert_eq!(
158			rpc.request("test_method", &[5u64]),
159			"[\n  5,\n  10\n]"
160		);
161	}
162
163	#[test]
164	fn should_test_make_request_compact() {
165		// given
166		let rpc = {
167			let mut io = rpc::IoHandler::new();
168			io.add_method("test_method", |_| {
169				Ok(rpc::Value::Array(vec![5.into(), 10.into()]))
170			});
171			Rpc::from(io)
172		};
173
174		// when
175		assert_eq!(
176			rpc.make_request("test_method", &[5u64], Encoding::Compact),
177			"[5,10]"
178		);
179	}
180}