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;
7
8/// Convenient wrapper for reading [`Body`] content from [`http::Response`].
9///
10/// It is useful in the most common response body reading cases.
11#[derive(Debug, Clone)]
12pub struct BodyReader<B> {
13    body: B,
14    #[allow(dead_code)]
15    headers: HeaderMap,
16}
17
18/// Read body errors.
19#[derive(Debug)]
20pub enum BodyReaderError<E, D> {
21    /// An error occurred while reading the body.
22    Read(E),
23    /// An error occurred while decoding the body content.
24    Decode(D),
25}
26
27impl<E, D> std::fmt::Display for BodyReaderError<E, D>
28where
29    E: std::fmt::Display,
30    D: std::fmt::Display,
31{
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            BodyReaderError::Read(err) => err.fmt(f),
35            BodyReaderError::Decode(err) => err.fmt(f),
36        }
37    }
38}
39
40// `thiserror` crate implements the `std::error::Error::source` method which breaks the boxing of the error.
41// For use cases of this crate the boxing of the errors is more important that the `source` method.
42impl<E, D> std::error::Error for BodyReaderError<E, D>
43where
44    E: std::fmt::Debug + std::fmt::Display,
45    D: std::fmt::Debug + std::fmt::Display,
46{
47}
48
49impl<B> BodyReader<B> {
50    /// Reads the full response body as [`Bytes`].
51    ///
52    /// # Example
53    ///
54    /// ```
55    #[doc = include_str!("../examples/read_bytes.rs")]
56    /// ```
57    pub async fn bytes(self) -> Result<Bytes, B::Error>
58    where
59        B: Body,
60        B::Data: Buf,
61    {
62        let body_bytes = self.body.collect().await?.to_bytes();
63        Ok(body_bytes)
64    }
65
66    /// Reads the full response text.
67    ///
68    /// # Note
69    ///
70    /// The method will only attempt to decode the response as `UTF-8`, regardless of the
71    /// `Content-Type` header.
72    ///
73    /// # Errors
74    ///
75    /// This method fails if the response body cannot be decoded as UTF-8.
76    ///
77    /// # Example
78    ///
79    /// ```
80    #[doc = include_str!("../examples/read_utf8.rs")]
81    /// ```
82    pub async fn utf8(self) -> Result<String, BodyReaderError<B::Error, FromUtf8Error>>
83    where
84        B: Body,
85        B::Data: Buf,
86    {
87        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
88        String::from_utf8(bytes.into()).map_err(BodyReaderError::Decode)
89    }
90
91    /// Deserializes the response body as JSON.
92    ///
93    /// # Errors
94    ///
95    /// This method fails whenever the response body is not valid JSON
96    /// or it cannot be properly deserialized to the target type `T`.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    #[doc = include_str!("../examples/read_json.rs")]
102    /// ```
103    #[cfg(feature = "json")]
104    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
105    pub async fn json<T>(self) -> Result<T, BodyReaderError<B::Error, serde_json::Error>>
106    where
107        T: serde::de::DeserializeOwned,
108        B: Body,
109        B::Data: Buf,
110    {
111        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
112        serde_json::from_slice(&bytes).map_err(BodyReaderError::Decode)
113    }
114
115    /// Deserializes the response body as form data.
116    ///
117    /// # Errors
118    ///
119    /// This method fails whenever the response body is not valid form data
120    /// or it cannot be properly deserialized to the target type `T`.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    #[doc = include_str!("../examples/read_form.rs")]
126    /// ```
127    #[cfg(feature = "form")]
128    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
129    pub async fn form<T>(self) -> Result<T, BodyReaderError<B::Error, serde_urlencoded::de::Error>>
130    where
131        T: serde::de::DeserializeOwned,
132        B: Body,
133        B::Data: Buf,
134    {
135        let bytes = self.bytes().await.map_err(BodyReaderError::Read)?;
136        serde_urlencoded::from_bytes(&bytes).map_err(BodyReaderError::Decode)
137    }
138
139    /// Maps the body content using the provided function.
140    ///
141    /// # Example
142    ///
143    /// ```
144    #[doc = include_str!("../examples/map_body.rs")]
145    /// ```
146    pub fn map<F, T>(self, f: F) -> BodyReader<T>
147    where
148        F: FnOnce(B) -> T,
149        T: Body,
150        T::Data: Buf,
151    {
152        BodyReader {
153            body: f(self.body),
154            headers: self.headers,
155        }
156    }
157}
158
159impl<B> From<http::Response<B>> for BodyReader<B> {
160    fn from(response: http::Response<B>) -> Self {
161        let (parts, body) = response.into_parts();
162        Self {
163            body,
164            headers: parts.headers,
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::BodyReaderError;
172
173    type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
174
175    // Check that the error can be converted into a boxed error.
176    #[test]
177    fn test_body_reader_error_into_boxed() {
178        // Read error
179        let read_error = std::io::Error::new(std::io::ErrorKind::Other, "read error");
180        let error: BodyReaderError<std::io::Error, BoxError> = BodyReaderError::Read(read_error);
181        let boxed_error: BoxError = Box::new(error);
182
183        assert_eq!(boxed_error.to_string(), "read error");
184        // Decode error
185        let decode_error = std::io::Error::new(std::io::ErrorKind::Other, "decode error");
186        let error: BodyReaderError<BoxError, std::io::Error> =
187            BodyReaderError::Decode(decode_error);
188        let boxed_error: BoxError = Box::new(error);
189
190        assert_eq!(boxed_error.to_string(), "decode error");
191    }
192}