nimiq_jsonrpc_client/
lib.rs

1//! This crate implements multiple JSON-RPC clients. Currently available are: `http` and `websocket`. Only `websocket`
2//! supports *PubSub*.
3//!
4//! Instead of using a [`Client`] implementation and calling [`Client::send_request`] directly, you can derive a proxy
5//! struct that implements methods for ergonomic RPC calling:
6//!
7//! ```rust
8//! use async_trait::async_trait;
9//!
10//! #[nimiq_jsonrpc_derive::proxy]
11//! #[async_trait]
12//! trait Foobar {
13//!     type Error;
14//!     async fn hello(&self, name: String) -> Result<String, Self::Error>;
15//! }
16//!```
17//!
18//! # TODO
19//!
20//!  - Implement PubSub
21//!  - Proper error handling
22//!
23
24#![warn(missing_docs)]
25#![warn(rustdoc::missing_doc_code_examples)]
26
27/// An implementation of JSON-RPC over HTTP post requests. Feature `http` must be enabled:
28///
29/// ```toml
30/// [dependencies]
31/// nimiq-jsonrpc-client = { version = "...", features = ["http"] }
32/// ```
33///
34#[cfg(feature = "http-client")]
35pub mod http;
36
37/// An implementation of JSON-RPC over websockets. Feature `websocket` must be enabled:
38///
39/// ```toml
40/// [dependencies]
41/// nimiq-jsonrpc-client = { version = "...", features = ["websocket"] }
42/// ```
43///
44#[cfg(feature = "websocket-client")]
45pub mod websocket;
46
47#[cfg(feature = "wasm-websocket-client")]
48pub mod wasm_websocket;
49
50use std::{fmt::Debug, sync::Arc};
51
52use async_trait::async_trait;
53use futures::{lock::Mutex, stream::BoxStream};
54use serde::{de::Deserialize, ser::Serialize};
55
56use nimiq_jsonrpc_core::{Sensitive, SubscriptionId};
57
58#[async_trait]
59/// This trait must be implemented by the client's transport. It is responsible to send the request and return the
60/// server's response.
61///
62/// # TODO
63///
64///  - Support sending notifications (i.e. don't set the request ID and always a `Result<(), Self::Error>`.
65///
66pub trait Client {
67    /// Error type that this client returns.
68    type Error: Debug;
69
70    /// Sends a JSON-HTTP request
71    ///
72    /// # Arguments
73    ///
74    ///  - `method`: The name of the method to call.
75    ///  - `params`: The request parameters. This can be anything that implements [`serde::ser::Serialize`], but
76    ///              should serialize to a struct containing the named method arguments.
77    ///
78    /// # Returns
79    ///
80    /// Returns either the result that was responded with by the server, or an error. The error can be either a
81    /// client-side error (e.g. a network error), or an error object sent by the server.
82    ///
83    async fn send_request<P, R>(&self, method: &str, params: &P) -> Result<R, Self::Error>
84    where
85        P: Serialize + Debug + Send + Sync,
86        R: for<'de> Deserialize<'de> + Debug + Send + Sync;
87
88    /// If the client supports streams (i.e. receiving notifications), this should return a stream for the specific
89    /// subscription ID.
90    ///
91    /// # Arguments
92    ///
93    ///  - `id`: The subscription ID
94    ///
95    /// # Returns
96    ///
97    /// Returns a stream of items of type `T` that are received as notifications with the specific subscription ID.
98    ///
99    /// # Panics
100    ///
101    /// If the client doesn't support receiving notifications, this method is allowed to panic.
102    ///
103    async fn connect_stream<T: Unpin + 'static>(&self, id: SubscriptionId) -> BoxStream<'static, T>
104    where
105        T: for<'de> Deserialize<'de> + Debug + Send + Sync;
106
107    /// If the client supports streams (i.e. receiving notifications) and there is a matching subscription ID, this
108    /// should close the corresponding stream.
109    ///
110    /// # Arguments
111    ///
112    ///  - `id`: The subscription ID
113    ///
114    /// # Returns
115    ///
116    /// Returns a result on whether or not the client was able to unsubscribe from the stream.
117    ///
118    /// # Panics
119    ///
120    /// If the client doesn't support receiving notifications, this method is allowed to panic.
121    ///
122    async fn disconnect_stream(&self, id: SubscriptionId) -> Result<(), Self::Error>;
123
124    /// Closes the client connection
125    async fn close(&self);
126}
127
128/// Wraps a client into an `Arc<Mutex<_>>`, so that it can be cloned.
129pub struct ArcClient<C> {
130    inner: Arc<Mutex<C>>,
131}
132
133#[async_trait]
134impl<C: Client + Send> Client for ArcClient<C> {
135    type Error = <C as Client>::Error;
136
137    async fn send_request<P, R>(&self, method: &str, params: &P) -> Result<R, Self::Error>
138    where
139        P: Serialize + Debug + Send + Sync,
140        R: for<'de> Deserialize<'de> + Debug + Send + Sync,
141    {
142        self.inner.lock().await.send_request(method, params).await
143    }
144
145    async fn connect_stream<T: Unpin + 'static>(&self, id: SubscriptionId) -> BoxStream<'static, T>
146    where
147        T: for<'de> Deserialize<'de> + Debug + Send + Sync,
148    {
149        self.inner.lock().await.connect_stream(id).await
150    }
151
152    async fn disconnect_stream(&self, id: SubscriptionId) -> Result<(), Self::Error> {
153        self.inner.lock().await.disconnect_stream(id).await
154    }
155
156    async fn close(&self) {
157        self.inner.lock().await.close().await
158    }
159}
160
161impl<C: Client> ArcClient<C> {
162    /// Creates a new `ArcClient` from the inner client.
163    pub fn new(inner: C) -> Self {
164        Self {
165            inner: Arc::new(Mutex::new(inner)),
166        }
167    }
168}
169
170impl<C> Clone for ArcClient<C> {
171    fn clone(&self) -> Self {
172        ArcClient {
173            inner: Arc::clone(&self.inner),
174        }
175    }
176}
177
178/// Basic auth credentials, containing username and password.
179#[derive(Clone, Debug)]
180pub struct Credentials {
181    /// Username.
182    pub username: String,
183    /// Password.
184    pub password: Sensitive<String>,
185}
186
187impl Credentials {
188    /// Create basic auth credentials from username and password.
189    pub fn new<T: Into<String>, U: Into<String>>(username: T, password: U) -> Credentials {
190        Credentials {
191            username: username.into(),
192            password: Sensitive(password.into()),
193        }
194    }
195}