1- //! An extensible blocking/ async Esplora client
1+ //! An extensible blocking and async Esplora client.
22//!
3- //! This library provides an extensible blocking and
4- //! async Esplora client to query Esplora's backend.
3+ //! This library provides a blocking client built on [`minreq`] and an async
4+ //! client built on [`reqwest`] for interacting with an
5+ //! [Esplora](https://github.com/Blockstream/esplora) server.
56//!
6- //! The library provides the possibility to build a blocking
7- //! client using [`minreq`] and an async client using [`reqwest`].
8- //! The library supports communicating to Esplora via a proxy
9- //! and also using TLS (SSL) for secure communication.
7+ //! Both clients support communicating via a proxy and TLS (SSL).
108//!
9+ //! # Blocking Client
1110//!
12- //! ## Usage
13- //!
14- //! You can create a blocking client as follows:
15- //!
16- //! ```no_run
17- //! # #[cfg(feature = "blocking")]
18- //! # {
11+ //! ```rust,ignore
1912//! use esplora_client::Builder;
20- //! let builder = Builder::new("https://blockstream.info/testnet/api");
21- //! let blocking_client = builder.build_blocking();
22- //! # Ok::<(), esplora_client::Error>(());
23- //! # }
13+ //! let client = Builder::new("https://mempool.space/api").build_blocking();
14+ //! let height = client.get_height().unwrap();
2415//! ```
2516//!
26- //! Here is an example of how to create an asynchronous client.
17+ //! # Async Client
2718//!
28- //! ```no_run
29- //! # #[cfg(all(feature = "async", feature = "tokio"))]
30- //! # {
19+ //! ```rust,ignore
3120//! use esplora_client::Builder;
32- //! let builder = Builder::new("https://blockstream.info/testnet/api");
33- //! let async_client = builder.build_async();
34- //! # Ok::<(), esplora_client::Error>(());
35- //! # }
21+ //! async fn example() {
22+ //! let client = Builder::new("https://mempool.space/api")
23+ //! .build_async()
24+ //! .unwrap();
25+ //! let height = client.get_height().await.unwrap();
26+ //! }
3627//! ```
3728//!
38- //! ## Features
29+ //! # Features
30+ //!
31+ //! By default, all features are enabled. To use a specific feature
32+ //! combination, set `default-features = false` and explicitly enable
33+ //! the desired features in your `Cargo.toml` manifest:
34+ //!
35+ //! `esplora-client = { version = "*", default-features = false, features = ["blocking"] }`
3936//!
40- //! By default the library enables all features. To specify
41- //! specific features, set `default-features` to `false` in your `Cargo.toml`
42- //! and specify the features you want. This will look like this:
37+ //! ### Blocking
4338//!
44- //! `esplora-client = { version = "*", default-features = false, features =
45- //! ["blocking"] }`
39+ //! | Feature | Description |
40+ //! |---------|-------------|
41+ //! | `blocking` | Enables the blocking client with proxy support. |
42+ //! | `blocking-https` | Enables the blocking client with proxy and TLS using the default [`minreq`](https://docs.rs/minreq) backend. |
43+ //! | `blocking-https-rustls` | Enables the blocking client with proxy and TLS using [`rustls`](https://docs.rs/rustls). |
44+ //! | `blocking-https-native` | Enables the blocking client with proxy and TLS using the platform's native TLS backend. |
45+ //! | `blocking-https-bundled` | Enables the blocking client with proxy and TLS using a bundled OpenSSL backend. |
4646//!
47- //! * `blocking` enables [`minreq`], the blocking client with proxy.
48- //! * `blocking-https` enables [`minreq`], the blocking client with proxy and TLS (SSL) capabilities
49- //! using the default [`minreq`] backend.
50- //! * `blocking-https-rustls` enables [`minreq`], the blocking client with proxy and TLS (SSL)
51- //! capabilities using the `rustls` backend.
52- //! * `blocking-https-native` enables [`minreq`], the blocking client with proxy and TLS (SSL)
53- //! capabilities using the platform's native TLS backend (likely OpenSSL).
54- //! * `blocking-https-bundled` enables [`minreq`], the blocking client with proxy and TLS (SSL)
55- //! capabilities using a bundled OpenSSL library backend.
56- //! * `async` enables [`reqwest`], the async client with proxy capabilities.
57- //! * `async-https` enables [`reqwest`], the async client with support for proxying and TLS (SSL)
58- //! using the default [`reqwest`] TLS backend.
59- //! * `async-https-native` enables [`reqwest`], the async client with support for proxying and TLS
60- //! (SSL) using the platform's native TLS backend (likely OpenSSL).
61- //! * `async-https-rustls` enables [`reqwest`], the async client with support for proxying and TLS
62- //! (SSL) using the `rustls` TLS backend.
63- //! * `async-https-rustls-manual-roots` enables [`reqwest`], the async client with support for
64- //! proxying and TLS (SSL) using the `rustls` TLS backend without using the default root
65- //! certificates.
47+ //! ### Async
6648//!
67- //! [`dont remove this line or cargo doc will break`]: https://example.com
49+ //! | Feature | Description |
50+ //! |---------|-------------|
51+ //! | `async` | Enables the async client with proxy support. |
52+ //! | `tokio` | Enables the Tokio runtime for the async client. |
53+ //! | `async-https` | Enables the async client with proxy and TLS using the default [`reqwest`](https://docs.rs/reqwest) backend. |
54+ //! | `async-https-native` | Enables the async client with proxy and TLS using the platform's native TLS backend. |
55+ //! | `async-https-rustls` | Enables the async client with proxy and TLS using [`rustls`](https://docs.rs/rustls). |
56+ //! | `async-https-rustls-manual-roots` | Enables the async client with proxy and TLS using `rustls` without default root certificates. |
57+ //!
58+ //! [`dont remove the 2 lines below or `cargo doc` will break`]: https://example.com
6859#![ cfg_attr( not( feature = "minreq" ) , doc = "[`minreq`]: https://docs.rs/minreq" ) ]
6960#![ cfg_attr( not( feature = "reqwest" ) , doc = "[`reqwest`]: https://docs.rs/reqwest" ) ]
7061#![ allow( clippy:: result_large_err) ]
7162#![ warn( missing_docs) ]
7263
64+ use core:: fmt;
65+ use core:: fmt:: Display ;
66+ use core:: fmt:: Formatter ;
67+ use core:: time:: Duration ;
7368use std:: collections:: HashMap ;
74- use std:: fmt;
75- use std:: num:: TryFromIntError ;
76- use std:: time:: Duration ;
7769
7870#[ cfg( feature = "async" ) ]
7971pub use r#async:: Sleeper ;
@@ -98,10 +90,12 @@ pub const RETRYABLE_ERROR_CODES: [u16; 3] = [
9890] ;
9991
10092/// Base backoff in milliseconds.
101- const BASE_BACKOFF_MILLIS : Duration = Duration :: from_millis ( 256 ) ;
93+ #[ doc( hidden) ]
94+ pub const BASE_BACKOFF_MILLIS : Duration = Duration :: from_millis ( 256 ) ;
10295
10396/// Default max retries.
104- const DEFAULT_MAX_RETRIES : usize = 6 ;
97+ #[ doc( hidden) ]
98+ pub const DEFAULT_MAX_RETRIES : usize = 6 ;
10599
106100/// Returns the [`FeeRate`] for the given confirmation target in blocks.
107101///
@@ -139,33 +133,36 @@ pub fn convert_fee_rate(target_blocks: usize, estimates: HashMap<u16, FeeRate>)
139133}
140134
141135/// A builder for an [`AsyncClient`] or [`BlockingClient`].
136+ ///
137+ /// Use [`Builder::new`] to create a new builder, configure it with the
138+ /// chainable methods, then call [`Builder::build_blocking`] or
139+ /// [`Builder::build_async`] to construct the client.
142140#[ derive( Debug , Clone ) ]
143141pub struct Builder {
144142 /// The URL of the Esplora server.
145143 pub base_url : String ,
146- /// Optional URL of the proxy to use to make requests to the Esplora server
144+ /// Optional URL of the proxy to use to make requests to the Esplora server.
147145 ///
148146 /// The string should be formatted as:
149147 /// `<protocol>://<user>:<password>@host:<port>`.
150148 ///
151149 /// Note that the format of this value and the supported protocols change
152- /// slightly between the blocking version of the client (using `minreq`)
153- /// and the async version (using `reqwest`). For more details check with
154- /// the documentation of the two crates. Both of them are compiled with
155- /// the `socks` feature enabled.
150+ /// slightly between the blocking client (using [`minreq`]) and the async
151+ /// client (using [`reqwest`]). Both are compiled with the `socks` feature
152+ /// enabled.
156153 ///
157154 /// The proxy is ignored when targeting `wasm32`.
158155 pub proxy : Option < String > ,
159- /// Socket timeout.
156+ /// The socket's timeout, in seconds .
160157 pub timeout : Option < u64 > ,
161- /// HTTP headers to set on every request made to Esplora server.
158+ /// HTTP headers to set on every request made to the Esplora server.
162159 pub headers : HashMap < String , String > ,
163- /// Max retries
160+ /// Maximum number of times to retry a request.
164161 pub max_retries : usize ,
165162}
166163
167164impl Builder {
168- /// Instantiate a new builder
165+ /// Create a new [`Builder`] with the given Esplora server URL.
169166 pub fn new ( base_url : & str ) -> Self {
170167 Builder {
171168 base_url : base_url. to_string ( ) ,
@@ -176,95 +173,110 @@ impl Builder {
176173 }
177174 }
178175
179- /// Set the proxy of the builder
176+ /// Set the proxy URL.
177+ ///
178+ /// See [`Builder::proxy`] for the expected format.
180179 pub fn proxy ( mut self , proxy : & str ) -> Self {
181180 self . proxy = Some ( proxy. to_string ( ) ) ;
182181 self
183182 }
184183
185- /// Set the timeout of the builder
184+ /// Set the socket's timeout, in seconds.
186185 pub fn timeout ( mut self , timeout : u64 ) -> Self {
187186 self . timeout = Some ( timeout) ;
188187 self
189188 }
190189
191- /// Add a header to set on each request
190+ /// Add an HTTP header to set on every request.
192191 pub fn header ( mut self , key : & str , value : & str ) -> Self {
193192 self . headers . insert ( key. to_string ( ) , value. to_string ( ) ) ;
194193 self
195194 }
196195
197- /// Set the maximum number of times to retry a request if the response status
198- /// is one of [`RETRYABLE_ERROR_CODES`].
196+ /// Set the maximum number of times to retry a request.
197+ ///
198+ /// Retries are only attempted for responses
199+ /// with status codes defined in [`RETRYABLE_ERROR_CODES`].
199200 pub fn max_retries ( mut self , count : usize ) -> Self {
200201 self . max_retries = count;
201202 self
202203 }
203204
204- /// Build a blocking client from builder
205+ /// Build a [`BlockingClient`] from this [`Builder`].
205206 #[ cfg( feature = "blocking" ) ]
206207 pub fn build_blocking ( self ) -> BlockingClient {
207208 BlockingClient :: from_builder ( self )
208209 }
209210
210- /// Build an asynchronous client from builder
211+ /// Build an [`AsyncClient`] from this [`Builder`].
212+ ///
213+ /// # Errors
214+ ///
215+ /// Returns an [`Error`] if the underlying [`reqwest::Client`] fails to build.
211216 #[ cfg( all( feature = "async" , feature = "tokio" ) ) ]
212217 pub fn build_async ( self ) -> Result < AsyncClient , Error > {
213218 AsyncClient :: from_builder ( self )
214219 }
215220
216- /// Build an asynchronous client from builder where the returned client uses a
217- /// user-defined [`Sleeper`].
221+ /// Build an [`AsyncClient`] from this [`Builder`] with a custom [`Sleeper`].
222+ ///
223+ /// Use this instead of [`Builder::build_async`] when you want to use a
224+ /// runtime other than Tokio for sleeping between retries.
225+ ///
226+ /// # Errors
227+ ///
228+ /// Returns an [`Error`] if the underlying [`reqwest::Client`] fails to build.
218229 #[ cfg( feature = "async" ) ]
219230 pub fn build_async_with_sleeper < S : Sleeper > ( self ) -> Result < AsyncClient < S > , Error > {
220231 AsyncClient :: from_builder ( self )
221232 }
222233}
223234
224- /// Errors that can happen during a request to ` Esplora` servers .
235+ /// Errors that can occur during a request to an Esplora server .
225236#[ derive( Debug ) ]
226237pub enum Error {
227- /// Error during `minreq` HTTP request
238+ /// A [ `minreq`] error occurred during a blocking HTTP request.
228239 #[ cfg( feature = "blocking" ) ]
229240 Minreq ( minreq:: Error ) ,
230- /// Error during `reqwest` HTTP request
241+ /// A [ `reqwest`] error occurred during an async HTTP request.
231242 #[ cfg( feature = "async" ) ]
232243 Reqwest ( reqwest:: Error ) ,
233- /// Error during JSON (de) serialization
244+ /// An error occurred during JSON serialization or deserialization.
234245 SerdeJson ( serde_json:: Error ) ,
235- /// HTTP response error
246+ /// The server returned a non-success HTTP status code.
236247 HttpResponse {
237248 /// The HTTP status code returned by the server.
238249 status : u16 ,
239- /// The error message content .
250+ /// The error message returned by the server .
240251 message : String ,
241252 } ,
242- /// Invalid number returned
243- Parsing ( std :: num:: ParseIntError ) ,
244- /// Invalid status code, unable to convert to `u16`
245- StatusCode ( TryFromIntError ) ,
246- /// Invalid Bitcoin data returned
253+ /// Failed to parse an integer from the server response.
254+ Parsing ( core :: num:: ParseIntError ) ,
255+ /// Failed to convert an HTTP status code to `u16`.
256+ StatusCode ( core :: num :: TryFromIntError ) ,
257+ /// Failed to decode a Bitcoin consensus-encoded value.
247258 BitcoinEncoding ( bitcoin:: consensus:: encode:: Error ) ,
248- /// Invalid hex data returned (attempting to create an array)
259+ /// Failed to decode a hex string into a fixed-size array.
249260 HexToArray ( bitcoin:: hex:: HexToArrayError ) ,
250- /// Invalid hex data returned (attempting to create a vector)
261+ /// Failed to decode a hex string into a vector of bytes.
251262 HexToBytes ( bitcoin:: hex:: HexToBytesError ) ,
252- /// Transaction not found
263+ /// The requested [` Transaction`] was not found.
253264 TransactionNotFound ( Txid ) ,
254- /// Block Header height not found
265+ /// No [`block header`](bitcoin::blockdata::block:: Header) was found at the given height.
255266 HeaderHeightNotFound ( u32 ) ,
256- /// Block Header hash not found
267+ /// No [`block header`](bitcoin::blockdata::block::Header) was found with the given
268+ /// [`BlockHash`].
257269 HeaderHashNotFound ( BlockHash ) ,
258- /// Invalid HTTP Header name specified
270+ /// The specified HTTP header name is invalid.
259271 InvalidHttpHeaderName ( String ) ,
260- /// Invalid HTTP Header value specified
272+ /// The specified HTTP header value is invalid.
261273 InvalidHttpHeaderValue ( String ) ,
262- /// The server sent an invalid response
274+ /// The server returned an invalid or unexpected response.
263275 InvalidResponse ,
264276}
265277
266- impl fmt :: Display for Error {
267- fn fmt ( & self , f : & mut fmt :: Formatter < ' _ > ) -> fmt:: Result {
278+ impl Display for Error {
279+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
268280 write ! ( f, "{self:?}" )
269281 }
270282}
@@ -274,7 +286,7 @@ macro_rules! impl_error {
274286 impl_error!( $from, $to, Error ) ;
275287 } ;
276288 ( $from: ty, $to: ident, $impl_for: ty ) => {
277- impl std :: convert:: From <$from> for $impl_for {
289+ impl core :: convert:: From <$from> for $impl_for {
278290 fn from( err: $from) -> Self {
279291 <$impl_for>:: $to( err)
280292 }
@@ -288,7 +300,7 @@ impl_error!(::minreq::Error, Minreq, Error);
288300#[ cfg( feature = "async" ) ]
289301impl_error ! ( :: reqwest:: Error , Reqwest , Error ) ;
290302impl_error ! ( serde_json:: Error , SerdeJson , Error ) ;
291- impl_error ! ( std :: num:: ParseIntError , Parsing , Error ) ;
303+ impl_error ! ( core :: num:: ParseIntError , Parsing , Error ) ;
292304impl_error ! ( bitcoin:: consensus:: encode:: Error , BitcoinEncoding , Error ) ;
293305impl_error ! ( bitcoin:: hex:: HexToArrayError , HexToArray , Error ) ;
294306impl_error ! ( bitcoin:: hex:: HexToBytesError , HexToBytes , Error ) ;
0 commit comments