ic_web3_rs/
helpers.rs

1//! Web3 helpers.
2
3use crate::{error, rpc, Error};
4use futures::{
5    task::{Context, Poll},
6    Future,
7};
8use pin_project::pin_project;
9use serde::de::DeserializeOwned;
10use std::{marker::PhantomData, pin::Pin};
11
12/// Takes any type which is deserializable from rpc::Value and such a value and
13/// yields the deserialized value
14pub fn decode<T: serde::de::DeserializeOwned>(value: rpc::Value) -> error::Result<T> {
15    serde_json::from_value(value).map_err(Into::into)
16}
17
18/// Calls decode on the result of the wrapped future.
19#[pin_project]
20#[derive(Debug)]
21pub struct CallFuture<T, F> {
22    #[pin]
23    inner: F,
24    _marker: PhantomData<T>,
25}
26
27impl<T, F> CallFuture<T, F> {
28    /// Create a new CallFuture wrapping the inner future.
29    pub fn new(inner: F) -> Self {
30        CallFuture {
31            inner,
32            _marker: PhantomData,
33        }
34    }
35}
36
37impl<T, F> Future for CallFuture<T, F>
38where
39    T: serde::de::DeserializeOwned,
40    F: Future<Output = error::Result<rpc::Value>>,
41{
42    type Output = error::Result<T>;
43
44    fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
45        let this = self.project();
46        let x = ready!(this.inner.poll(ctx));
47        Poll::Ready(x.and_then(decode))
48    }
49}
50
51/// Serialize a type. Panics if the type is returns error during serialization.
52pub fn serialize<T: serde::Serialize>(t: &T) -> rpc::Value {
53    serde_json::to_value(t).expect("Types never fail to serialize.")
54}
55
56/// Serializes a request to string. Panics if the type returns error during serialization.
57pub fn to_string<T: serde::Serialize>(request: &T) -> String {
58    serde_json::to_string(&request).expect("String serialization never fails.")
59}
60
61/// Build a JSON-RPC request.
62pub fn build_request(id: usize, method: &str, params: Vec<rpc::Value>) -> rpc::Call {
63    rpc::Call::MethodCall(rpc::MethodCall {
64        jsonrpc: Some(rpc::Version::V2),
65        method: method.into(),
66        params: rpc::Params::Array(params),
67        id: rpc::Id::Num(id as u64),
68    })
69}
70
71/// Parse bytes slice into JSON-RPC response.
72/// It looks for arbitrary_precision feature as a temporary workaround for https://github.com/tomusdrw/rust-web3/issues/460.
73pub fn to_response_from_slice(response: &[u8]) -> error::Result<rpc::Response> {
74    arbitrary_precision_deserialize_workaround(response).map_err(|e| Error::InvalidResponse(format!("{:?}", e)))
75}
76
77/// Deserialize bytes into T.
78/// It looks for arbitrary_precision feature as a temporary workaround for https://github.com/tomusdrw/rust-web3/issues/460.
79pub fn arbitrary_precision_deserialize_workaround<T>(bytes: &[u8]) -> Result<T, serde_json::Error>
80where
81    T: DeserializeOwned,
82{
83    if cfg!(feature = "arbitrary_precision") {
84        serde_json::from_value(serde_json::from_slice(bytes)?)
85    } else {
86        serde_json::from_slice(bytes)
87    }
88}
89
90/// Parse bytes slice into JSON-RPC notification.
91pub fn to_notification_from_slice(notification: &[u8]) -> error::Result<rpc::Notification> {
92    serde_json::from_slice(notification).map_err(|e| error::Error::InvalidResponse(format!("{:?}", e)))
93}
94
95/// Parse a Vec of `rpc::Output` into `Result`.
96pub fn to_results_from_outputs(outputs: Vec<rpc::Output>) -> error::Result<Vec<error::Result<rpc::Value>>> {
97    Ok(outputs.into_iter().map(to_result_from_output).collect())
98}
99
100/// Parse `rpc::Output` into `Result`.
101pub fn to_result_from_output(output: rpc::Output) -> error::Result<rpc::Value> {
102    match output {
103        rpc::Output::Success(success) => Ok(success.result),
104        rpc::Output::Failure(failure) => Err(error::Error::Rpc(failure.error)),
105    }
106}
107
108#[macro_use]
109#[cfg(test)]
110pub mod tests {
111    macro_rules! rpc_test {
112    // With parameters
113    (
114      $namespace: ident : $name: ident : $test_name: ident  $(, $param: expr)+ => $method: expr,  $results: expr;
115      $returned: expr => $expected: expr
116    ) => {
117      #[test]
118      fn $test_name() {
119        // given
120        let mut transport = $crate::transports::test::TestTransport::default();
121        transport.set_response($returned);
122        let result = {
123          let eth = $namespace::new(&transport);
124
125          // when
126          eth.$name($($param.into(), )+)
127        };
128
129        // then
130        transport.assert_request($method, &$results.into_iter().map(Into::into).collect::<Vec<_>>());
131        transport.assert_no_more_requests();
132        let result = futures::executor::block_on(result);
133        assert_eq!(result, Ok($expected.into()));
134      }
135    };
136    // With parameters (implicit test name)
137    (
138      $namespace: ident : $name: ident $(, $param: expr)+ => $method: expr,  $results: expr;
139      $returned: expr => $expected: expr
140    ) => {
141      rpc_test! (
142        $namespace : $name : $name $(, $param)+ => $method, $results;
143        $returned => $expected
144      );
145    };
146
147    // No params entry point (explicit name)
148    (
149      $namespace: ident: $name: ident: $test_name: ident => $method: expr;
150      $returned: expr => $expected: expr
151    ) => {
152      #[test]
153      fn $test_name() {
154        // given
155        let mut transport = $crate::transports::test::TestTransport::default();
156        transport.set_response($returned);
157        let result = {
158          let eth = $namespace::new(&transport);
159
160          // when
161          eth.$name()
162        };
163
164        // then
165        transport.assert_request($method, &[]);
166        transport.assert_no_more_requests();
167        let result = futures::executor::block_on(result);
168        assert_eq!(result, Ok($expected.into()));
169      }
170    };
171
172    // No params entry point
173    (
174      $namespace: ident: $name: ident => $method: expr;
175      $returned: expr => $expected: expr
176    ) => {
177      rpc_test! (
178        $namespace: $name: $name => $method;
179        $returned => $expected
180      );
181    }
182  }
183}