Skip to content

Commit 6346012

Browse files
committed
refactor(async)!: remove async-std dependency, allow custom runtime
1 parent 9fcaf01 commit 6346012

File tree

3 files changed

+53
-12
lines changed

3 files changed

+53
-12
lines changed

Cargo.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ hex = { version = "0.2", package = "hex-conservative" }
2323
log = "^0.4"
2424
minreq = { version = "2.11.0", features = ["json-using-serde"], optional = true }
2525
reqwest = { version = "0.11", features = ["json"], default-features = false, optional = true }
26-
async-std = { version = "1.13.0", optional = true }
26+
27+
# default async runtime
28+
tokio = { version = "1.38", features = ["time"], optional = true }
2729

2830
[dev-dependencies]
2931
serde_json = "1.0"
@@ -32,13 +34,15 @@ electrsd = { version = "0.28.0", features = ["legacy", "esplora_a33e97e1", "bitc
3234
lazy_static = "1.4.0"
3335

3436
[features]
35-
default = ["blocking", "async", "async-https"]
37+
default = ["blocking", "async", "async-https", "tokio"]
3638
blocking = ["minreq", "minreq/proxy"]
3739
blocking-https = ["blocking", "minreq/https"]
3840
blocking-https-rustls = ["blocking", "minreq/https-rustls"]
3941
blocking-https-native = ["blocking", "minreq/https-native"]
4042
blocking-https-bundled = ["blocking", "minreq/https-bundled"]
41-
async = ["async-std", "reqwest", "reqwest/socks"]
43+
44+
tokio = ["dep:tokio"]
45+
async = ["reqwest", "reqwest/socks", "tokio?/time"]
4246
async-https = ["async", "reqwest/default-tls"]
4347
async-https-native = ["async", "reqwest/native-tls"]
4448
async-https-rustls = ["async", "reqwest/rustls-tls"]

src/async.rs

+26-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
//! Esplora by way of `reqwest` HTTP client.
1313
14-
use async_std::task;
1514
use std::collections::HashMap;
15+
use std::marker::PhantomData;
1616
use std::str::FromStr;
1717

1818
use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable};
@@ -35,16 +35,19 @@ use crate::{
3535
};
3636

3737
#[derive(Debug, Clone)]
38-
pub struct AsyncClient {
38+
pub struct AsyncClient<S = DefaultSleeper> {
3939
/// The URL of the Esplora Server.
4040
url: String,
4141
/// The inner [`reqwest::Client`] to make HTTP requests.
4242
client: Client,
4343
/// Number of times to retry a request
4444
max_retries: usize,
45+
46+
/// Marker for the type of sleeper used
47+
marker: PhantomData<S>,
4548
}
4649

47-
impl AsyncClient {
50+
impl<S: Sleeper> AsyncClient<S> {
4851
/// Build an async client from a builder
4952
pub fn from_builder(builder: Builder) -> Result<Self, Error> {
5053
let mut client_builder = Client::builder();
@@ -75,15 +78,16 @@ impl AsyncClient {
7578
url: builder.base_url,
7679
client: client_builder.build()?,
7780
max_retries: builder.max_retries,
81+
marker: PhantomData,
7882
})
7983
}
8084

81-
/// Build an async client from the base url and [`Client`]
8285
pub fn from_client(url: String, client: Client) -> Self {
8386
AsyncClient {
8487
url,
8588
client,
8689
max_retries: crate::DEFAULT_MAX_RETRIES,
90+
marker: PhantomData,
8791
}
8892
}
8993

@@ -460,7 +464,7 @@ impl AsyncClient {
460464
loop {
461465
match self.client.get(url).send().await? {
462466
resp if attempts < self.max_retries && is_status_retryable(resp.status()) => {
463-
task::sleep(delay).await;
467+
S::sleep(delay).await;
464468
attempts += 1;
465469
delay *= 2;
466470
}
@@ -473,3 +477,20 @@ impl AsyncClient {
473477
fn is_status_retryable(status: reqwest::StatusCode) -> bool {
474478
RETRYABLE_ERROR_CODES.contains(&status.as_u16())
475479
}
480+
481+
pub trait Sleeper: 'static {
482+
type Sleep: std::future::Future<Output = ()>;
483+
fn sleep(dur: std::time::Duration) -> Self::Sleep;
484+
}
485+
486+
#[derive(Debug, Clone, Copy)]
487+
pub struct DefaultSleeper;
488+
489+
#[cfg(any(test, feature = "tokio"))]
490+
impl Sleeper for DefaultSleeper {
491+
type Sleep = tokio::time::Sleep;
492+
493+
fn sleep(dur: std::time::Duration) -> Self::Sleep {
494+
tokio::time::sleep(dur)
495+
}
496+
}

src/lib.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
//! Here is an example of how to create an asynchronous client.
2727
//!
2828
//! ```no_run
29-
//! # #[cfg(feature = "async")]
29+
//! # #[cfg(all(feature = "async", feature = "tokio"))]
3030
//! # {
3131
//! use esplora_client::Builder;
3232
//! let builder = Builder::new("https://blockstream.info/testnet/api");
@@ -71,8 +71,10 @@ use std::fmt;
7171
use std::num::TryFromIntError;
7272
use std::time::Duration;
7373

74-
pub mod api;
74+
#[cfg(feature = "async")]
75+
use r#async::Sleeper;
7576

77+
pub mod api;
7678
#[cfg(feature = "async")]
7779
pub mod r#async;
7880
#[cfg(feature = "blocking")]
@@ -178,11 +180,18 @@ impl Builder {
178180
BlockingClient::from_builder(self)
179181
}
180182

181-
// Build an asynchronous client from builder
182-
#[cfg(feature = "async")]
183+
/// Build an asynchronous client from builder
184+
#[cfg(all(feature = "async", feature = "tokio"))]
183185
pub fn build_async(self) -> Result<AsyncClient, Error> {
184186
AsyncClient::from_builder(self)
185187
}
188+
189+
/// Build an asynchronous client from builder where the returned client uses a
190+
/// user-defined [`Sleeper`].
191+
#[cfg(feature = "async")]
192+
pub fn build_async_with_sleeper<S: Sleeper>(self) -> Result<AsyncClient<S>, Error> {
193+
AsyncClient::from_builder(self)
194+
}
186195
}
187196

188197
/// Errors that can happen during a request to `Esplora` servers.
@@ -320,8 +329,15 @@ mod test {
320329
let blocking_client = builder.build_blocking();
321330

322331
let builder_async = Builder::new(&format!("http://{}", esplora_url));
332+
333+
#[cfg(feature = "tokio")]
323334
let async_client = builder_async.build_async().unwrap();
324335

336+
#[cfg(not(feature = "tokio"))]
337+
let async_client = builder_async
338+
.build_async_with_sleeper::<r#async::DefaultSleeper>()
339+
.unwrap();
340+
325341
(blocking_client, async_client)
326342
}
327343

0 commit comments

Comments
 (0)