bitcoind_json_rpc_client/client_sync/
mod.rs1mod error;
6pub mod v17;
7pub mod v18;
8pub mod v19;
9pub mod v20;
10pub mod v21;
11pub mod v22;
12pub mod v23;
13pub mod v24;
14pub mod v25;
15pub mod v26;
16pub mod v27;
17pub mod v28;
18
19use std::fs::File;
20use std::io::{BufRead, BufReader};
21use std::path::PathBuf;
22
23pub use crate::client_sync::error::Error;
24
25pub type Result<T> = std::result::Result<T, Error>;
29
30#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
32pub enum Auth {
33 None,
34 UserPass(String, String),
35 CookieFile(PathBuf),
36}
37
38impl Auth {
39 pub fn get_user_pass(self) -> Result<(Option<String>, Option<String>)> {
41 match self {
42 Auth::None => Ok((None, None)),
43 Auth::UserPass(u, p) => Ok((Some(u), Some(p))),
44 Auth::CookieFile(path) => {
45 let line = BufReader::new(File::open(path)?)
46 .lines()
47 .next()
48 .ok_or(Error::InvalidCookieFile)??;
49 let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
50 Ok((Some(line[..colon].into()), Some(line[colon + 1..].into())))
51 }
52 }
53 }
54}
55
56#[macro_export]
58macro_rules! define_jsonrpc_minreq_client {
59 ($version:literal) => {
60 use std::fmt;
61
62 use $crate::client_sync::{log_response, Auth, Result};
63 use $crate::client_sync::error::Error;
64
65 pub struct Client {
67 inner: jsonrpc::client::Client,
68 }
69
70 impl fmt::Debug for Client {
71 fn fmt(&self, f: &mut fmt::Formatter) -> core::fmt::Result {
72 write!(
73 f,
74 "bitcoind-json-rpc::client_sync::{}::Client({:?})", $version, self.inner
75 )
76 }
77 }
78
79 impl Client {
80 pub fn new(url: &str) -> Self {
82 let transport = jsonrpc::http::minreq_http::Builder::new()
83 .url(url)
84 .expect("jsonrpc v0.18, this function does not error")
85 .build();
86 let inner = jsonrpc::client::Client::with_transport(transport);
87
88 Self { inner }
89 }
90
91 pub fn new_with_auth(url: &str, auth: Auth) -> Result<Self> {
93 if matches!(auth, Auth::None) {
94 return Err(Error::MissingUserPassword);
95 }
96 let (user, pass) = auth.get_user_pass()?;
97
98 let transport = jsonrpc::http::minreq_http::Builder::new()
99 .url(url)
100 .expect("jsonrpc v0.18, this function does not error")
101 .basic_auth(user.unwrap(), pass)
102 .build();
103 let inner = jsonrpc::client::Client::with_transport(transport);
104
105 Ok(Self { inner })
106 }
107
108 pub fn call<T: for<'a> serde::de::Deserialize<'a>>(
110 &self,
111 method: &str,
112 args: &[serde_json::Value],
113 ) -> Result<T> {
114 let raw = serde_json::value::to_raw_value(args)?;
115 let req = self.inner.build_request(&method, Some(&*raw));
116 if log::log_enabled!(log::Level::Debug) {
117 log::debug!(target: "bitcoind-json-rpc", "request: {} {}", method, serde_json::Value::from(args));
118 }
119
120 let resp = self.inner.send_request(req).map_err(Error::from);
121 log_response(method, &resp);
122 Ok(resp?.result()?)
123 }
124 }
125 }
126}
127
128#[macro_export]
137macro_rules! impl_client_check_expected_server_version {
138 ($expected_versions:expr) => {
139 impl Client {
140 pub fn check_expected_server_version(&self) -> Result<()> {
142 let server_version = self.server_version()?;
143 if !$expected_versions.contains(&server_version) {
144 return Err($crate::client_sync::error::UnexpectedServerVersionError {
145 got: server_version,
146 expected: $expected_versions.to_vec(),
147 })?;
148 }
149 Ok(())
150 }
151 }
152 };
153}
154
155fn into_json<T>(val: T) -> Result<serde_json::Value>
157where
158 T: serde::ser::Serialize,
159{
160 Ok(serde_json::to_value(val)?)
161}
162
163#[allow(dead_code)] fn opt_into_json<T>(opt: Option<T>) -> Result<serde_json::Value>
166where
167 T: serde::ser::Serialize,
168{
169 match opt {
170 Some(val) => Ok(into_json(val)?),
171 None => Ok(serde_json::Value::Null),
172 }
173}
174
175#[allow(dead_code)] fn null() -> serde_json::Value { serde_json::Value::Null }
178
179#[allow(dead_code)] fn empty_arr() -> serde_json::Value { serde_json::Value::Array(vec![]) }
182
183#[allow(dead_code)] fn empty_obj() -> serde_json::Value { serde_json::Value::Object(Default::default()) }
186
187#[allow(dead_code)] fn handle_defaults<'a>(
203 args: &'a mut [serde_json::Value],
204 defaults: &[serde_json::Value],
205) -> &'a [serde_json::Value] {
206 assert!(args.len() >= defaults.len());
207
208 let mut first_non_null_optional_idx = None;
211 for i in 0..defaults.len() {
212 let args_i = args.len() - 1 - i;
213 let defaults_i = defaults.len() - 1 - i;
214 if args[args_i] == serde_json::Value::Null {
215 if first_non_null_optional_idx.is_some() {
216 if defaults[defaults_i] == serde_json::Value::Null {
217 panic!("Missing `default` for argument idx {}", args_i);
218 }
219 args[args_i] = defaults[defaults_i].clone();
220 }
221 } else if first_non_null_optional_idx.is_none() {
222 first_non_null_optional_idx = Some(args_i);
223 }
224 }
225
226 let required_num = args.len() - defaults.len();
227
228 if let Some(i) = first_non_null_optional_idx {
229 &args[..i + 1]
230 } else {
231 &args[..required_num]
232 }
233}
234
235#[allow(dead_code)] fn opt_result<T: for<'a> serde::de::Deserialize<'a>>(
238 result: serde_json::Value,
239) -> Result<Option<T>> {
240 if result == serde_json::Value::Null {
241 Ok(None)
242 } else {
243 Ok(serde_json::from_value(result)?)
244 }
245}
246
247fn log_response(method: &str, resp: &Result<jsonrpc::Response>) {
249 use log::Level::{Debug, Trace, Warn};
250
251 if log::log_enabled!(Warn) || log::log_enabled!(Debug) || log::log_enabled!(Trace) {
252 match resp {
253 Err(ref e) =>
254 if log::log_enabled!(Debug) {
255 log::debug!(target: "bitcoind-json-rpc", "error: {}: {:?}", method, e);
256 },
257 Ok(ref resp) =>
258 if let Some(ref e) = resp.error {
259 if log::log_enabled!(Debug) {
260 log::debug!(target: "bitcoind-json-rpc", "response error for {}: {:?}", method, e);
261 }
262 } else if log::log_enabled!(Trace) {
263 let def =
264 serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap();
265 let result = resp.result.as_ref().unwrap_or(&def);
266 log::trace!(target: "bitcoind-json-rpc", "response for {}: {}", method, result);
267 },
268 }
269 }
270}