jsonrpc_async/
client.rs

1// Rust JSON-RPC Library
2// Written in 2015 by
3//     Andrew Poelstra <apoelstra@wpsoftware.net>
4//
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9//
10// You should have received a copy of the CC0 Public Domain Dedication
11// along with this software.
12// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13//
14
15//! # Client support
16//!
17//! Support for connecting to JSONRPC servers over HTTP, sending requests,
18//! and parsing responses
19//!
20
21use std::borrow::Cow;
22use std::collections::HashMap;
23use std::fmt;
24use std::sync::atomic;
25
26use serde;
27use serde_json;
28use serde_json::value::RawValue;
29
30use super::{Request, Response};
31use crate::error::Error;
32use crate::util::HashableValue;
33use async_trait::async_trait;
34
35/// An interface for a transport over which to use the JSONRPC protocol.
36#[async_trait]
37pub trait Transport: Send + Sync + 'static {
38    /// Send an RPC request over the transport.
39    async fn send_request(&self, r: Request<'_>) -> Result<Response, Error>;
40    /// Send a batch of RPC requests over the transport.
41    async fn send_batch(&self, rs: &[Request<'_>]) -> Result<Vec<Response>, Error>;
42    /// Format the target of this transport.
43    /// I.e. the URL/socket/...
44    fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result;
45}
46
47/// A JSON-RPC client.
48///
49/// Create a new Client using one of the transport-specific constructors:
50/// - [Client::simple_http] for the built-in bare-minimum HTTP transport
51pub struct Client {
52    pub(crate) transport: Box<dyn Transport>,
53    nonce: atomic::AtomicUsize,
54}
55
56impl Client {
57    /// Creates a new client with the given transport.
58    pub fn with_transport<T: Transport>(transport: T) -> Client {
59        Client {
60            transport: Box::new(transport),
61            nonce: atomic::AtomicUsize::new(1),
62        }
63    }
64
65    /// Builds a request.
66    ///
67    /// To construct the arguments, one can use one of the shorthand methods
68    /// [jsonrpc::arg] or [jsonrpc::try_arg].
69    pub fn build_request<'a>(&self, method: &'a str, params: &'a [Box<RawValue>]) -> Request<'a> {
70        let nonce = self.nonce.fetch_add(1, atomic::Ordering::Relaxed);
71        Request {
72            method: method,
73            params: params,
74            id: serde_json::Value::from(nonce),
75            jsonrpc: Some("2.0"),
76        }
77    }
78
79    /// Sends a request to a client
80    pub async fn send_request(&self, request: Request<'_>) -> Result<Response, Error> {
81        self.transport.send_request(request).await
82    }
83
84    /// Sends a batch of requests to the client.  The return vector holds the response
85    /// for the request at the corresponding index.  If no response was provided, it's [None].
86    ///
87    /// Note that the requests need to have valid IDs, so it is advised to create the requests
88    /// with [build_request].
89    pub async fn send_batch(
90        &self,
91        requests: &[Request<'_>],
92    ) -> Result<Vec<Option<Response>>, Error> {
93        if requests.is_empty() {
94            return Err(Error::EmptyBatch);
95        }
96
97        // If the request body is invalid JSON, the response is a single response object.
98        // We ignore this case since we are confident we are producing valid JSON.
99        let responses = self.transport.send_batch(requests).await?;
100        if responses.len() > requests.len() {
101            return Err(Error::WrongBatchResponseSize);
102        }
103
104        //TODO(stevenroose) check if the server preserved order to avoid doing the mapping
105
106        // First index responses by ID and catch duplicate IDs.
107        let mut by_id = HashMap::with_capacity(requests.len());
108        for resp in responses.into_iter() {
109            let id = HashableValue(Cow::Owned(resp.id.clone()));
110            if let Some(dup) = by_id.insert(id, resp) {
111                return Err(Error::BatchDuplicateResponseId(dup.id));
112            }
113        }
114        // Match responses to the requests.
115        let results = requests
116            .into_iter()
117            .map(|r| by_id.remove(&HashableValue(Cow::Borrowed(&r.id))))
118            .collect();
119
120        // Since we're also just producing the first duplicate ID, we can also just produce the
121        // first incorrect ID in case there are multiple.
122        if let Some((id, _)) = by_id.into_iter().nth(0) {
123            return Err(Error::WrongBatchResponseId(id.0.into_owned()));
124        }
125
126        Ok(results)
127    }
128
129    /// Make a request and deserialize the response.
130    ///
131    /// To construct the arguments, one can use one of the shorthand methods
132    /// [jsonrpc::arg] or [jsonrpc::try_arg].
133    pub async fn call<R: for<'a> serde::de::Deserialize<'a>>(
134        &self,
135        method: &str,
136        args: &[Box<RawValue>],
137    ) -> Result<R, Error> {
138        let request = self.build_request(method, args);
139        let id = request.id.clone();
140
141        let response = self.send_request(request).await?;
142        if response.jsonrpc != None && response.jsonrpc != Some(From::from("2.0")) {
143            return Err(Error::VersionMismatch);
144        }
145        if response.id != id {
146            return Err(Error::NonceMismatch);
147        }
148
149        Ok(response.result()?)
150    }
151}
152
153impl fmt::Debug for Client {
154    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155        write!(f, "jsonrpc::Client(")?;
156        self.transport.fmt_target(f)?;
157        write!(f, ")")
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use std::sync;
165
166    struct DummyTransport;
167    #[async_trait]
168    impl Transport for DummyTransport {
169        async fn send_request(&self, _: Request<'_>) -> Result<Response, Error> {
170            Err(Error::NonceMismatch)
171        }
172        async fn send_batch(&self, _: &[Request<'_>]) -> Result<Vec<Response>, Error> {
173            Ok(vec![])
174        }
175        fn fmt_target(&self, _: &mut fmt::Formatter) -> fmt::Result {
176            Ok(())
177        }
178    }
179
180    #[test]
181    fn sanity() {
182        let client = Client::with_transport(DummyTransport);
183        assert_eq!(client.nonce.load(sync::atomic::Ordering::Relaxed), 1);
184        let req1 = client.build_request("test", &[]);
185        assert_eq!(client.nonce.load(sync::atomic::Ordering::Relaxed), 2);
186        let req2 = client.build_request("test", &[]);
187        assert_eq!(client.nonce.load(sync::atomic::Ordering::Relaxed), 3);
188        assert!(req1.id != req2.id);
189    }
190}