bitcoin_rpc_midas/transport/
core.rs1use serde_json::Value;
2use thiserror::Error;
3use {reqwest, serde};
4
5#[derive(Debug, Error, serde::Serialize, serde::Deserialize)]
6pub enum TransportError {
7 #[error("HTTP error: {0}")]
8 Http(String),
9 #[error("JSON error: {0}")]
10 Json(String),
11 #[error("RPC error: {0}")]
12 Rpc(String),
13}
14
15impl From<reqwest::Error> for TransportError {
16 fn from(err: reqwest::Error) -> Self { TransportError::Http(err.to_string()) }
17}
18
19impl From<serde_json::Error> for TransportError {
20 fn from(err: serde_json::Error) -> Self { TransportError::Json(err.to_string()) }
21}
22
23impl From<anyhow::Error> for TransportError {
24 fn from(err: anyhow::Error) -> Self { TransportError::Rpc(err.to_string()) }
25}
26
27pub trait TransportTrait: Send + Sync {
28 fn send_request<'a>(
29 &'a self,
30 method: &'a str,
31 params: &'a [Value],
32 ) -> std::pin::Pin<
33 Box<dyn std::future::Future<Output = Result<Value, TransportError>> + Send + 'a>,
34 >;
35
36 fn send_batch<'a>(
41 &'a self,
42 bodies: &'a [Value],
43 ) -> std::pin::Pin<
44 Box<dyn std::future::Future<Output = Result<Vec<Value>, TransportError>> + Send + 'a>,
45 >;
46
47 fn url(&self) -> &str;
48}
49pub trait TransportExt {
50 fn call<'a, T: serde::de::DeserializeOwned>(
51 &'a self,
52 method: &'a str,
53 params: &'a [Value],
54 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, TransportError>> + Send + 'a>>;
55}
56
57impl<T: TransportTrait> TransportExt for T {
58 fn call<'a, T2: serde::de::DeserializeOwned>(
59 &'a self,
60 method: &'a str,
61 params: &'a [Value],
62 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T2, TransportError>> + Send + 'a>>
63 {
64 Box::pin(async move {
65 let result = self.send_request(method, params).await?;
66 Ok(serde_json::from_value(result)?)
67 })
68 }
69}
70
71#[derive(Clone, Debug)]
72pub struct DefaultTransport {
73 client: reqwest::Client,
74 url: String,
75 auth: Option<(String, String)>,
76 wallet_name: Option<String>,
77}
78
79impl DefaultTransport {
80 pub fn new(url: impl Into<String>, auth: Option<(String, String)>) -> Self {
81 Self { client: reqwest::Client::new(), url: url.into(), auth, wallet_name: None }
82 }
83
84 pub fn with_wallet(mut self, wallet_name: impl Into<String>) -> Self {
85 self.wallet_name = Some(wallet_name.into());
86 self
87 }
88}
89
90impl TransportTrait for DefaultTransport {
91 fn send_request<'a>(
92 &'a self,
93 method: &'a str,
94 params: &'a [Value],
95 ) -> std::pin::Pin<
96 Box<dyn std::future::Future<Output = Result<Value, TransportError>> + Send + 'a>,
97 > {
98 let client = self.client.clone();
99 let url = self.url.clone();
100 let auth = self.auth.clone();
101 let wallet_name = self.wallet_name.clone();
102 Box::pin(async move {
103 let request = serde_json::json!({
104 "jsonrpc": "2.0", "id": "1", "method": method, "params": params
105 });
106 eprintln!("[debug] Sending request to {}", url);
107
108 if let Some(wallet) = &wallet_name {
110 let wallet_url = format!("{}/wallet/{}", url.trim_end_matches('/'), wallet);
111
112 let mut req = client.post(&wallet_url).json(&request);
114 if let Some((username, password)) = &auth {
115 req = req.basic_auth(username, Some(password));
116 }
117 let response = match req.send().await {
118 Ok(resp) => {
119 eprintln!("[debug] Response status: {}", resp.status());
120 resp
121 }
122 Err(e) => return Err(TransportError::Http(e.to_string())),
123 };
124
125 let text =
126 response.text().await.map_err(|e| TransportError::Http(e.to_string()))?;
127 eprintln!("[debug] Response body: {}", text);
128 let json: Value =
129 serde_json::from_str(&text).map_err(|e| TransportError::Json(e.to_string()))?;
130
131 if let Some(error) = json.get("error") {
132 if error.get("code").and_then(|c| c.as_i64()) == Some(-32601) {
134 let mut req = client.post(&url).json(&request);
135 if let Some((username, password)) = &auth {
136 req = req.basic_auth(username, Some(password));
137 }
138 let response = match req.send().await {
139 Ok(resp) => {
140 eprintln!("[debug] Base response status: {}", resp.status());
141 resp
142 }
143 Err(e) => return Err(TransportError::Http(e.to_string())),
144 };
145 let text = response
146 .text()
147 .await
148 .map_err(|e| TransportError::Http(e.to_string()))?;
149 eprintln!("[debug] Base response body: {}", text);
150 let json: Value = serde_json::from_str(&text)
151 .map_err(|e| TransportError::Json(e.to_string()))?;
152 if let Some(error) = json.get("error") {
153 return Err(TransportError::Rpc(error.to_string()));
154 }
155 return json
156 .get("result")
157 .cloned()
158 .ok_or_else(|| TransportError::Rpc("No result field".to_string()));
159 } else {
160 return Err(TransportError::Rpc(error.to_string()));
161 }
162 }
163
164 return json
165 .get("result")
166 .cloned()
167 .ok_or_else(|| TransportError::Rpc("No result field".to_string()));
168 }
169
170 let mut req = client.post(&url).json(&request);
172 if let Some((username, password)) = &auth {
173 req = req.basic_auth(username, Some(password));
174 }
175 let response = match req.send().await {
176 Ok(resp) => {
177 eprintln!("[debug] Response status: {}", resp.status());
178 resp
179 }
180 Err(e) => return Err(TransportError::Http(e.to_string())),
181 };
182 let text = response.text().await.map_err(|e| TransportError::Http(e.to_string()))?;
183 eprintln!("[debug] Response body: {}", text);
184 let json: Value =
185 serde_json::from_str(&text).map_err(|e| TransportError::Json(e.to_string()))?;
186 if let Some(error) = json.get("error") {
187 return Err(TransportError::Rpc(error.to_string()));
188 }
189 json.get("result")
190 .cloned()
191 .ok_or_else(|| TransportError::Rpc("No result field".to_string()))
192 })
193 }
194
195 fn send_batch<'a>(
196 &'a self,
197 bodies: &'a [Value],
198 ) -> std::pin::Pin<
199 Box<dyn std::future::Future<Output = Result<Vec<Value>, TransportError>> + Send + 'a>,
200 > {
201 let client = self.client.clone();
202 let url = self.url.clone();
203 let auth = self.auth.clone();
204 Box::pin(async move {
205 eprintln!("[debug] Sending batch request to {}: {:?}", url, bodies);
206 let mut req = client.post(&url).json(bodies);
207 if let Some((username, password)) = &auth {
208 req = req.basic_auth(username, Some(password));
209 }
210 let response = match req.send().await {
211 Ok(resp) => {
212 eprintln!("[debug] Batch response status: {}", resp.status());
213 resp
214 }
215 Err(e) => return Err(TransportError::Http(e.to_string())),
216 };
217 let text = response.text().await.map_err(|e| TransportError::Http(e.to_string()))?;
218 eprintln!("[debug] Batch response body: {}", text);
219 let v: Vec<Value> =
220 serde_json::from_str(&text).map_err(|e| TransportError::Json(e.to_string()))?;
221 Ok(v)
222 })
223 }
224
225 fn url(&self) -> &str { &self.url }
226}