jsonrpc_test/
lib.rs

1//! An utility package to test jsonrpc-core based projects.
2//!
3//! ```
4//! use jsonrpc_derive::rpc;
5//! use jsonrpc_test as test;
6//!
7//! use jsonrpc_core::{Result, Error, IoHandler};
8//!
9//! #[rpc]
10//! pub trait Test {
11//!    #[rpc(name = "rpc_some_method")]
12//!    fn some_method(&self, a: 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_sync_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#![deny(missing_docs)]
47
48extern crate jsonrpc_core as rpc;
49
50/// Test RPC options.
51#[derive(Default, Debug)]
52pub struct Options {
53	/// Disable printing requests and responses.
54	pub no_print: bool,
55}
56
57#[derive(Default, Debug)]
58/// RPC instance.
59pub struct Rpc {
60	/// Underlying `IoHandler`.
61	pub io: rpc::IoHandler,
62	/// Options
63	pub options: Options,
64}
65
66/// Encoding format.
67pub enum Encoding {
68	/// Encodes params using `serde::to_string`.
69	Compact,
70	/// Encodes params using `serde::to_string_pretty`.
71	Pretty,
72}
73
74impl From<rpc::IoHandler> for Rpc {
75	fn from(io: rpc::IoHandler) -> Self {
76		Rpc {
77			io,
78			..Default::default()
79		}
80	}
81}
82
83impl Rpc {
84	/// Create a new RPC instance from a single delegate.
85	pub fn new<D>(delegate: D) -> Self
86	where
87		D: IntoIterator<Item = (String, rpc::RemoteProcedure<()>)>,
88	{
89		let mut io = rpc::IoHandler::new();
90		io.extend_with(delegate);
91		io.into()
92	}
93
94	/// Perform a single, synchronous method call and return pretty-printed value
95	pub fn request<T>(&self, method: &str, params: &T) -> String
96	where
97		T: serde::Serialize,
98	{
99		self.make_request(method, params, Encoding::Pretty)
100	}
101
102	/// Perform a single, synchronous method call.
103	pub fn make_request<T>(&self, method: &str, params: &T, encoding: Encoding) -> String
104	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
116			.io
117			.handle_request_sync(&request)
118			.expect("We are sending a method call not notification.");
119
120		// extract interesting part from the response
121		let extracted = match rpc::serde_from_str(&response).expect("We will always get a single output.") {
122			response::Output::Success(response::Success { result, .. }) => match encoding {
123				Encoding::Compact => serde_json::to_string(&result),
124				Encoding::Pretty => serde_json::to_string_pretty(&result),
125			},
126			response::Output::Failure(response::Failure { error, .. }) => match encoding {
127				Encoding::Compact => serde_json::to_string(&error),
128				Encoding::Pretty => serde_json::to_string_pretty(&error),
129			},
130		}
131		.expect("Serialization is infallible; qed");
132
133		println!("\n{}\n --> {}\n", request, extracted);
134
135		extracted
136	}
137}
138
139#[cfg(test)]
140mod tests {
141	use super::*;
142
143	#[test]
144	fn should_test_request_is_pretty() {
145		// given
146		let rpc = {
147			let mut io = rpc::IoHandler::new();
148			io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()])));
149			Rpc::from(io)
150		};
151
152		// when
153		assert_eq!(rpc.request("test_method", &[5u64]), "[\n  5,\n  10\n]");
154	}
155
156	#[test]
157	fn should_test_make_request_compact() {
158		// given
159		let rpc = {
160			let mut io = rpc::IoHandler::new();
161			io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()])));
162			Rpc::from(io)
163		};
164
165		// when
166		assert_eq!(rpc.make_request("test_method", &[5u64], Encoding::Compact), "[5,10]");
167	}
168}