Skip to main content

accumulate_client/
json_rpc_client.rs

1//! GENERATED BY Accumulate gen-sdk. DO NOT EDIT.
2
3#![allow(missing_docs)]
4
5use reqwest::Client;
6use serde::de::DeserializeOwned;
7use serde_json::Value;
8use std::time::Duration;
9use thiserror::Error;
10use url::Url;
11
12#[derive(Error, Debug)]
13pub enum JsonRpcError {
14    #[error("HTTP request failed: {0}")]
15    Http(#[from] reqwest::Error),
16
17    #[error("JSON serialization/deserialization failed: {0}")]
18    Json(#[from] serde_json::Error),
19
20    #[error("JSON-RPC error: code={code}, message={message}")]
21    Rpc { code: i32, message: String },
22
23    #[error("Invalid URL: {0}")]
24    InvalidUrl(#[from] url::ParseError),
25
26    #[error("General error: {0}")]
27    General(#[from] anyhow::Error),
28}
29
30/// JSON-RPC client for Accumulate API calls
31#[derive(Debug, Clone)]
32pub struct JsonRpcClient {
33    pub base_url: Url,
34    pub http: Client,
35}
36
37impl JsonRpcClient {
38    /// Create a new JSON-RPC client
39    pub fn new(base_url: Url) -> Result<Self, JsonRpcError> {
40        let http = Client::builder().timeout(Duration::from_secs(30)).build()?;
41
42        Ok(Self { base_url, http })
43    }
44
45    /// Create a new JSON-RPC client with custom HTTP client
46    pub fn with_client(base_url: Url, http: Client) -> Result<Self, JsonRpcError> {
47        Ok(Self { base_url, http })
48    }
49
50    /// Make a generic JSON-RPC call
51    pub async fn call<T: DeserializeOwned>(
52        &self,
53        method: &str,
54        params: Value,
55    ) -> Result<T, JsonRpcError> {
56        let request_body = serde_json::json!({
57            "jsonrpc": "2.0",
58            "id": 1,
59            "method": method,
60            "params": params
61        });
62
63        let response = self
64            .http
65            .post(self.base_url.clone())
66            .json(&request_body)
67            .send()
68            .await?;
69
70        let response_text = response.text().await?;
71        let response_json: Value = serde_json::from_str(&response_text)?;
72
73        if let Some(error) = response_json.get("error") {
74            let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
75            let message = error
76                .get("message")
77                .and_then(|m| m.as_str())
78                .unwrap_or("Unknown error")
79                .to_string();
80            return Err(JsonRpcError::Rpc { code, message });
81        }
82
83        let result = response_json.get("result").cloned().unwrap_or(Value::Null);
84        Ok(serde_json::from_value(result)?)
85    }
86
87    /// Make a V2 API call (non-JSON-RPC)
88    pub async fn call_v2<T: DeserializeOwned>(
89        &self,
90        method_path: &str,
91        payload: Option<Value>,
92    ) -> Result<T, JsonRpcError> {
93        let url = self.base_url.join(method_path)?;
94
95        let request = match payload {
96            Some(body) => self.http.post(url).json(&body),
97            None => self.http.get(url),
98        };
99
100        let response = request.send().await?;
101        let response_text = response.text().await?;
102
103        Ok(serde_json::from_str(&response_text)?)
104    }
105
106    /// Make a V3 API call
107    pub async fn call_v3<T: DeserializeOwned>(
108        &self,
109        method: &str,
110        params: Value,
111    ) -> Result<T, JsonRpcError> {
112        self.call(method, params).await
113    }
114}
115
116/// Convert a JSON value to canonical JSON string with deterministic ordering
117pub fn canonical_json(value: &Value) -> String {
118    crate::canonjson::canonicalize(value)
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use serde_json::json;
125
126    #[test]
127    fn test_canonical_json() {
128        let value = json!({
129            "b": 2,
130            "a": 1,
131            "c": {
132                "z": 3,
133                "y": 4
134            }
135        });
136
137        let canonical = canonical_json(&value);
138        assert!(canonical.contains(r#""a":1"#));
139        assert!(canonical.contains(r#""b":2"#));
140
141        // Keys should be in alphabetical order
142        let a_pos = canonical.find(r#""a":"#).unwrap();
143        let b_pos = canonical.find(r#""b":"#).unwrap();
144        assert!(a_pos < b_pos);
145    }
146}