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}