From 31dfa4b9f23f9409a2e150712b4bf78855e86ce1 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Wed, 21 Aug 2024 17:00:53 -0300 Subject: [PATCH 1/4] chore(docs): minor improvements on docstrings - apply some standard on `Cargo.toml` deps. - minor docstring improvements, and fix missing docstrings. --- Cargo.toml | 4 ++-- src/api.rs | 4 ++-- src/async.rs | 6 ++++-- src/blocking.rs | 3 ++- src/lib.rs | 17 ++++++++--------- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d90703..8963fdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ path = "src/lib.rs" [dependencies] serde = { version = "1.0", features = ["derive"] } bitcoin = { version = "0.32", features = ["serde", "std"], default-features = false } -hex = { package = "hex-conservative", version = "0.2" } +hex = { version = "0.2", package = "hex-conservative" } log = "^0.4" minreq = { version = "2.11.0", features = ["json-using-serde"], optional = true } -reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] } +reqwest = { version = "0.11", features = ["json"], default-features = false, optional = true } [dev-dependencies] serde_json = "1.0" diff --git a/src/api.rs b/src/api.rs index d4dfa1e..1d30bb6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,6 @@ -//! structs from the esplora API +//! Structs from the Esplora API //! -//! see: +//! See: pub use bitcoin::consensus::{deserialize, serialize}; pub use bitcoin::hex::FromHex; diff --git a/src/async.rs b/src/async.rs index 62b1689..031d7e0 100644 --- a/src/async.rs +++ b/src/async.rs @@ -30,12 +30,14 @@ use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus #[derive(Debug, Clone)] pub struct AsyncClient { + /// The URL of the Esplora Server. url: String, + /// The inner [`reqwest::Client`] to make HTTP requests. client: Client, } impl AsyncClient { - /// build an async client from a builder + /// Build an async client from a builder pub fn from_builder(builder: Builder) -> Result { let mut client_builder = Client::builder(); @@ -64,7 +66,7 @@ impl AsyncClient { Ok(Self::from_client(builder.base_url, client_builder.build()?)) } - /// build an async client from the base url and [`Client`] + /// Build an async client from the base url and [`Client`] pub fn from_client(url: String, client: Client) -> Self { AsyncClient { url, client } } diff --git a/src/blocking.rs b/src/blocking.rs index 22c95fd..be1cf1e 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -31,6 +31,7 @@ use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus #[derive(Debug, Clone)] pub struct BlockingClient { + /// The URL of the Esplora server. url: String, /// The proxy is ignored when targeting `wasm32`. pub proxy: Option, @@ -41,7 +42,7 @@ pub struct BlockingClient { } impl BlockingClient { - /// build a blocking client from a [`Builder`] + /// Build a blocking client from a [`Builder`] pub fn from_builder(builder: Builder) -> Self { Self { url: builder.base_url, diff --git a/src/lib.rs b/src/lib.rs index 7b7efc3..20722be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,8 +70,6 @@ use std::collections::HashMap; use std::fmt; use std::num::TryFromIntError; -use bitcoin::consensus; - pub mod api; #[cfg(feature = "async")] @@ -100,6 +98,7 @@ pub fn convert_fee_rate(target: usize, estimates: HashMap) -> Option, /// Socket timeout. pub timeout: Option, - /// HTTP headers to set on every request made to Esplora server + /// HTTP headers to set on every request made to Esplora server. pub headers: HashMap, } @@ -149,20 +148,20 @@ impl Builder { self } - /// build a blocking client from builder + /// 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 + // Build an asynchronous client from builder #[cfg(feature = "async")] pub fn build_async(self) -> Result { AsyncClient::from_builder(self) } } -/// Errors that can happen during a sync with `Esplora` +/// Errors that can happen during a request to `Esplora` servers. #[derive(Debug)] pub enum Error { /// Error during `minreq` HTTP request @@ -185,9 +184,9 @@ pub enum Error { HexToBytes(bitcoin::hex::HexToBytesError), /// Transaction not found TransactionNotFound(Txid), - /// Header height not found + /// Block Header height not found HeaderHeightNotFound(u32), - /// Header hash not found + /// Block Header hash not found HeaderHashNotFound(BlockHash), /// Invalid HTTP Header name specified InvalidHttpHeaderName(String), @@ -220,7 +219,7 @@ impl_error!(::minreq::Error, Minreq, Error); #[cfg(feature = "async")] impl_error!(::reqwest::Error, Reqwest, Error); impl_error!(std::num::ParseIntError, Parsing, Error); -impl_error!(consensus::encode::Error, BitcoinEncoding, Error); +impl_error!(bitcoin::consensus::encode::Error, BitcoinEncoding, Error); impl_error!(bitcoin::hex::HexToArrayError, HexToArray, Error); impl_error!(bitcoin::hex::HexToBytesError, HexToBytes, Error); From 565d79e4a6b6d6c0388256d71507403d42a4b6f3 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Wed, 21 Aug 2024 18:45:01 -0300 Subject: [PATCH 2/4] refactor(async): add common GET and POST methods - remove duplicated HTTP client code for handling GET and POST requests. - adds a few new methods to `AsyncClient` implementation, the new methods are responsible to handle common HTTP requests and parsing. It was previously duplicated throughout the Esplora API implementation, but now it follows the same approach already implemented for blocking client (`BlockingClient`). --- src/async.rs | 508 ++++++++++++++++++++++++--------------------------- 1 file changed, 236 insertions(+), 272 deletions(-) diff --git a/src/async.rs b/src/async.rs index 031d7e0..16d72d5 100644 --- a/src/async.rs +++ b/src/async.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; use std::str::FromStr; -use bitcoin::consensus::{deserialize, serialize}; +use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable}; use bitcoin::hashes::{sha256, Hash}; use bitcoin::hex::{DisplayHex, FromHex}; use bitcoin::{ @@ -24,7 +24,7 @@ use bitcoin::{ #[allow(unused_imports)] use log::{debug, error, info, trace}; -use reqwest::{header, Client, StatusCode}; +use reqwest::{header, Client}; use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus}; @@ -71,28 +71,206 @@ impl AsyncClient { AsyncClient { url, client } } - /// Get a [`Transaction`] option given its [`Txid`] - pub async fn get_tx(&self, txid: &Txid) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/tx/{}/raw", self.url, txid)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); + /// Make an HTTP GET request to given URL, deserializing to any `T` that + /// implement [`bitcoin::consensus::Decodable`]. + /// + /// It should be used when requesting Esplora endpoints that can be directly + /// deserialized to native `rust-bitcoin` types, which implements + /// [`bitcoin::consensus::Decodable`] from `&[u8]`. + /// + /// # Errors + /// + /// This function will return an error either from the HTTP client, or the + /// [`bitcoin::consensus::Decodable`] deserialization. + async fn get_response(&self, path: &str) -> Result { + let url = format!("{}{}", self.url, path); + let response = self.client.get(url).send().await?; + + if !response.status().is_success() { + return Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }); + } + + Ok(deserialize::(&response.bytes().await?)?) + } + + /// Make an HTTP GET request to given URL, deserializing to `Option`. + /// + /// It uses [`AsyncEsploraClient::get_response`] internally. + /// + /// See [`AsyncEsploraClient::get_response`] above for full documentation. + async fn get_opt_response(&self, path: &str) -> Result, Error> { + match self.get_response::(path).await { + Ok(res) => Ok(Some(res)), + Err(Error::HttpResponse { status, message }) => match status { + 404 => Ok(None), + _ => Err(Error::HttpResponse { status, message }), + }, + Err(e) => Err(e), + } + } + + /// Make an HTTP GET request to given URL, deserializing to any `T` that + /// implements [`serde::de::DeserializeOwned`]. + /// + /// It should be used when requesting Esplora endpoints that have a specific + /// defined API, mostly defined in [`crate::api`]. + /// + /// # Errors + /// + /// This function will return an error either from the HTTP client, or the + /// [`serde::de::DeserializeOwned`] deserialization. + async fn get_response_json( + &self, + path: &str, + ) -> Result { + let url = format!("{}{}", self.url, path); + let response = self.client.get(url).send().await?; + + match response.status().is_success() { + true => Ok(response.json::().await.map_err(Error::Reqwest)?), + false => Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }), + } + } + + /// Make an HTTP GET request to given URL, deserializing to `Option`. + /// + /// It uses [`AsyncEsploraClient::get_response_json`] internally. + /// + /// See [`AsyncEsploraClient::get_response_json`] above for full + /// documentation. + async fn get_opt_response_json( + &self, + url: &str, + ) -> Result, Error> { + match self.get_response_json(url).await { + Ok(res) => Ok(Some(res)), + Err(Error::HttpResponse { status, message }) => match status { + 404 => Ok(None), + _ => Err(Error::HttpResponse { status, message }), + }, + Err(e) => Err(e), + } + } + + /// Make an HTTP GET request to given URL, deserializing to any `T` that + /// implement [`bitcoin::consensus::Decodable`] from Hex, [`Vec`]. + /// + /// It should be used when requesting Esplora endpoints that can be directly + /// deserialized to native `rust-bitcoin` types, which implements + /// [`bitcoin::consensus::Decodable`] from Hex, `Vec<&u8>`. + /// + /// # Errors + /// + /// This function will return an error either from the HTTP client, or the + /// [`bitcoin::consensus::Decodable`] deserialization. + async fn get_response_hex(&self, path: &str) -> Result { + let url = format!("{}{}", self.url, path); + let response = self.client.get(url).send().await?; + + match response.status().is_success() { + true => { + let hex_str = response.text().await?; + let hex_vec = Vec::from_hex(&hex_str)?; + Ok(deserialize(&hex_vec)?) + } + false => Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }), + } + } + + /// Make an HTTP GET request to given URL, deserializing to `Option`. + /// + /// It uses [`AsyncEsploraClient::get_response_hex`] internally. + /// + /// See [`AsyncEsploraClient::get_response_hex`] above for full + /// documentation. + async fn get_opt_response_hex(&self, path: &str) -> Result, Error> { + match self.get_response_hex(path).await { + Ok(res) => Ok(Some(res)), + Err(Error::HttpResponse { status, message }) => match status { + 404 => Ok(None), + _ => Err(Error::HttpResponse { status, message }), + }, + Err(e) => Err(e), + } + } + + /// Make an HTTP GET request to given URL, deserializing to `String`. + /// + /// It should be used when requesting Esplora endpoints that can return + /// `String` formatted data that can be parsed downstream. + /// + /// # Errors + /// + /// This function will return an error either from the HTTP client. + async fn get_response_text(&self, path: &str) -> Result { + let url = format!("{}{}", self.url, path); + let response = self.client.get(url).send().await?; + + match response.status().is_success() { + true => Ok(response.text().await?), + false => Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }), } + } + + /// Make an HTTP GET request to given URL, deserializing to `Option`. + /// + /// It uses [`AsyncEsploraClient::get_response_text`] internally. + /// + /// See [`AsyncEsploraClient::get_response_text`] above for full + /// documentation. + async fn get_opt_response_text(&self, path: &str) -> Result, Error> { + match self.get_response_text(path).await { + Ok(s) => Ok(Some(s)), + Err(Error::HttpResponse { status, message }) => match status { + 404 => Ok(None), + _ => Err(Error::HttpResponse { status, message }), + }, + Err(e) => Err(e), + } + } - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(deserialize(&resp.bytes().await?)?)) + /// Make an HTTP POST request to given URL, serializing from any `T` that + /// implement [`bitcoin::consensus::Encodable`]. + /// + /// It should be used when requesting Esplora endpoints that expected a + /// native bitcoin type serialized with [`bitcoin::consensus::Encodable`]. + /// + /// # Errors + /// + /// This function will return an error either from the HTTP client, or the + /// [`bitcoin::consensus::Encodable`] serialization. + async fn post_request_hex(&self, path: &str, body: T) -> Result<(), Error> { + let url = format!("{}{}", self.url, path); + let body = serialize::(&body).to_lower_hex_string(); + + let response = self.client.post(url).body(body).send().await?; + + match response.status().is_success() { + true => Ok(()), + false => Err(Error::HttpResponse { + status: response.status().as_u16(), + message: response.text().await?, + }), } } + /// Get a [`Transaction`] option given its [`Txid`] + pub async fn get_tx(&self, txid: &Txid) -> Result, Error> { + self.get_opt_response(&format!("/tx/{txid}/raw")).await + } + /// Get a [`Transaction`] given its [`Txid`]. pub async fn get_tx_no_opt(&self, txid: &Txid) -> Result { match self.get_tx(txid).await { @@ -109,167 +287,55 @@ impl AsyncClient { block_hash: &BlockHash, index: usize, ) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/block/{}/txid/{}", self.url, block_hash, index)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(Txid::from_str(&resp.text().await?)?)) + match self + .get_opt_response_text(&format!("/block/{block_hash}/txid/{index}")) + .await? + { + Some(s) => Ok(Some(Txid::from_str(&s).map_err(Error::HexToArray)?)), + None => Ok(None), } } /// Get the status of a [`Transaction`] given its [`Txid`]. pub async fn get_tx_status(&self, txid: &Txid) -> Result { - let resp = self - .client - .get(&format!("{}/tx/{}/status", self.url, txid)) - .send() - .await?; - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.json().await?) - } + self.get_response_json(&format!("/tx/{txid}/status")).await } /// Get transaction info given it's [`Txid`]. pub async fn get_tx_info(&self, txid: &Txid) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/tx/{}", self.url, txid)) - .send() - .await?; - if resp.status() == StatusCode::NOT_FOUND { - return Ok(None); - } - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(resp.json().await?)) - } + self.get_opt_response_json(&format!("/tx/{txid}")).await } /// Get a [`BlockHeader`] given a particular block hash. pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result { - let resp = self - .client - .get(&format!("{}/block/{}/header", self.url, block_hash)) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - let header = deserialize(&Vec::from_hex(&resp.text().await?)?)?; - Ok(header) - } + self.get_response_hex(&format!("/block/{block_hash}/header")) + .await } /// Get the [`BlockStatus`] given a particular [`BlockHash`]. pub async fn get_block_status(&self, block_hash: &BlockHash) -> Result { - let resp = self - .client - .get(&format!("{}/block/{}/status", self.url, block_hash)) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.json().await?) - } + self.get_response_json(&format!("/block/{block_hash}/status")) + .await } /// Get a [`Block`] given a particular [`BlockHash`]. pub async fn get_block_by_hash(&self, block_hash: &BlockHash) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/block/{}/raw", self.url, block_hash)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(deserialize(&resp.bytes().await?)?)) - } + self.get_opt_response(&format!("/block/{block_hash}/raw")) + .await } /// Get a merkle inclusion proof for a [`Transaction`] with the given /// [`Txid`]. pub async fn get_merkle_proof(&self, tx_hash: &Txid) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/tx/{}/merkle-proof", self.url, tx_hash)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(resp.json().await?)) - } + self.get_opt_response_json(&format!("/tx/{tx_hash}/merkle-proof")) + .await } /// Get a [`MerkleBlock`] inclusion proof for a [`Transaction`] with the /// given [`Txid`]. pub async fn get_merkle_block(&self, tx_hash: &Txid) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/tx/{}/merkleblock-proof", self.url, tx_hash)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - let merkle_block = deserialize(&Vec::from_hex(&resp.text().await?)?)?; - Ok(Some(merkle_block)) - } + self.get_opt_response_hex(&format!("/tx/{tx_hash}/merkleblock-proof")) + .await } /// Get the spending status of an output given a [`Txid`] and the output @@ -279,101 +345,34 @@ impl AsyncClient { txid: &Txid, index: u64, ) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/tx/{}/outspend/{}", self.url, txid, index)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Ok(None); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(Some(resp.json().await?)) - } + self.get_opt_response_json(&format!("/tx/{txid}/outspend/{index}")) + .await } /// Broadcast a [`Transaction`] to Esplora pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), Error> { - let resp = self - .client - .post(&format!("{}/tx", self.url)) - .body(serialize(transaction).to_lower_hex_string()) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(()) - } + self.post_request_hex("/tx", transaction).await } /// Get the current height of the blockchain tip pub async fn get_height(&self) -> Result { - let resp = self - .client - .get(&format!("{}/blocks/tip/height", self.url)) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.text().await?.parse()?) - } + self.get_response_text("/blocks/tip/height") + .await + .map(|height| u32::from_str(&height).map_err(Error::Parsing))? } /// Get the [`BlockHash`] of the current blockchain tip. pub async fn get_tip_hash(&self) -> Result { - let resp = self - .client - .get(&format!("{}/blocks/tip/hash", self.url)) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(BlockHash::from_str(&resp.text().await?)?) - } + self.get_response_text("/blocks/tip/hash") + .await + .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))? } /// Get the [`BlockHash`] of a specific block height pub async fn get_block_hash(&self, block_height: u32) -> Result { - let resp = self - .client - .get(&format!("{}/block-height/{}", self.url, block_height)) - .send() - .await?; - - if let StatusCode::NOT_FOUND = resp.status() { - return Err(Error::HeaderHeightNotFound(block_height)); - } - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(BlockHash::from_str(&resp.text().await?)?) - } + self.get_response_text(&format!("/block-height/{block_height}")) + .await + .map(|block_hash| BlockHash::from_str(&block_hash).map_err(Error::HexToArray))? } /// Get confirmed transaction history for the specified address/scripthash, @@ -386,43 +385,18 @@ impl AsyncClient { last_seen: Option, ) -> Result, Error> { let script_hash = sha256::Hash::hash(script.as_bytes()); - let url = match last_seen { - Some(last_seen) => format!( - "{}/scripthash/{:x}/txs/chain/{}", - self.url, script_hash, last_seen - ), - None => format!("{}/scripthash/{:x}/txs", self.url, script_hash), + let path = match last_seen { + Some(last_seen) => format!("/scripthash/{:x}/txs/chain/{}", script_hash, last_seen), + None => format!("/scripthash/{:x}/txs", script_hash), }; - let resp = self.client.get(url).send().await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.json::>().await?) - } + self.get_response_json(&path).await } /// Get an map where the key is the confirmation target (in number of /// blocks) and the value is the estimated feerate (in sat/vB). pub async fn get_fee_estimates(&self) -> Result, Error> { - let resp = self - .client - .get(&format!("{}/fee-estimates", self.url,)) - .send() - .await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.json::>().await?) - } + self.get_response_json("/fee-estimates").await } /// Gets some recent block summaries starting at the tip or at `height` if @@ -431,21 +405,11 @@ impl AsyncClient { /// The maximum number of summaries returned depends on the backend itself: /// esplora returns `10` while [mempool.space](https://mempool.space/docs/api) returns `15`. pub async fn get_blocks(&self, height: Option) -> Result, Error> { - let url = match height { - Some(height) => format!("{}/blocks/{}", self.url, height), - None => format!("{}/blocks", self.url), + let path = match height { + Some(height) => format!("/blocks/{height}"), + None => "/blocks".to_string(), }; - - let resp = self.client.get(&url).send().await?; - - if resp.status().is_server_error() || resp.status().is_client_error() { - Err(Error::HttpResponse { - status: resp.status().as_u16(), - message: resp.text().await?, - }) - } else { - Ok(resp.json::>().await?) - } + self.get_response_json(&path).await } /// Get the underlying base URL. From 442789cca2fde11a51460dd194153ac909a2ffaa Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Tue, 3 Sep 2024 11:43:30 -0300 Subject: [PATCH 3/4] chore(style): update new methods style --- src/async.rs | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/async.rs b/src/async.rs index 16d72d5..8a56f2a 100644 --- a/src/async.rs +++ b/src/async.rs @@ -129,13 +129,14 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.client.get(url).send().await?; - match response.status().is_success() { - true => Ok(response.json::().await.map_err(Error::Reqwest)?), - false => Err(Error::HttpResponse { + if !response.status().is_success() { + return Err(Error::HttpResponse { status: response.status().as_u16(), message: response.text().await?, - }), + }); } + + response.json::().await.map_err(Error::Reqwest) } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -173,17 +174,15 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.client.get(url).send().await?; - match response.status().is_success() { - true => { - let hex_str = response.text().await?; - let hex_vec = Vec::from_hex(&hex_str)?; - Ok(deserialize(&hex_vec)?) - } - false => Err(Error::HttpResponse { + if !response.status().is_success() { + return Err(Error::HttpResponse { status: response.status().as_u16(), message: response.text().await?, - }), + }); } + + let hex_str = response.text().await?; + Ok(deserialize(&Vec::from_hex(&hex_str)?)?) } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -215,13 +214,14 @@ impl AsyncClient { let url = format!("{}{}", self.url, path); let response = self.client.get(url).send().await?; - match response.status().is_success() { - true => Ok(response.text().await?), - false => Err(Error::HttpResponse { + if !response.status().is_success() { + return Err(Error::HttpResponse { status: response.status().as_u16(), message: response.text().await?, - }), + }); } + + Ok(response.text().await?) } /// Make an HTTP GET request to given URL, deserializing to `Option`. @@ -257,13 +257,14 @@ impl AsyncClient { let response = self.client.post(url).body(body).send().await?; - match response.status().is_success() { - true => Ok(()), - false => Err(Error::HttpResponse { + if !response.status().is_success() { + return Err(Error::HttpResponse { status: response.status().as_u16(), message: response.text().await?, - }), + }); } + + Ok(()) } /// Get a [`Transaction`] option given its [`Txid`] From 1103936477a2f502658b6240ea9be8917e8f12b0 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Mon, 9 Sep 2024 15:01:31 -0300 Subject: [PATCH 4/4] chore(style+docs): style and docs retouch --- src/async.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/async.rs b/src/async.rs index 8a56f2a..ac0a761 100644 --- a/src/async.rs +++ b/src/async.rs @@ -104,10 +104,7 @@ impl AsyncClient { async fn get_opt_response(&self, path: &str) -> Result, Error> { match self.get_response::(path).await { Ok(res) => Ok(Some(res)), - Err(Error::HttpResponse { status, message }) => match status { - 404 => Ok(None), - _ => Err(Error::HttpResponse { status, message }), - }, + Err(Error::HttpResponse { status: 404, .. }) => Ok(None), Err(e) => Err(e), } } @@ -151,20 +148,17 @@ impl AsyncClient { ) -> Result, Error> { match self.get_response_json(url).await { Ok(res) => Ok(Some(res)), - Err(Error::HttpResponse { status, message }) => match status { - 404 => Ok(None), - _ => Err(Error::HttpResponse { status, message }), - }, + Err(Error::HttpResponse { status: 404, .. }) => Ok(None), Err(e) => Err(e), } } /// Make an HTTP GET request to given URL, deserializing to any `T` that - /// implement [`bitcoin::consensus::Decodable`] from Hex, [`Vec`]. + /// implements [`bitcoin::consensus::Decodable`]. /// - /// It should be used when requesting Esplora endpoints that can be directly - /// deserialized to native `rust-bitcoin` types, which implements - /// [`bitcoin::consensus::Decodable`] from Hex, `Vec<&u8>`. + /// It should be used when requesting Esplora endpoints that are expected + /// to return a hex string decodable to native `rust-bitcoin` types which + /// implement [`bitcoin::consensus::Decodable`] from `&[u8]`. /// /// # Errors /// @@ -194,10 +188,7 @@ impl AsyncClient { async fn get_opt_response_hex(&self, path: &str) -> Result, Error> { match self.get_response_hex(path).await { Ok(res) => Ok(Some(res)), - Err(Error::HttpResponse { status, message }) => match status { - 404 => Ok(None), - _ => Err(Error::HttpResponse { status, message }), - }, + Err(Error::HttpResponse { status: 404, .. }) => Ok(None), Err(e) => Err(e), } } @@ -233,10 +224,7 @@ impl AsyncClient { async fn get_opt_response_text(&self, path: &str) -> Result, Error> { match self.get_response_text(path).await { Ok(s) => Ok(Some(s)), - Err(Error::HttpResponse { status, message }) => match status { - 404 => Ok(None), - _ => Err(Error::HttpResponse { status, message }), - }, + Err(Error::HttpResponse { status: 404, .. }) => Ok(None), Err(e) => Err(e), } }