Skip to content

Commit fd63f37

Browse files
committed
wip(feat(minireq)): add minireq/bitreq HTTP client blocking support
1 parent 0cef6a9 commit fd63f37

File tree

5 files changed

+522
-30
lines changed

5 files changed

+522
-30
lines changed

.github/workflows/cont_integration.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
- blocking
2424
- blocking-https
2525
- blocking-https-rustls
26-
- blocking-https-native
27-
- blocking-https-bundled
26+
# - blocking-https-native
27+
# - blocking-https-bundled
2828
- async
2929
- async-https
3030
- async-https-native
@@ -55,7 +55,7 @@ jobs:
5555
if: matrix.rust.version == '1.63.0'
5656
run: |
5757
cargo update -p reqwest --precise "0.12.4"
58-
cargo update -p minreq --precise "2.13.2"
58+
# cargo update -p minreq --precise "2.13.2"
5959
cargo update -p home --precise "0.5.5"
6060
cargo update -p url --precise "2.5.0"
6161
cargo update -p tokio --precise "1.38.1"

Cargo.toml

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,56 @@ path = "src/lib.rs"
1818

1919
[dependencies]
2020
serde = { version = "1.0", features = ["derive"] }
21-
bitcoin = { version = "0.32", features = ["serde", "std"], default-features = false }
21+
bitcoin = { version = "0.32", features = [
22+
"serde",
23+
"std",
24+
], default-features = false }
2225
hex = { version = "0.2", package = "hex-conservative" }
2326
log = "^0.4"
24-
minreq = { version = "2.11.0", features = ["json-using-serde"], optional = true }
25-
reqwest = { version = "0.12", features = ["json"], default-features = false, optional = true }
27+
# minreq = { version = "2.11.0", features = [
28+
# "json-using-serde",
29+
# ], optional = true }
30+
reqwest = { version = "0.12", features = [
31+
"json",
32+
], default-features = false, optional = true }
33+
minireq = { git = "https://github.com/tcharding/minireq.git", branch = "master", optional = true }
2634

2735
# default async runtime
2836
tokio = { version = "1", features = ["time"], optional = true }
2937

38+
serde_json = { version = "1.0", optional = true }
39+
3040
[dev-dependencies]
3141
serde_json = "1.0"
3242
tokio = { version = "1.20.1", features = ["full"] }
33-
electrsd = { version = "0.33.0", features = ["legacy", "esplora_a33e97e1", "corepc-node_28_0"] }
43+
electrsd = { version = "0.33.0", features = [
44+
"legacy",
45+
"esplora_a33e97e1",
46+
"corepc-node_28_0",
47+
] }
3448
lazy_static = "1.4.0"
3549

3650
[features]
37-
default = ["blocking", "async", "async-https", "tokio"]
38-
blocking = ["minreq", "minreq/proxy"]
39-
blocking-https = ["blocking", "minreq/https"]
40-
blocking-https-rustls = ["blocking", "minreq/https-rustls"]
41-
blocking-https-native = ["blocking", "minreq/https-native"]
42-
blocking-https-bundled = ["blocking", "minreq/https-bundled"]
51+
default = ["blocking", "async", "async-https", "tokio", "serde_json"]
52+
blocking = ["minireq", "minireq/proxy", "serde_json"]
53+
blocking-https = ["blocking", "minireq/https", "serde_json"]
54+
blocking-https-rustls = ["blocking", "minireq/https-rustls"]
55+
# blocking-https-native = ["blocking", "minreq/https-native"]
56+
# blocking-https-bundled = ["blocking", "minreq/https-bundled"]
4357

4458
tokio = ["dep:tokio"]
4559
async = ["reqwest", "reqwest/socks", "tokio?/time"]
4660
async-https = ["async", "reqwest/default-tls"]
4761
async-https-native = ["async", "reqwest/native-tls"]
4862
async-https-rustls = ["async", "reqwest/rustls-tls"]
4963
async-https-rustls-manual-roots = ["async", "reqwest/rustls-tls-manual-roots"]
64+
65+
# # TODO: (@oleonardolima) add new minireq feature flag
66+
# minireq = [
67+
# "dep:minireq",
68+
# "minireq/urlencoding",
69+
# "minireq/punycode",
70+
# "minireq/base64",
71+
# "minireq/proxy",
72+
# "serde_json",
73+
# ]

src/blocking.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// You may not use this file except in accordance with one or both of these
1010
// licenses.
1111

12-
//! Esplora by way of `minreq` HTTP client.
12+
//! Esplora by way of `minireq` HTTP client.
1313
1414
use std::collections::HashMap;
1515
use std::convert::TryFrom;
@@ -19,7 +19,7 @@ use std::thread;
1919
#[allow(unused_imports)]
2020
use log::{debug, error, info, trace};
2121

22-
use minreq::{Proxy, Request, Response};
22+
use minireq::{Proxy, Request, Response};
2323

2424
use bitcoin::consensus::{deserialize, serialize, Decodable};
2525
use bitcoin::hashes::{sha256, Hash};
@@ -68,7 +68,7 @@ impl BlockingClient {
6868

6969
/// Perform a raw HTTP GET request with the given URI `path`.
7070
pub fn get_request(&self, path: &str) -> Result<Request, Error> {
71-
let mut request = minreq::get(format!("{}{}", self.url, path));
71+
let mut request = minireq::get(format!("{}{}", self.url, path));
7272

7373
if let Some(proxy) = &self.proxy {
7474
let proxy = Proxy::new(proxy.as_str())?;
@@ -110,7 +110,8 @@ impl BlockingClient {
110110
Err(Error::HttpResponse { status, message })
111111
}
112112
Ok(resp) => Ok(Some(
113-
Txid::from_str(resp.as_str().map_err(Error::Minreq)?).map_err(Error::HexToArray)?,
113+
Txid::from_str(resp.as_str().map_err(Error::MiniReq)?)
114+
.map_err(Error::HexToArray)?,
114115
)),
115116
Err(e) => Err(e),
116117
}
@@ -125,7 +126,7 @@ impl BlockingClient {
125126
Err(Error::HttpResponse { status, message })
126127
}
127128
Ok(resp) => {
128-
let hex_str = resp.as_str().map_err(Error::Minreq)?;
129+
let hex_str = resp.as_str().map_err(Error::MiniReq)?;
129130
let hex_vec = Vec::from_hex(hex_str).unwrap();
130131
deserialize::<T>(&hex_vec)
131132
.map_err(Error::BitcoinEncoding)
@@ -143,7 +144,7 @@ impl BlockingClient {
143144
Err(Error::HttpResponse { status, message })
144145
}
145146
Ok(resp) => {
146-
let hex_str = resp.as_str().map_err(Error::Minreq)?;
147+
let hex_str = resp.as_str().map_err(Error::MiniReq)?;
147148
let hex_vec = Vec::from_hex(hex_str).unwrap();
148149
deserialize::<T>(&hex_vec).map_err(Error::BitcoinEncoding)
149150
}
@@ -162,7 +163,7 @@ impl BlockingClient {
162163
let message = resp.as_str().unwrap_or_default().to_string();
163164
Err(Error::HttpResponse { status, message })
164165
}
165-
Ok(resp) => Ok(resp.json::<T>().map_err(Error::Minreq)?),
166+
Ok(resp) => Ok(self.into_json(&resp)?),
166167
Err(e) => Err(e),
167168
}
168169
}
@@ -178,7 +179,7 @@ impl BlockingClient {
178179
let message = resp.as_str().unwrap_or_default().to_string();
179180
Err(Error::HttpResponse { status, message })
180181
}
181-
Ok(resp) => Ok(Some(resp.json::<T>()?)),
182+
Ok(resp) => Ok(Some(self.into_json(&resp)?)),
182183
Err(e) => Err(e),
183184
}
184185
}
@@ -195,6 +196,20 @@ impl BlockingClient {
195196
}
196197
}
197198

199+
pub fn into_json<'a, T>(&self, res: &'a Response) -> Result<T, Error>
200+
where
201+
T: serde::de::Deserialize<'a>,
202+
{
203+
let str = match res.as_str() {
204+
Ok(str) => str,
205+
Err(_) => return Err(Error::InvalidUtf8InResponse),
206+
};
207+
match serde_json::from_str(str) {
208+
Ok(json) => Ok(json),
209+
Err(err) => Err(Error::SerdeJsonError(err)),
210+
}
211+
}
212+
198213
/// Get a [`Transaction`] option given its [`Txid`]
199214
pub fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
200215
self.get_opt_response(&format!("/tx/{txid}/raw"))
@@ -268,7 +283,7 @@ impl BlockingClient {
268283

269284
/// Broadcast a [`Transaction`] to Esplora
270285
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> {
271-
let mut request = minreq::post(format!("{}/tx", self.url)).with_body(
286+
let mut request = minireq::post(format!("{}/tx", self.url)).with_body(
272287
serialize(transaction)
273288
.to_lower_hex_string()
274289
.as_bytes()
@@ -291,7 +306,7 @@ impl BlockingClient {
291306
Err(Error::HttpResponse { status, message })
292307
}
293308
Ok(_resp) => Ok(()),
294-
Err(e) => Err(Error::Minreq(e)),
309+
Err(e) => Err(Error::MiniReq(e)),
295310
}
296311
}
297312

src/lib.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@
6565
//! certificates.
6666
//!
6767
//! [`dont remove this line or cargo doc will break`]: https://example.com
68-
#![cfg_attr(not(feature = "minreq"), doc = "[`minreq`]: https://docs.rs/minreq")]
68+
// TODO: (@oleonardolima) update it to bitreq once released
69+
#![cfg_attr(not(feature = "minireq"), doc = "[`minireq`]: https://docs.rs/minireq")]
6970
#![cfg_attr(not(feature = "reqwest"), doc = "[`reqwest`]: https://docs.rs/reqwest")]
7071
#![allow(clippy::result_large_err)]
7172

7273
use std::collections::HashMap;
73-
use std::fmt;
7474
use std::num::TryFromIntError;
7575
use std::time::Duration;
76+
use std::{fmt, str};
7677

7778
#[cfg(feature = "async")]
7879
pub use r#async::Sleeper;
@@ -83,12 +84,18 @@ pub mod r#async;
8384
#[cfg(feature = "blocking")]
8485
pub mod blocking;
8586

87+
#[cfg(feature = "minireq")]
88+
pub mod minireq;
89+
8690
pub use api::*;
8791
#[cfg(feature = "blocking")]
8892
pub use blocking::BlockingClient;
8993
#[cfg(feature = "async")]
9094
pub use r#async::AsyncClient;
9195

96+
// #[cfg(feature = "minireq")]
97+
// use crate::minireq::;
98+
9299
/// Response status codes for which the request may be retried.
93100
pub const RETRYABLE_ERROR_CODES: [u16; 3] = [
94101
429, // TOO_MANY_REQUESTS
@@ -195,14 +202,22 @@ impl Builder {
195202
pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
196203
AsyncClient::from_builder(self)
197204
}
205+
206+
// #[cfg(feature = "minireq")]
207+
// pub fn build_blocking_minireq(self) -> {
208+
// ::from_builder(self)
209+
// }
198210
}
199211

200212
/// Errors that can happen during a request to `Esplora` servers.
201213
#[derive(Debug)]
202214
pub enum Error {
203-
/// Error during `minreq` HTTP request
215+
// /// Error during `minreq` HTTP request
216+
// #[cfg(feature = "blocking")]
217+
// Minreq(::minreq::Error),
218+
/// Error during `minireq` HTTP request
204219
#[cfg(feature = "blocking")]
205-
Minreq(::minreq::Error),
220+
MiniReq(::minireq::Error),
206221
/// Error during reqwest HTTP request
207222
#[cfg(feature = "async")]
208223
Reqwest(::reqwest::Error),
@@ -230,6 +245,14 @@ pub enum Error {
230245
InvalidHttpHeaderValue(String),
231246
/// The server sent an invalid response
232247
InvalidResponse,
248+
249+
// TODO: (@oleonardolima) improve these error variants
250+
/// Ran into a Serde error.
251+
#[cfg(feature = "serde_json")]
252+
SerdeJsonError(serde_json::Error),
253+
/// The response contained invalid UTF-8 where it should be valid
254+
/// (eg. headers), so the response cannot interpreted correctly.
255+
InvalidUtf8InResponse,
233256
}
234257

235258
impl fmt::Display for Error {
@@ -253,7 +276,7 @@ macro_rules! impl_error {
253276

254277
impl std::error::Error for Error {}
255278
#[cfg(feature = "blocking")]
256-
impl_error!(::minreq::Error, Minreq, Error);
279+
impl_error!(::minireq::Error, MiniReq, Error);
257280
#[cfg(feature = "async")]
258281
impl_error!(::reqwest::Error, Reqwest, Error);
259282
impl_error!(std::num::ParseIntError, Parsing, Error);
@@ -325,7 +348,7 @@ mod test {
325348

326349
let mut builder = Builder::new(&format!("http://{esplora_url}"));
327350
if !headers.is_empty() {
328-
builder.headers = headers;
351+
builder.headers = headers.clone();
329352
}
330353

331354
let blocking_client = builder.build_blocking();
@@ -951,7 +974,7 @@ mod test {
951974
assert_eq!(blocks_genesis, blocks_genesis_async);
952975
}
953976

954-
#[cfg(all(feature = "blocking", feature = "async"))]
977+
#[cfg(all(feature = "blocking", feature = "async", feature = "minireq"))]
955978
#[tokio::test]
956979
async fn test_get_tx_with_http_header() {
957980
let headers = [(

0 commit comments

Comments
 (0)