From 90cf0069cb0e55508c32d58b57e3fbc77646e421 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Thu, 26 Sep 2024 16:44:38 -0700 Subject: [PATCH] Add empty body conversion from unit This makes it possible to convert an http::Request with an empty body by passing `()` to the `body` method. This is useful when writing sans-io libraries where you want to create requests that are agnostic to the specific reqwest implementation (async, blocking, wasm). E.g.: ```rust let http_request = http::Request::builder() .uri("http://localhost/") .body(())?; let req = reqwest::Request::try_from(http_request)?; reqwest::Client::new().execute(req).await?; // or let req = reqwest::blocking::Request::try_from(http_request)?; reqwest::blocking::Client::new().execute(req)?; ``` --- src/async_impl/body.rs | 23 +++++++++++++++++++++ src/async_impl/request.rs | 14 +++++++++++++ src/blocking/body.rs | 43 +++++++++++++++++++++++++++++++++++++++ src/blocking/request.rs | 14 +++++++++++++ src/wasm/body.rs | 20 ++++++++++++++++++ 5 files changed, 114 insertions(+) diff --git a/src/async_impl/body.rs b/src/async_impl/body.rs index c2f1257c1..07d63674f 100644 --- a/src/async_impl/body.rs +++ b/src/async_impl/body.rs @@ -208,6 +208,13 @@ impl From for Body { } */ +impl From<()> for Body { + #[inline] + fn from(_: ()) -> Body { + Body::empty() + } +} + impl From for Body { #[inline] fn from(bytes: Bytes) -> Body { @@ -467,6 +474,22 @@ mod tests { use super::Body; + #[test] + fn empty() { + let body = Body::empty(); + assert_eq!(body.as_bytes(), Some(&[] as &[u8])); + assert_eq!(body.size_hint().exact(), Some(0)); + assert!(body.is_end_stream()); + } + + #[test] + fn from_unit() { + let body = Body::from(()); + assert_eq!(body.as_bytes(), Some(&[] as &[u8])); + assert_eq!(body.size_hint().exact(), Some(0)); + assert!(body.is_end_stream()); + } + #[test] fn test_as_bytes() { let test_data = b"Test body"; diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index 76b40a788..cb0e9b615 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -656,6 +656,7 @@ mod tests { use super::{Client, HttpRequest, Request, RequestBuilder, Version}; use crate::Method; + use http::HeaderMap; use serde::Serialize; use std::collections::BTreeMap; use std::convert::TryFrom; @@ -877,6 +878,19 @@ mod tests { assert!(req.headers()["hiding"].is_sensitive()); } + #[test] + fn from_empty_http_request() { + let http_request = HttpRequest::builder() + .uri("http://localhost/") + .body(()) + .unwrap(); + let req = Request::try_from(http_request).unwrap(); + assert_eq!(req.body().unwrap().as_bytes(), Some(&[] as &[u8])); + assert_eq!(req.headers(), &HeaderMap::new()); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.url().as_str(), "http://localhost/"); + } + #[test] fn convert_from_http_request() { let http_request = HttpRequest::builder() diff --git a/src/blocking/body.rs b/src/blocking/body.rs index 4782b368c..f885c7678 100644 --- a/src/blocking/body.rs +++ b/src/blocking/body.rs @@ -115,6 +115,12 @@ impl Body { } } + pub(crate) fn empty() -> Body { + Body { + kind: Kind::Bytes(Bytes::new()), + } + } + #[cfg(feature = "multipart")] pub(crate) fn len(&self) -> Option { match self.kind { @@ -167,6 +173,20 @@ impl Kind { } } +impl Default for Body { + #[inline] + fn default() -> Body { + Body::empty() + } +} + +impl From<()> for Body { + #[inline] + fn from(_: ()) -> Body { + Body::empty() + } +} + impl From> for Body { #[inline] fn from(v: Vec) -> Body { @@ -367,3 +387,26 @@ pub(crate) fn read_to_string(mut body: Body) -> io::Result { } .map(|_| s) } + +#[cfg(test)] +mod tests { + use super::Body; + + #[test] + fn empty() { + let body = Body::empty(); + assert_eq!(body.as_bytes(), Some(&[] as &[u8])); + } + + #[test] + fn from_unit() { + let body = Body::from(()); + assert_eq!(body.as_bytes(), Some(&[] as &[u8])); + } + + #[test] + fn from_vec() { + let body = Body::from(vec![1, 2, 3]); + assert_eq!(body.as_bytes(), Some(&[1, 2, 3][..])); + } +} diff --git a/src/blocking/request.rs b/src/blocking/request.rs index 8ac112444..d23513fb8 100644 --- a/src/blocking/request.rs +++ b/src/blocking/request.rs @@ -1010,6 +1010,20 @@ mod tests { ); } + #[test] + fn from_empty_http_request() { + let http_request = HttpRequest::builder() + .method("GET") + .uri("http://localhost/") + .body(()) + .unwrap(); + let req = Request::try_from(http_request).unwrap(); + assert_eq!(req.body().unwrap().as_bytes(), Some(&[] as &[u8])); + assert_eq!(req.headers(), &HeaderMap::new()); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.url().as_str(), "http://localhost/"); + } + #[test] fn convert_from_http_request() { let http_request = HttpRequest::builder() diff --git a/src/wasm/body.rs b/src/wasm/body.rs index 241aa8173..4aadf0453 100644 --- a/src/wasm/body.rs +++ b/src/wasm/body.rs @@ -129,6 +129,20 @@ impl Body { Inner::MultipartForm(_) => None, } } + + #[inline] + pub(crate) fn empty() -> Body { + Body { + inner: Inner::Single(Single::Bytes(Bytes::new())), + } + } +} + +impl From<()> for Body { + #[inline] + fn from(_: ()) -> Body { + Body::empty() + } } impl From for Body { @@ -203,6 +217,12 @@ mod tests { fn log(s: String); } + #[wasm_bindgen_test] + async fn test_body_empty() { + let body = Body::empty(); + assert_eq!(body.as_bytes(), Some(&[] as &[u8])); + } + #[wasm_bindgen_test] async fn test_body() { let body = Body::from("TEST");