qorechain/query/
jsonrpc.rs1use 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#[derive(Debug, Clone)]
11pub struct JsonRpcClient {
12 url: String,
13 http: reqwest::Client,
14 next_id: Arc<AtomicI64>,
15}
16
17impl JsonRpcClient {
18 pub fn new(url: impl Into<String>) -> Self {
21 Self::with_client(url, reqwest::Client::new())
22 }
23
24 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 pub fn url(&self) -> &str {
36 &self.url
37 }
38
39 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}