From a2219e5a95cd7919806be46fa461996e72363e9a Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Sat, 16 Nov 2024 02:08:00 +0800 Subject: [PATCH] Release `v0.6.0` --- Cargo.lock | 24 +++- Cargo.toml | 52 ++++---- README.md | 47 ++++++- src/blocking.rs | 228 ++++---------------------------- src/error.rs | 2 + src/lib.rs | 335 +++++++----------------------------------------- src/test.rs | 49 +++++-- 7 files changed, 211 insertions(+), 526 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f754f8..71fcc4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,6 +1258,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -1275,7 +1276,7 @@ dependencies = [ [[package]] name = "reqwew" -version = "0.5.0" +version = "0.6.0" dependencies = [ "bytes", "reqwest", @@ -1560,6 +1561,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.14.0" diff --git a/Cargo.toml b/Cargo.toml index 5b6e70e..1746c40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "GPL-3.0" name = "reqwew" readme = "README.md" repository = "https://github.com/hack-ink/reqwew" -version = "0.5.0" +version = "0.6.0" [profile.ci-dev] incremental = false @@ -20,34 +20,37 @@ default = [ "charset", "default-tls", "http2", + "reqwest", ] -blocking = ["reqwest/blocking"] -brotli = ["reqwest/brotli"] -charset = ["reqwest/charset"] -cookies = ["reqwest/cookies"] -default-tls = ["reqwest/default-tls"] -deflate = ["reqwest/deflate"] -gzip = ["reqwest/gzip"] -hickory-dns = ["reqwest/hickory-dns"] -http2 = ["reqwest/http2"] -json = ["reqwest/json"] -multipart = ["reqwest/multipart"] -native-tls = ["reqwest/native-tls"] -native-tls-alpn = ["reqwest/native-tls-alpn"] -native-tls-vendored = ["reqwest/native-tls-vendored"] -rustls-tls = ["reqwest/rustls-tls"] -rustls-tls-manual-roots = ["reqwest/rustls-tls-manual-roots"] -rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] -rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"] -socks = ["reqwest/socks"] -stream = ["reqwest/stream"] -zstd = ["reqwest/zstd"] +blocking = ["reqwest?/blocking"] +brotli = ["reqwest?/brotli"] +charset = ["reqwest?/charset"] +cookies = ["reqwest?/cookies"] +default-tls = ["reqwest?/default-tls"] +deflate = ["reqwest?/deflate"] +gzip = ["reqwest?/gzip"] +hickory-dns = ["reqwest?/hickory-dns"] +http2 = ["reqwest?/http2"] +json = ["reqwest?/json"] +multipart = ["reqwest?/multipart"] +native-tls = ["reqwest?/native-tls"] +native-tls-alpn = ["reqwest?/native-tls-alpn"] +native-tls-vendored = ["reqwest?/native-tls-vendored"] +rustls-tls = ["reqwest?/rustls-tls"] +rustls-tls-manual-roots = ["reqwest?/rustls-tls-manual-roots"] +rustls-tls-native-roots = ["reqwest?/rustls-tls-native-roots"] +rustls-tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"] +socks = ["reqwest?/socks"] +stream = ["reqwest?/stream"] +zstd = ["reqwest?/zstd"] + +extra-tracing = ["reqwest"] [dependencies] # crates.io bytes = { version = "1.8" } -reqwest = { version = "0.12", default-features = false } +reqwest = { version = "0.12", optional = true, default-features = false } serde = { version = "1.0" } serde_json = { version = "1.0" } thiserror = { version = "2.0" } @@ -56,4 +59,5 @@ tracing = { version = "0.1" } [dev-dependencies] # crates.io -tokio = { version = "1.41", features = ["macros"] } +reqwest = { version = "0.12", features = ["blocking"] } +tokio = { version = "1.41", features = ["macros"] } diff --git a/README.md b/README.md index e755458..ac16907 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
# reqwew -### Reqwest effortless wrapper. +### HTTP client effortless wrapper. [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Checks](https://github.com/hack-ink/reqwew/actions/workflows/checks.yml/badge.svg?branch=main)](https://github.com/hack-ink/reqwew/actions/workflows/checks.yml) @@ -9,8 +9,11 @@ [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/hack-ink/reqwew)](https://github.com/hack-ink/reqwew/tags) [![GitHub code lines](https://tokei.rs/b1/github/hack-ink/reqwew)](https://github.com/hack-ink/reqwew) [![GitHub last commit](https://img.shields.io/github/last-commit/hack-ink/reqwew?color=red&style=plastic)](https://github.com/hack-ink/reqwew) -
+At the beginning, the goal was to create an easy-to-use wrapper for [reqwest](https://github.com/seanmonstar/reqwest). + +Now it has evolved into a more generic solution, allowing you to implement the `HTTP` trait for any client to enjoy the handy features provided by reqwew. + ## Usage ### Async @@ -25,12 +28,26 @@ use serde_json::Value; pub static CLIENT: LazyLock = reqwew::lazy(|| Client::default()); // Async. -let resp = CLIENT.get_with_retries("https://httpbin.org/get", 3, 50).await.unwrap(); +let resp = CLIENT + .request_with_retries( + CLIENT.request(Method::GET, "https://httpbin.org/get").build().unwrap(), + 3, + 50, + ) + .await + .unwrap(); assert!(resp.clone().text().contains("httpbin.org")); assert_eq!(resp.json::().unwrap()["headers"]["Host"].as_str().unwrap(), "httpbin.org"); -let resp = CLIENT.post_with_retries("https://httpbin.org/post", "hello", 3, 50).await.unwrap(); +let resp = CLIENT + .request_with_retries( + CLIENT.request(Method::POST, "https://httpbin.org/post").body("hello").build().unwrap(), + 3, + 50, + ) + .await + .unwrap(); assert!(resp.clone().text().contains("https://httpbin.org/post")); assert_eq!(resp.json::().unwrap()["url"].as_str().unwrap(), "https://httpbin.org/post"); @@ -42,7 +59,7 @@ assert_eq!(resp.json::().unwrap()["url"].as_str().unwrap(), "https://http use std::sync::LazyLock; // crates.io use reqwew::{ -blocking::Http as BlockingHttp, reqwest::blocking::Client as BlockingClient, Response, + blocking::Http as BlockingHttp, reqwest::blocking::Client as BlockingClient, Response, }; use serde_json::Value; @@ -50,12 +67,28 @@ use serde_json::Value; pub static BLOCKING_CLIENT: LazyLock = reqwew::lazy(|| BlockingClient::default()); // Blocking. -let resp = BLOCKING_CLIENT.get_with_retries("https://httpbin.org/get", 3, 50).unwrap(); +let resp = BLOCKING_CLIENT + .request_with_retries( + BLOCKING_CLIENT.request(Method::GET, "https://httpbin.org/get").build().unwrap(), + 3, + 50, + ) + .unwrap(); assert!(resp.clone().text().contains("httpbin.org")); assert_eq!(resp.json::().unwrap()["headers"]["Host"].as_str().unwrap(), "httpbin.org"); -let resp = BLOCKING_CLIENT.post_with_retries("https://httpbin.org/post", "hello", 3, 50).unwrap(); +let resp = BLOCKING_CLIENT + .request_with_retries( + BLOCKING_CLIENT + .request(Method::POST, "https://httpbin.org/post") + .body("hello") + .build() + .unwrap(), + 3, + 50, + ) + .unwrap(); assert!(resp.clone().text().contains("https://httpbin.org/post")); assert_eq!(resp.json::().unwrap()["url"].as_str().unwrap(), "https://httpbin.org/post"); diff --git a/src/blocking.rs b/src/blocking.rs index 123882c..ae78bce 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -3,43 +3,37 @@ // std use std::thread; // crates.io -use reqwest::blocking::{Body, Client}; +#[cfg(feature = "reqwest")] use reqwest::blocking::{Client, Request}; // self use crate::*; -/// Basic blocking HTTP client functionality. +/// Blocking HTTP client functionality. pub trait Http where Self: Send + Sync, { + /// Request type. + /// + /// If the body is a stream type, it may not be cloneable. + type Request: Send + TryClone; + /// Response type. + type Response: Send; + /// Perform a generic request. - fn request(&self, uri: U, method: Method, body: Option) -> Result - where - U: Send + IntoUrl, - B: Send + Into; + fn request(&self, request: Self::Request) -> Result; /// Perform a generic request with retries. - fn request_with_retries( + fn request_with_retries( &self, - uri: U, - method: Method, - body: Option, + request: Self::Request, retries: u32, retry_delay_ms: u64, - ) -> Result - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - let u = uri.as_str(); - - tracing::debug!("{method:?} {u}"); - + ) -> Result { for i in 1..=retries { - match self.request(u, method, body.clone()) { + match self.request(request.try_clone().ok_or(Error::NonRetriableRequest)?) { Ok(r) => return Ok(r), Err(e) => { - tracing::error!("attempt {i}/{retries} failed for {u}: {e:?}, retrying in {retry_delay_ms}ms"); + tracing::error!("attempt {i}/{retries}, {e:?}, retrying in {retry_delay_ms}ms"); thread::sleep(Duration::from_millis(retry_delay_ms)); }, } @@ -47,189 +41,23 @@ where Err(Error::ExceededMaxRetries(retries))? } +} +#[cfg(feature = "reqwest")] +impl Http for Client { + type Request = Request; + type Response = Bytes; - /// Perform a GET request. - fn get(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Get, None::<&[u8]>) - } - - /// Perform a GET request with retries. - fn get_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Get, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a POST request. - fn post(&self, uri: U, body: B) -> Result - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Post, Some(body)) - } - - /// Perform a POST request with retries. - fn post_with_retries( - &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> Result - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Post, Some(body), retries, retry_delay_ms) - } - - /// Perform a PUT request. - fn put(&self, uri: U, body: B) -> Result - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Put, Some(body)) - } - - /// Perform a PUT request with retries. - fn put_with_retries( - &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> Result - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Put, Some(body), retries, retry_delay_ms) - } - - /// Perform a DELETE request. - fn delete(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Delete, None::<&[u8]>) - } - - /// Perform a DELETE request with retries. - fn delete_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Delete, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a HEAD request. - fn head(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Head, None::<&[u8]>) - } - - /// Perform a HEAD request with retries. - fn head_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Head, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform an OPTIONS request. - fn options(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Options, None::<&[u8]>) - } - - /// Perform an OPTIONS request with retries. - fn options_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Options, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a CONNECT request. - fn connect(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Connect, None::<&[u8]>) - } - - /// Perform a CONNECT request with retries. - fn connect_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Connect, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a PATCH request. - fn patch(&self, uri: U, body: B) -> Result - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Patch, Some(body)) - } - - /// Perform a PATCH request with retries. - fn patch_with_retries( - &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> Result - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Patch, Some(body), retries, retry_delay_ms) - } - - /// Perform a TRACE request. - fn trace(&self, uri: U) -> Result - where - U: Send + IntoUrl, - { - self.request(uri, Method::Trace, None::<&[u8]>) - } + fn request(&self, request: Self::Request) -> Result { + #[cfg(feature = "extra-tracing")] + tracing::info!("{:?} {}", request.method(), request.url()); - /// Perform a TRACE request with retries. - fn trace_with_retries(&self, uri: U, retries: u32, retry_delay_ms: u64) -> Result - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Trace, None::<&[u8]>, retries, retry_delay_ms) + Ok(self.execute(request)?.bytes()?) } } -impl Http for Client { - fn request(&self, uri: U, method: Method, body: Option) -> Result - where - U: Send + IntoUrl, - B: Send + Into, - { - let u = uri.as_str(); - - tracing::debug!("{method:?} {u}"); - Ok(if let Some(body) = body { - self.request(method.into(), uri).body(body).send()?.bytes()? - } else { - self.request(method.into(), uri).send()?.bytes()? - }) +#[cfg(feature = "reqwest")] +impl TryClone for Request { + fn try_clone(&self) -> Option { + self.try_clone() } } diff --git a/src/error.rs b/src/error.rs index 9275cc2..9a8c081 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,8 @@ pub enum Error { #[error(transparent)] SerdeJson(#[from] serde_json::Error), + #[error("[reqwew] non-retriable request; this typically occurs when attempting to retry a stream body request")] + NonRetriableRequest, #[error("[reqwew] max retries exceeded after {0} attempts")] ExceededMaxRetries(u32), } diff --git a/src/lib.rs b/src/lib.rs index 0ab41db..32c05aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Reqwest effortless wrapper. +//! HTTP client effortless wrapper. #![deny(clippy::all, missing_docs, unused_crate_dependencies)] @@ -9,93 +9,49 @@ use error::*; #[cfg(test)] mod test; -pub use reqwest; +#[cfg(feature = "reqwest")] pub use reqwest; // std use std::{future::Future, sync::LazyLock, time::Duration}; // crates.io use bytes::Bytes; -use reqwest::{Body, Client, IntoUrl, Method as RMethod}; +#[cfg(feature = "reqwest")] use reqwest::{Client, Request}; use serde::de::DeserializeOwned; use tokio::time; -/// HTTP methods. -#[derive(Clone, Copy, Debug)] -pub enum Method { - /// HTTP GET method. - Get, - /// HTTP POST method. - Post, - /// HTTP PUT method. - Put, - /// HTTP DELETE method. - Delete, - /// HTTP HEAD method. - Head, - /// HTTP OPTIONS method. - Options, - /// HTTP CONNECT method. - Connect, - /// HTTP PATCH method. - Patch, - /// HTTP TRACE method. - Trace, -} -impl From for RMethod { - fn from(method: Method) -> Self { - match method { - Method::Get => RMethod::GET, - Method::Post => RMethod::POST, - Method::Put => RMethod::PUT, - Method::Delete => RMethod::DELETE, - Method::Head => RMethod::HEAD, - Method::Options => RMethod::OPTIONS, - Method::Connect => RMethod::CONNECT, - Method::Patch => RMethod::PATCH, - Method::Trace => RMethod::TRACE, - } - } -} - -/// Basic HTTP client functionality. +/// HTTP client functionality. pub trait Http where Self: Send + Sync, { + /// Request type. + /// + /// If the body is a stream type, it may not be cloneable. + type Request: Send + TryClone; + /// Response type. + type Response: Send; + /// Perform a generic request. - fn request( + fn request( &self, - uri: U, - method: Method, - body: Option, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Send + Into; + request: Self::Request, + ) -> impl Send + Future>; /// Perform a generic request with retries. - fn request_with_retries( + fn request_with_retries( &self, - uri: U, - method: Method, - body: Option, + request: Self::Request, retries: u32, retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { + ) -> impl Send + Future> { async move { - let u = uri.as_str(); - - tracing::debug!("{method:?} {u}"); - for i in 1..=retries { - match self.request(u, method, body.clone()).await { + match self.request(request.try_clone().ok_or(Error::NonRetriableRequest)?).await { Ok(r) => return Ok(r), Err(e) => { - tracing::error!("attempt {i}/{retries} failed for {u}: {e:?}, retrying in {retry_delay_ms}ms"); + tracing::error!( + "attempt {i}/{retries}, {e:?}, retrying in {retry_delay_ms}ms" + ); time::sleep(Duration::from_millis(retry_delay_ms)).await; }, } @@ -104,231 +60,39 @@ where Err(Error::ExceededMaxRetries(retries))? } } +} +#[cfg(feature = "reqwest")] +impl Http for Client { + type Request = Request; + type Response = Bytes; - /// Perform a GET request. - fn get(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Get, None::<&[u8]>) - } - - /// Perform a GET request with retries. - fn get_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Get, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a POST request. - fn post(&self, uri: U, body: B) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Post, Some(body)) - } - - /// Perform a POST request with retries. - fn post_with_retries( - &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Post, Some(body), retries, retry_delay_ms) - } - - /// Perform a PUT request. - fn put(&self, uri: U, body: B) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Put, Some(body)) - } - - /// Perform a PUT request with retries. - fn put_with_retries( - &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Put, Some(body), retries, retry_delay_ms) - } - - /// Perform a DELETE request. - fn delete(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Delete, None::<&[u8]>) - } - - /// Perform a DELETE request with retries. - fn delete_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Delete, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a HEAD request. - fn head(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Head, None::<&[u8]>) - } - - /// Perform a HEAD request with retries. - fn head_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Head, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform an OPTIONS request. - fn options(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Options, None::<&[u8]>) - } - - /// Perform an OPTIONS request with retries. - fn options_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Options, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a CONNECT request. - fn connect(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Connect, None::<&[u8]>) - } - - /// Perform a CONNECT request with retries. - fn connect_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Connect, None::<&[u8]>, retries, retry_delay_ms) - } - - /// Perform a PATCH request. - fn patch(&self, uri: U, body: B) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Send + Into, - { - self.request(uri, Method::Patch, Some(body)) - } - - /// Perform a PATCH request with retries. - fn patch_with_retries( + fn request( &self, - uri: U, - body: B, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Clone + Send + Into, - { - self.request_with_retries(uri, Method::Patch, Some(body), retries, retry_delay_ms) - } + request: Self::Request, + ) -> impl Send + Future> { + #[cfg(feature = "extra-tracing")] + tracing::info!("{:?} {}", request.method(), request.url()); - /// Perform a TRACE request. - fn trace(&self, uri: U) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request(uri, Method::Trace, None::<&[u8]>) - } - - /// Perform a TRACE request with retries. - fn trace_with_retries( - &self, - uri: U, - retries: u32, - retry_delay_ms: u64, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - { - self.request_with_retries(uri, Method::Trace, None::<&[u8]>, retries, retry_delay_ms) + async move { Ok(self.execute(request).await?.bytes().await?) } } } -impl Http for Client { - fn request( - &self, - uri: U, - method: Method, - body: Option, - ) -> impl Future> + Send - where - U: Send + IntoUrl, - B: Send + Into, - { - let u = uri.as_str(); - - tracing::debug!("{method:?} {u}"); - async move { - Ok(if let Some(body) = body { - self.request(method.into(), uri).body(body).send().await?.bytes().await? - } else { - self.request(method.into(), uri).send().await?.bytes().await? - }) - } +/// Try clone. +pub trait TryClone +where + Self: Sized, +{ + /// Try clone the object. + fn try_clone(&self) -> Option; +} +#[cfg(feature = "reqwest")] +impl TryClone for Request { + fn try_clone(&self) -> Option { + self.try_clone() } } -/// [`reqwest::Response`] wrapper. +/// HTTP response. pub trait Response where Self: AsRef<[u8]>, @@ -344,7 +108,7 @@ where Ok(d) => Ok(d), Err(e) => { tracing::error!( - "failed to deserialize the following response into an object\n{}", + "failed to deserialize the following response into an object {}", String::from_utf8_lossy(s) ); @@ -358,22 +122,21 @@ where String::from_utf8_lossy(self.as_ref()).into() } } -impl Response for Bytes {} +impl Response for T where T: AsRef<[u8]> {} /// Create a new lazy static client instance. /// /// This is useful to avoid allocating multiple new clients. /// /// # Example -/// ```rust +/// ```rs /// // std /// use std::sync::LazyLock; /// // crates.io /// use reqwew::reqwest::{blocking::Client as BlockingClient, Client}; /// /// pub static CLIENT: LazyLock = reqwew::lazy(|| Client::default()); -/// pub static BLOCKING_CLIENT: LazyLock = -/// reqwew::lazy(|| BlockingClient::default()); +/// pub static BLOCKING_CLIENT: LazyLock = reqwew::lazy(|| BlockingClient::default()); /// ``` pub const fn lazy(f: F) -> LazyLock where diff --git a/src/test.rs b/src/test.rs index c5c4aad..c91694d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,34 +1,67 @@ // crates.io -use reqwest::blocking::Client as BlockingClient; +#[cfg(feature = "blocking")] use reqwest::blocking::Client as BlockingClient; +use reqwest::Method; use serde_json::Value; // self -use super::{blocking::Http as blockingHttp, *}; +use super::*; +#[cfg(feature = "blocking")] use blocking::Http as blockingHttp; static CLIENT: LazyLock = crate::lazy(|| Client::default()); +#[cfg(feature = "blocking")] static BLOCKING_CLIENT: LazyLock = crate::lazy(|| BlockingClient::default()); #[tokio::test] async fn http_and_response_should_work() { - let resp = CLIENT.get_with_retries("https://httpbin.org/get", 3, 50).await.unwrap(); + let resp = CLIENT + .request_with_retries( + CLIENT.request(Method::GET, "https://httpbin.org/get").build().unwrap(), + 3, + 50, + ) + .await + .unwrap(); assert!(resp.clone().text().contains("httpbin.org")); assert_eq!(resp.json::().unwrap()["headers"]["Host"].as_str().unwrap(), "httpbin.org"); - let resp = CLIENT.post_with_retries("https://httpbin.org/post", "hello", 3, 50).await.unwrap(); + let resp = CLIENT + .request_with_retries( + CLIENT.request(Method::POST, "https://httpbin.org/post").body("hello").build().unwrap(), + 3, + 50, + ) + .await + .unwrap(); assert!(resp.clone().text().contains("https://httpbin.org/post")); assert_eq!(resp.json::().unwrap()["url"].as_str().unwrap(), "https://httpbin.org/post"); } -#[cfg_attr(feature = "blocking", test)] +#[cfg(feature = "blocking")] +#[test] fn blocking_http_and_response_should_work() { - let resp = BLOCKING_CLIENT.get_with_retries("https://httpbin.org/get", 3, 50).unwrap(); + let resp = BLOCKING_CLIENT + .request_with_retries( + BLOCKING_CLIENT.request(Method::GET, "https://httpbin.org/get").build().unwrap(), + 3, + 50, + ) + .unwrap(); assert!(resp.clone().text().contains("httpbin.org")); assert_eq!(resp.json::().unwrap()["headers"]["Host"].as_str().unwrap(), "httpbin.org"); - let resp = - BLOCKING_CLIENT.post_with_retries("https://httpbin.org/post", "hello", 3, 50).unwrap(); + let resp = BLOCKING_CLIENT + .request_with_retries( + BLOCKING_CLIENT + .request(Method::POST, "https://httpbin.org/post") + .body("hello") + .build() + .unwrap(), + 3, + 50, + ) + .unwrap(); assert!(resp.clone().text().contains("https://httpbin.org/post")); assert_eq!(resp.json::().unwrap()["url"].as_str().unwrap(), "https://httpbin.org/post");