diff --git a/src/async_impl/body.rs b/src/async_impl/body.rs index 0ff7fec..dbe2809 100644 --- a/src/async_impl/body.rs +++ b/src/async_impl/body.rs @@ -125,6 +125,13 @@ impl Body { (reuse, self) } + pub(crate) fn try_clone(&self) -> Option { + match self.inner { + Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())), + Inner::Streaming { .. } => None, + } + } + pub(crate) fn into_stream(self) -> ImplStream { ImplStream(self) } diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index dc3cbd6..f4fe960 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -89,6 +89,20 @@ impl Request { &mut self.body } + /// Attempt to clone the request. + /// + /// `None` is returned if the request can not be cloned, i.e. if the body is a stream. + pub fn try_clone(&self) -> Option { + let body = match self.body.as_ref() { + Some(ref body) => Some(body.try_clone()?), + None => None, + }; + let mut req = Request::new(self.method().clone(), self.url().clone()); + *req.headers_mut() = self.headers().clone(); + req.body = body; + Some(req) + } + pub(super) fn pieces(self) -> (Method, Url, HeaderMap, Option) { (self.method, self.url, self.headers, self.body) } @@ -323,6 +337,36 @@ impl RequestBuilder { Err(err) => Pending::new_err(err), } } + + /// Attempt to clone the RequestBuilder. + /// + /// `None` is returned if the RequestBuilder can not be cloned, + /// i.e. if the request body is a stream. + /// + /// # Examples + /// + /// ``` + /// # use reqwest::Error; + /// # + /// # fn run() -> Result<(), Error> { + /// let client = reqwest::Client::new(); + /// let builder = client.post("http://httpbin.org/post") + /// .body("from a &str!"); + /// let clone = builder.try_clone(); + /// assert!(clone.is_some()); + /// # Ok(()) + /// # } + /// ``` + pub fn try_clone(&self) -> Option { + self.request + .as_ref() + .ok() + .and_then(|req| req.try_clone()) + .map(|req| RequestBuilder { + client: self.client.clone(), + request: Ok(req), + }) + } } impl fmt::Debug for Request { @@ -380,6 +424,7 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { #[cfg(test)] mod tests { use super::Client; + use crate::Method; use serde::Serialize; use std::collections::BTreeMap; @@ -488,6 +533,52 @@ mod tests { assert_eq!(req.url().as_str(), "https://google.com/"); } + #[test] + fn try_clone_reusable() { + let client = Client::new(); + let builder = client.post("http://httpbin.org/post") + .header("foo", "bar") + .body("from a &str!"); + let req = builder + .try_clone() + .expect("clone successful") + .build() + .expect("request is valid"); + assert_eq!(req.url().as_str(), "http://httpbin.org/post"); + assert_eq!(req.method(), Method::POST); + assert_eq!(req.headers()["foo"], "bar"); + } + + #[test] + fn try_clone_no_body() { + let client = Client::new(); + let builder = client.get("http://httpbin.org/get"); + let req = builder + .try_clone() + .expect("clone successful") + .build() + .expect("request is valid"); + assert_eq!(req.url().as_str(), "http://httpbin.org/get"); + assert_eq!(req.method(), Method::GET); + assert!(req.body().is_none()); + } + + #[test] + #[cfg(feature = "unstable-stream")] + fn try_clone_stream() { + let chunks: Vec> = vec![ + Ok("hello"), + Ok(" "), + Ok("world"), + ]; + let stream = futures_util::stream::iter(chunks); + let client = Client::new(); + let builder = client.get("http://httpbin.org/get") + .body(super::Body::wrap_stream(stream)); + let clone = builder.try_clone(); + assert!(clone.is_none()); + } + /* use {body, Method}; use super::Client;