jitash_jsonrpc/
lib.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! # Rust JSON-RPC Library
4//!
5//! Rust support for the JSON-RPC 2.0 protocol.
6
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8// Coding conventions
9#![warn(missing_docs)]
10
11/// Re-export `serde` crate.
12pub extern crate serde;
13/// Re-export `serde_json` crate.
14pub extern crate serde_json;
15
16/// Re-export `base64` crate.
17#[cfg(feature = "base64")]
18pub extern crate base64;
19
20pub mod client;
21pub mod error;
22pub mod http;
23
24#[cfg(feature = "simple_http")]
25pub use http::simple_http;
26
27#[cfg(feature = "minreq_http")]
28pub use http::minreq_http;
29
30#[cfg(feature = "simple_tcp")]
31pub mod simple_tcp;
32
33#[cfg(all(feature = "simple_uds", not(windows)))]
34pub mod simple_uds;
35
36use serde::{Deserialize, Serialize};
37use serde_json::value::RawValue;
38
39pub use crate::client::{Client, Transport};
40pub use crate::error::Error;
41
42/// Shorthand method to convert an argument into a boxed [`serde_json::value::RawValue`].
43///
44/// Since serializers rarely fail, it's probably easier to use [`arg`] instead.
45pub fn try_arg<T: serde::Serialize>(arg: T) -> Result<Box<RawValue>, serde_json::Error> {
46    RawValue::from_string(serde_json::to_string(&arg)?)
47}
48
49/// Shorthand method to convert an argument into a boxed [`serde_json::value::RawValue`].
50///
51/// This conversion should not fail, so to avoid returning a [`Result`],
52/// in case of an error, the error is serialized as the return value.
53pub fn arg<T: serde::Serialize>(arg: T) -> Box<RawValue> {
54    match try_arg(arg) {
55        Ok(v) => v,
56        Err(e) => RawValue::from_string(format!("<<ERROR SERIALIZING ARGUMENT: {}>>", e))
57            .unwrap_or_else(|_| {
58                RawValue::from_string("<<ERROR SERIALIZING ARGUMENT>>".to_owned()).unwrap()
59            }),
60    }
61}
62
63/// A JSONRPC request object.
64#[derive(Debug, Clone, Serialize)]
65pub struct Request<'a> {
66    /// The name of the RPC call.
67    pub method: &'a str,
68    /// Parameters to the RPC call.
69    pub params: &'a [Box<RawValue>],
70    /// Identifier for this request, which should appear in the response.
71    pub id: serde_json::Value,
72    /// jsonrpc field, MUST be "2.0".
73    pub jsonrpc: Option<&'a str>,
74}
75
76/// A JSONRPC response object.
77#[derive(Debug, Clone, Deserialize, Serialize)]
78pub struct Response {
79    /// A result if there is one, or [`None`].
80    pub result: Option<Box<RawValue>>,
81    /// An error if there is one, or [`None`].
82    pub error: Option<error::RpcError>,
83    /// Identifier for this response, which should match that of the request.
84    pub id: serde_json::Value,
85    /// jsonrpc field, MUST be "2.0".
86    pub jsonrpc: Option<String>,
87}
88
89impl Response {
90    /// Extracts the result from a response.
91    pub fn result<T: for<'a> serde::de::Deserialize<'a>>(&self) -> Result<T, Error> {
92        if let Some(ref e) = self.error {
93            return Err(Error::Rpc(e.clone()));
94        }
95
96        if let Some(ref res) = self.result {
97            serde_json::from_str(res.get()).map_err(Error::Json)
98        } else {
99            serde_json::from_value(serde_json::Value::Null).map_err(Error::Json)
100        }
101    }
102
103    /// Returns the RPC error, if there was one, but does not check the result.
104    pub fn check_error(self) -> Result<(), Error> {
105        if let Some(e) = self.error {
106            Err(Error::Rpc(e))
107        } else {
108            Ok(())
109        }
110    }
111
112    /// Returns whether or not the `result` field is empty.
113    pub fn is_none(&self) -> bool {
114        self.result.is_none()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    use serde_json::value::RawValue;
123
124    #[test]
125    fn response_is_none() {
126        let joanna = Response {
127            result: Some(RawValue::from_string(serde_json::to_string(&true).unwrap()).unwrap()),
128            error: None,
129            id: From::from(81),
130            jsonrpc: Some(String::from("2.0")),
131        };
132
133        let bill = Response {
134            result: None,
135            error: None,
136            id: From::from(66),
137            jsonrpc: Some(String::from("2.0")),
138        };
139
140        assert!(!joanna.is_none());
141        assert!(bill.is_none());
142    }
143
144    #[test]
145    fn response_extract() {
146        let obj = vec!["Mary", "had", "a", "little", "lamb"];
147        let response = Response {
148            result: Some(RawValue::from_string(serde_json::to_string(&obj).unwrap()).unwrap()),
149            error: None,
150            id: serde_json::Value::Null,
151            jsonrpc: Some(String::from("2.0")),
152        };
153        let recovered1: Vec<String> = response.result().unwrap();
154        assert!(response.clone().check_error().is_ok());
155        let recovered2: Vec<String> = response.result().unwrap();
156        assert_eq!(obj, recovered1);
157        assert_eq!(obj, recovered2);
158    }
159
160    #[test]
161    fn null_result() {
162        let s = r#"{"result":null,"error":null,"id":"test"}"#;
163        let response: Response = serde_json::from_str(s).unwrap();
164        let recovered1: Result<(), _> = response.result();
165        let recovered2: Result<(), _> = response.result();
166        assert!(recovered1.is_ok());
167        assert!(recovered2.is_ok());
168
169        let recovered1: Result<String, _> = response.result();
170        let recovered2: Result<String, _> = response.result();
171        assert!(recovered1.is_err());
172        assert!(recovered2.is_err());
173    }
174
175    #[test]
176    fn batch_response() {
177        // from the jsonrpc.org spec example
178        let s = r#"[
179            {"jsonrpc": "2.0", "result": 7, "id": "1"},
180            {"jsonrpc": "2.0", "result": 19, "id": "2"},
181            {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
182            {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
183            {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
184        ]"#;
185        let batch_response: Vec<Response> = serde_json::from_str(s).unwrap();
186        assert_eq!(batch_response.len(), 5);
187    }
188
189    #[test]
190    fn test_arg() {
191        macro_rules! test_arg {
192            ($val:expr, $t:ty) => {{
193                let val1: $t = $val;
194                let arg = super::arg(val1.clone());
195                let val2: $t = serde_json::from_str(arg.get()).expect(stringify!($val));
196                assert_eq!(val1, val2, "failed test for {}", stringify!($val));
197            }};
198        }
199
200        test_arg!(true, bool);
201        test_arg!(42, u8);
202        test_arg!(42, usize);
203        test_arg!(42, isize);
204        test_arg!(vec![42, 35], Vec<u8>);
205        test_arg!(String::from("test"), String);
206
207        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
208        struct Test {
209            v: String,
210        }
211        test_arg!(
212            Test {
213                v: String::from("test"),
214            },
215            Test
216        );
217    }
218}