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