gear_subxt/rpc/rpc_client.rs
1// Copyright 2019-2023 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use super::{RpcClientT, RpcSubscription, RpcSubscriptionId};
6use crate::error::Error;
7use futures::{Stream, StreamExt};
8use serde::{de::DeserializeOwned, Serialize};
9use serde_json::value::RawValue;
10use std::{pin::Pin, sync::Arc, task::Poll};
11
12/// A concrete wrapper around an [`RpcClientT`] which exposes the udnerlying interface via some
13/// higher level methods that make it a little easier to work with.
14///
15/// Wrapping [`RpcClientT`] in this way is simply a way to expose this additional functionality
16/// without getting into issues with non-object-safe methods or no `async` in traits.
17#[derive(Clone)]
18pub struct RpcClient(Arc<dyn RpcClientT>);
19
20impl RpcClient {
21 pub(crate) fn new<R: RpcClientT>(client: Arc<R>) -> Self {
22 RpcClient(client)
23 }
24
25 /// Make an RPC request, given a method name and some parameters.
26 ///
27 /// See [`RpcParams`] and the [`rpc_params!`] macro for an example of how to
28 /// construct the parameters.
29 pub async fn request<Res: DeserializeOwned>(
30 &self,
31 method: &str,
32 params: RpcParams,
33 ) -> Result<Res, Error> {
34 let res = self.0.request_raw(method, params.build()).await?;
35 let val = serde_json::from_str(res.get())?;
36 Ok(val)
37 }
38
39 /// Subscribe to an RPC endpoint, providing the parameters and the method to call to
40 /// unsubscribe from it again.
41 ///
42 /// See [`RpcParams`] and the [`rpc_params!`] macro for an example of how to
43 /// construct the parameters.
44 pub async fn subscribe<Res: DeserializeOwned>(
45 &self,
46 sub: &str,
47 params: RpcParams,
48 unsub: &str,
49 ) -> Result<Subscription<Res>, Error> {
50 let sub = self.0.subscribe_raw(sub, params.build(), unsub).await?;
51 Ok(Subscription::new(sub))
52 }
53}
54
55impl std::fmt::Debug for RpcClient {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_tuple("RpcClient").finish()
58 }
59}
60
61impl std::ops::Deref for RpcClient {
62 type Target = dyn RpcClientT;
63 fn deref(&self) -> &Self::Target {
64 &*self.0
65 }
66}
67
68/// Create some [`RpcParams`] to pass to our [`RpcClient`]. [`RpcParams`]
69/// simply enforces that parameters handed to our [`RpcClient`] methods
70/// are the correct shape.
71///
72/// As with the [`serde_json::json!`] macro, this will panic if you provide
73/// parameters which cannot successfully be serialized to JSON.
74///
75/// # Example
76///
77/// ```rust
78/// use subxt::rpc::{ rpc_params, RpcParams };
79///
80/// // If you provide no params you get `None` back
81/// let params: RpcParams = rpc_params![];
82/// assert!(params.build().is_none());
83///
84/// // If you provide params you get `Some<Box<RawValue>>` back.
85/// let params: RpcParams = rpc_params![1, true, "foo"];
86/// assert_eq!(params.build().unwrap().get(), "[1,true,\"foo\"]");
87/// ```
88#[macro_export]
89macro_rules! rpc_params {
90 ($($p:expr), *) => {{
91 // May be unused if empty; no params.
92 #[allow(unused_mut)]
93 let mut params = $crate::rpc::RpcParams::new();
94 $(
95 params.push($p).expect("values passed to rpc_params! must be serializable to JSON");
96 )*
97 params
98 }}
99}
100pub use rpc_params;
101
102/// This represents the parameters passed to an [`RpcClient`], and exists to
103/// enforce that parameters are provided in the correct format.
104///
105/// Prefer to use the [`rpc_params!`] macro for simpler creation of these.
106///
107/// # Example
108///
109/// ```rust
110/// use subxt::rpc::RpcParams;
111///
112/// let mut params = RpcParams::new();
113/// params.push(1).unwrap();
114/// params.push(true).unwrap();
115/// params.push("foo").unwrap();
116///
117/// assert_eq!(params.build().unwrap().get(), "[1,true,\"foo\"]");
118/// ```
119#[derive(Debug, Clone, Default)]
120pub struct RpcParams(Vec<u8>);
121
122impl RpcParams {
123 /// Create a new empty set of [`RpcParams`].
124 pub fn new() -> Self {
125 Self(Vec::new())
126 }
127 /// Push a parameter into our [`RpcParams`]. This serializes it to JSON
128 /// in the process, and so will return an error if this is not possible.
129 pub fn push<P: Serialize>(&mut self, param: P) -> Result<(), Error> {
130 if self.0.is_empty() {
131 self.0.push(b'[');
132 } else {
133 self.0.push(b',')
134 }
135 serde_json::to_writer(&mut self.0, ¶m)?;
136 Ok(())
137 }
138 /// Build a [`RawValue`] from our params, returning `None` if no parameters
139 /// were provided.
140 pub fn build(mut self) -> Option<Box<RawValue>> {
141 if self.0.is_empty() {
142 None
143 } else {
144 self.0.push(b']');
145 let s = unsafe { String::from_utf8_unchecked(self.0) };
146 Some(RawValue::from_string(s).expect("Should be valid JSON"))
147 }
148 }
149}
150
151/// A generic RPC Subscription. This implements [`Stream`], and so most of
152/// the functionality you'll need to interact with it comes from the
153/// [`StreamExt`] extension trait.
154pub struct Subscription<Res> {
155 inner: RpcSubscription,
156 _marker: std::marker::PhantomData<Res>,
157}
158
159impl<Res> std::fmt::Debug for Subscription<Res> {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("Subscription")
162 .field("inner", &"RpcSubscription")
163 .field("_marker", &self._marker)
164 .finish()
165 }
166}
167
168impl<Res> Subscription<Res> {
169 /// Creates a new [`Subscription`].
170 pub fn new(inner: RpcSubscription) -> Self {
171 Self {
172 inner,
173 _marker: std::marker::PhantomData,
174 }
175 }
176
177 /// Obtain the ID associated with this subscription.
178 pub fn subscription_id(&self) -> Option<&RpcSubscriptionId> {
179 self.inner.id.as_ref()
180 }
181}
182
183impl<Res: DeserializeOwned> Subscription<Res> {
184 /// Wait for the next item from the subscription.
185 pub async fn next(&mut self) -> Option<Result<Res, Error>> {
186 StreamExt::next(self).await
187 }
188}
189
190impl<Res> std::marker::Unpin for Subscription<Res> {}
191
192impl<Res: DeserializeOwned> Stream for Subscription<Res> {
193 type Item = Result<Res, Error>;
194
195 fn poll_next(
196 mut self: Pin<&mut Self>,
197 cx: &mut std::task::Context<'_>,
198 ) -> Poll<Option<Self::Item>> {
199 let res = futures::ready!(self.inner.stream.poll_next_unpin(cx));
200
201 // Decode the inner RawValue to the type we're expecting and map
202 // any errors to the right shape:
203 let res = res.map(|r| {
204 r.map_err(|e| e.into())
205 .and_then(|raw_val| serde_json::from_str(raw_val.get()).map_err(|e| e.into()))
206 });
207
208 Poll::Ready(res)
209 }
210}