Skip to main content

qorechain/query/
jsonrpc.rs

1//! Minimal JSON-RPC 2.0 client over HTTP POST.
2
3use crate::error::{Error, Result};
4use crate::query::DEFAULT_USER_AGENT;
5use serde_json::{json, Value};
6use std::sync::atomic::{AtomicI64, Ordering};
7use std::sync::Arc;
8
9/// A JSON-RPC 2.0 client over HTTP POST. Request ids auto-increment per client.
10#[derive(Debug, Clone)]
11pub struct JsonRpcClient {
12    url: String,
13    http: reqwest::Client,
14    next_id: Arc<AtomicI64>,
15}
16
17impl JsonRpcClient {
18    /// Creates a JSON-RPC client targeting the given URL using a fresh HTTP
19    /// client.
20    pub fn new(url: impl Into<String>) -> Self {
21        Self::with_client(url, reqwest::Client::new())
22    }
23
24    /// Creates a JSON-RPC client targeting the given URL using the supplied HTTP
25    /// client.
26    pub fn with_client(url: impl Into<String>, http: reqwest::Client) -> Self {
27        Self {
28            url: url.into(),
29            http,
30            next_id: Arc::new(AtomicI64::new(1)),
31        }
32    }
33
34    /// The configured endpoint URL.
35    pub fn url(&self) -> &str {
36        &self.url
37    }
38
39    /// Invokes a JSON-RPC method and returns its `result`.
40    ///
41    /// Returns [`Error::JsonRpc`] when the response carries an error member, or
42    /// [`Error::Http`] on a non-2xx transport response.
43    pub async fn call(&self, method: &str, params: Value) -> Result<Value> {
44        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
45        let params = match params {
46            Value::Null => json!([]),
47            other => other,
48        };
49        let body = json!({
50            "jsonrpc": "2.0",
51            "id": id,
52            "method": method,
53            "params": params,
54        });
55
56        let resp = self
57            .http
58            .post(&self.url)
59            .header("Content-Type", "application/json")
60            .header("Accept", "application/json")
61            .header("User-Agent", DEFAULT_USER_AGENT)
62            .json(&body)
63            .send()
64            .await?;
65
66        let status = resp.status();
67        let text = resp.text().await?;
68        if !status.is_success() {
69            return Err(Error::Http {
70                status: status.as_u16(),
71                url: self.url.clone(),
72                body: text,
73            });
74        }
75
76        let parsed: Value =
77            serde_json::from_str(&text).map_err(|e| Error::InvalidResponse(e.to_string()))?;
78        if let Some(err) = parsed.get("error").filter(|e| !e.is_null()) {
79            let code = err.get("code").and_then(Value::as_i64).unwrap_or(0);
80            let message = err
81                .get("message")
82                .and_then(Value::as_str)
83                .unwrap_or("unknown JSON-RPC error")
84                .to_string();
85            return Err(Error::JsonRpc { code, message });
86        }
87        Ok(parsed.get("result").cloned().unwrap_or(Value::Null))
88    }
89}