Skip to main content

accumulate_client/runtime/
rpc.rs

1// Allow unwrap in this module - HTTP client building with valid settings cannot fail
2#![allow(clippy::unwrap_used)]
3
4use serde::{Serialize, de::DeserializeOwned};
5use serde_json::json;
6use std::time::Duration;
7use crate::errors::Error;
8use async_trait::async_trait;
9
10#[derive(Debug, Clone)]
11pub struct HttpTransport {
12    pub base_url: String,
13    pub client: reqwest::Client,
14    pub timeout: Duration,
15}
16
17impl HttpTransport {
18    pub fn new(base_url: impl Into<String>) -> Self {
19        Self {
20            base_url: base_url.into(),
21            client: reqwest::Client::builder()
22                .timeout(Duration::from_secs(30))
23                .build()
24                .unwrap(),
25            timeout: Duration::from_secs(30),
26        }
27    }
28
29    pub fn with_timeout(base_url: impl Into<String>, timeout: Duration) -> Self {
30        Self {
31            base_url: base_url.into(),
32            client: reqwest::Client::builder()
33                .timeout(timeout)
34                .build()
35                .unwrap(),
36            timeout,
37        }
38    }
39}
40
41#[async_trait]
42impl crate::generated::api_methods::AccumulateRpc for HttpTransport {
43    async fn rpc_call<TP: Serialize + Send + Sync, TR: DeserializeOwned>(
44        &self, method: &str, params: &TP
45    ) -> Result<TR, Error> {
46        let payload = json!({
47            "jsonrpc": "2.0",
48            "id": 1,
49            "method": method,
50            "params": params
51        });
52
53        let res = self.client.post(&self.base_url)
54            .json(&payload)
55            .send().await
56            .map_err(|e| Error::General(format!("Transport error: {}", e)))?;
57
58        if !res.status().is_success() {
59            return Err(Error::General(format!("HTTP status error: {}", res.status().as_u16())));
60        }
61
62        let v: serde_json::Value = res.json().await
63            .map_err(|e| Error::General(format!("Failed to parse JSON response: {}", e)))?;
64
65        if let Some(e) = v.get("error") {
66            let code = e.get("code").and_then(|c| c.as_i64()).unwrap_or(0) as i32;
67            let message = e.get("message").and_then(|m| m.as_str()).unwrap_or("rpc error").to_string();
68            return Err(Error::General(format!("RPC error {}: {}", code, message)));
69        }
70
71        let result = v.get("result").ok_or_else(|| Error::General("missing result".into()))?;
72        let typed: TR = serde_json::from_value(result.clone())
73            .map_err(|e| Error::General(format!("Failed to deserialize result: {}", e)))?;
74        Ok(typed)
75    }
76}