diff --git a/src/async.rs b/src/async.rs index 44b3542..77f2c5f 100644 --- a/src/async.rs +++ b/src/async.rs @@ -12,6 +12,7 @@ //! Esplora by way of `reqwest` HTTP client. use std::collections::HashMap; +use std::marker::PhantomData; use std::str::FromStr; use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable}; @@ -26,24 +27,30 @@ use log::{debug, error, info, trace}; use reqwest::{header, Client, Response}; +use crate::runtime::{DefaultRuntime, Runtime}; use crate::{ BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES, }; #[derive(Debug, Clone)] -pub struct AsyncClient { +pub struct AsyncClient { /// The URL of the Esplora Server. url: String, /// The inner [`reqwest::Client`] to make HTTP requests. client: Client, /// Number of times to retry a request max_retries: usize, + + runtime: PhantomData, } -impl AsyncClient { +impl AsyncClient +where + R: Runtime, +{ /// Build an async client from a builder - pub fn from_builder(builder: Builder) -> Result { + pub fn from_builder(builder: Builder) -> Result { let mut client_builder = Client::builder(); #[cfg(not(target_arch = "wasm32"))] @@ -72,15 +79,16 @@ impl AsyncClient { url: builder.base_url, client: client_builder.build()?, max_retries: builder.max_retries, + runtime: PhantomData, }) } - /// Build an async client from the base url and [`Client`] pub fn from_client(url: String, client: Client) -> Self { AsyncClient { url, client, max_retries: crate::DEFAULT_MAX_RETRIES, + runtime: PhantomData, } } @@ -433,7 +441,7 @@ impl AsyncClient { loop { match self.client.get(url).send().await? { resp if attempts < self.max_retries && is_status_retryable(resp.status()) => { - crate::runtime::sleep(delay).await; + R::sleep(delay).await; attempts += 1; delay *= 2; } diff --git a/src/lib.rs b/src/lib.rs index f1f988e..b2901e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ pub use api::*; pub use blocking::BlockingClient; #[cfg(feature = "async")] pub use r#async::AsyncClient; +use runtime::{DefaultRuntime, Runtime}; /// Response status codes for which the request may be retried. const RETRYABLE_ERROR_CODES: [u16; 3] = [ @@ -113,7 +114,7 @@ pub fn convert_fee_rate(target: usize, estimates: HashMap) -> Option { /// The URL of the Esplora server. pub base_url: String, /// Optional URL of the proxy to use to make requests to the Esplora server @@ -135,6 +136,8 @@ pub struct Builder { pub headers: HashMap, /// Max retries pub max_retries: usize, + /// Async runtime, trait must implement `sleep` function, default is `tokio` + pub runtime: R, } impl Builder { @@ -146,6 +149,31 @@ impl Builder { timeout: None, headers: HashMap::new(), max_retries: DEFAULT_MAX_RETRIES, + runtime: DefaultRuntime, + } + } + + /// Build a blocking client from builder + #[cfg(feature = "blocking")] + pub fn build_blocking(self) -> BlockingClient { + BlockingClient::from_builder(self) + } +} + +impl Builder +where + R: Runtime, +{ + /// New with runtime + #[cfg(feature = "async")] + pub fn new_with_runtime(base_url: &str, runtime: R) -> Self { + Builder { + base_url: base_url.to_string(), + proxy: None, + timeout: None, + headers: HashMap::new(), + max_retries: DEFAULT_MAX_RETRIES, + runtime, } } @@ -174,15 +202,9 @@ impl Builder { self } - /// Build a blocking client from builder - #[cfg(feature = "blocking")] - pub fn build_blocking(self) -> BlockingClient { - BlockingClient::from_builder(self) - } - // Build an asynchronous client from builder #[cfg(feature = "async")] - pub fn build_async(self) -> Result { + pub fn build_async(self) -> Result, Error> { AsyncClient::from_builder(self) } } diff --git a/src/runtime.rs b/src/runtime.rs index 64f4e40..4f21eb3 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,8 +1,25 @@ #[cfg(feature = "tokio")] -pub use tokio::time::sleep; +use std::time::Duration; + +pub trait Runtime { + fn sleep(duration: Duration) -> impl std::future::Future + Send; +} + +pub struct DefaultRuntime; + +#[cfg(feature = "tokio")] +impl Runtime for DefaultRuntime { + async fn sleep(duration: Duration) { + tokio::time::sleep(duration).await; + } +} #[cfg(feature = "async-std")] -pub use async_std::task::sleep; +pub struct AsyncStd; -#[cfg(not(any(feature = "tokio", feature = "async-std")))] -compile_error!("Either 'tokio' or 'async-std' feature must be enabled"); +#[cfg(feature = "async-std")] +impl Runtime for AsyncStd { + async fn sleep(duration: Duration) { + async_std::task::sleep(duration).await; + } +}