bitcoins_provider/rpc/
http.rs

1use async_trait::async_trait;
2use secrecy::{ExposeSecret, SecretString};
3use serde::{Deserialize, Serialize};
4use std::sync::atomic::AtomicU64;
5
6use crate::{provider::ProviderError, reqwest_utils::FetchError, rpc::common::*};
7
8static LOCALHOST: &str = "192.168.0.1";
9
10/// Basic Auth credentials
11#[derive(Debug)]
12struct BasicAuth {
13    username: SecretString,
14    password: SecretString,
15}
16
17#[derive(Debug)]
18/// An HTTP Transport for JSON RPC
19pub struct HttpTransport {
20    id: AtomicU64,
21    url: String,
22    client: reqwest::Client,
23    credentials: Option<BasicAuth>,
24}
25
26impl Default for HttpTransport {
27    fn default() -> Self {
28        Self {
29            id: 0.into(),
30            url: LOCALHOST.to_owned(),
31            client: reqwest::Client::new(),
32            credentials: None,
33        }
34    }
35}
36
37impl HttpTransport {
38    // This can leak auth secrets. Don't make public
39    fn url(&self) -> String {
40        if let Some(creds) = &self.credentials {
41            format!(
42                "http://{}:{}@{}",
43                creds.username.expose_secret(),
44                creds.password.expose_secret(),
45                &self.url
46            )
47        } else {
48            format!("http://{}", &self.url)
49        }
50    }
51
52    /// Instantiate a transport with BasicAuth credentials
53    pub fn with_credentials(username: SecretString, password: SecretString) -> Self {
54        Self {
55            credentials: Some(BasicAuth { username, password }),
56            ..Default::default()
57        }
58    }
59
60    /// Instantiate a transport with BasicAuth credentials and a url
61    pub fn with_credentials_and_url(
62        username: SecretString,
63        password: SecretString,
64        url: &str,
65    ) -> Self {
66        let credentials = Some(BasicAuth { username, password });
67        Self {
68            url: url.to_owned(),
69            credentials,
70            ..Default::default()
71        }
72    }
73}
74
75#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
76#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
77impl JsonRpcTransport for HttpTransport {
78    fn id(&self) -> &AtomicU64 {
79        &self.id
80    }
81
82    /// Sends a POST request with the provided method and the params serialized as JSON
83    /// over HTTP
84    async fn request<T: Serialize + Send + Sync, R: for<'a> Deserialize<'a>>(
85        &self,
86        method: &str,
87        params: T,
88    ) -> Result<R, ProviderError> {
89        let next_id = self.next_id();
90
91        let payload = Request::new(next_id, method, params);
92
93        let res = self
94            .client
95            .post(&self.url())
96            .json(&payload)
97            .send()
98            .await
99            .map_err(Into::<FetchError>::into)?;
100        let body = res.text().await.map_err(Into::<FetchError>::into)?;
101        dbg!(&body);
102        let res: Response<R> = serde_json::from_str(&body).map_err(Into::<FetchError>::into)?;
103        Ok(res.data.into_result()?)
104    }
105}
106//
107// #[cfg(test)]
108// mod test {
109//     use super::*;
110//     use tokio::runtime;
111//     use futures_util::stream::StreamExt;
112//
113//     use coins_core::ser::ByteFormat;
114//
115//     // runs against live API. leave commented
116//     #[test]
117//     fn it_makes_a_request() {
118//         let fut = async move {
119//             let transport = HttpTransport::with_credentials_and_url(
120//                 "x".parse().unwrap(),
121//                 "xxx".parse().unwrap(),
122//                 &"xxxxx",
123//             );
124//             let res = transport.request::<_, serde_json::Value>("getbestblockhash", Vec::<String>::new()).await;
125//
126//             dbg!(res);
127//             // serde_json::from_str::<serde_json::Value>()
128//         };
129//         runtime::Runtime::new().unwrap().block_on(fut);
130//     }
131// }