http_body_reader/
body_reader.rs

1use std::string::FromUtf8Error;
2
3use bytes::{Buf, Bytes};
4use http::HeaderMap;
5use http_body::Body;
6use http_body_util::BodyExt;
7use thiserror::Error;
8
9/// Convenient wrapper for reading [`Body`] content from [`http::Response`].
10///
11/// It is useful in the most common response body reading cases.
12#[derive(Debug, Clone)]
13pub struct BodyReader<B> {
14    body: B,
15    #[allow(dead_code)]
16    headers: HeaderMap,
17}
18
19/// Read body errors.
20#[derive(Debug, Error)]
21#[error(transparent)]
22pub enum BodyReaderError<E, D> {
23    /// An error occurred while reading the body.
24    Read(E),
25    /// An error occurred while decoding the body content.
26    Decode(D),
27}
28
29impl<B> BodyReader<B> {
30    /// Reads the full response body as [`Bytes`].
31    ///
32    /// # Example
33    ///
34    /// ```
35    #[doc = include_str!("../examples/read_bytes.rs")]
36    /// ```
37    pub async fn bytes(self) -> Result<Bytes, B::Error>
38    where
39        B: Body,
40        B::Data: Buf,
41    {
42        let body_bytes = self.body.collect().await?.to_bytes();
43        Ok(body_bytes)
44    }
45
46    /// Reads the full response text.
47    ///
48    /// # Note
49    ///
50    /// The method will only attempt to decode the response as `UTF-8`, regardless of the
51    /// `Content-Type` header.
52    ///
53    /// # Errors
54    ///
55    /// This method fails if the response body cannot be decoded as UTF-8.
56    ///
57    /// # Example
58    ///
59    /// ```
60    #[doc = include_str!("../examples/read_utf8.rs")]
61    /// ```
62    pub async fn utf8(self) -> Result<String, BodyReaderError<B::Error, FromUtf8Error>>
63    where
64        B: Body,
65        B::Data: Buf,
66    {
67        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
68        String::from_utf8(bytes.into()).map_err(BodyReaderError::Decode)
69    }
70
71    /// Deserializes the response body as JSON.
72    ///
73    /// # Errors
74    ///
75    /// This method fails whenever the response body is not valid JSON
76    /// or it cannot be properly deserialized to the target type `T`.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    #[doc = include_str!("../examples/read_json.rs")]
82    /// ```
83    #[cfg(feature = "json")]
84    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
85    pub async fn json<T>(self) -> Result<T, BodyReaderError<B::Error, serde_json::Error>>
86    where
87        T: serde::de::DeserializeOwned,
88        B: Body,
89        B::Data: Buf,
90    {
91        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
92        serde_json::from_slice(&bytes).map_err(BodyReaderError::Decode)
93    }
94
95    /// Deserializes the response body as form data.
96    ///
97    /// # Errors
98    ///
99    /// This method fails whenever the response body is not valid form data
100    /// or it cannot be properly deserialized to the target type `T`.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    #[doc = include_str!("../examples/read_form.rs")]
106    /// ```
107    #[cfg(feature = "form")]
108    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
109    pub async fn form<T>(self) -> Result<T, BodyReaderError<B::Error, serde_urlencoded::de::Error>>
110    where
111        T: serde::de::DeserializeOwned,
112        B: Body,
113        B::Data: Buf,
114    {
115        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
116        serde_urlencoded::from_bytes(&bytes).map_err(BodyReaderError::Decode)
117    }
118
119    /// Maps the body content using the provided function.
120    ///
121    /// # Example
122    ///
123    /// ```
124    #[doc = include_str!("../examples/map_body.rs")]
125    /// ```
126    pub fn map<F, T>(self, f: F) -> BodyReader<T>
127    where
128        F: FnOnce(B) -> T,
129        T: Body,
130        T::Data: Buf,
131    {
132        BodyReader {
133            body: f(self.body),
134            headers: self.headers,
135        }
136    }
137}
138
139impl<B> From<http::Response<B>> for BodyReader<B> {
140    fn from(response: http::Response<B>) -> Self {
141        let (parts, body) = response.into_parts();
142        Self {
143            body,
144            headers: parts.headers,
145        }
146    }
147}