crux_http/response/
response.rs

1use super::{decode::decode_body, new_headers};
2use http_types::{
3    self,
4    headers::{self, HeaderName, HeaderValues, ToHeaderValues},
5    Mime, StatusCode, Version,
6};
7
8use http_types::{headers::CONTENT_TYPE, Headers};
9use serde::de::DeserializeOwned;
10
11use std::fmt;
12use std::ops::Index;
13
14/// An HTTP Response that will be passed to in a message to an apps update function
15#[derive(Clone, serde::Serialize, serde::Deserialize)]
16pub struct Response<Body> {
17    version: Option<http_types::Version>,
18    status: http_types::StatusCode,
19    #[serde(with = "header_serde")]
20    headers: Headers,
21    body: Option<Body>,
22}
23
24impl<Body> Response<Body> {
25    /// Create a new instance.
26    pub(crate) async fn new(mut res: super::ResponseAsync) -> crate::Result<Response<Vec<u8>>> {
27        let body = res.body_bytes().await?;
28        let status = res.status();
29
30        if status.is_client_error() || status.is_server_error() {
31            return Err(crate::HttpError::Http {
32                code: status,
33                message: status.to_string(),
34                body: Some(body),
35            });
36        }
37
38        let headers: &Headers = res.as_ref();
39        let headers = headers.clone();
40
41        Ok(Response {
42            status: res.status(),
43            headers,
44            version: res.version(),
45            body: Some(body),
46        })
47    }
48
49    /// Get the HTTP status code.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// # let res = crux_http::testing::ResponseBuilder::ok().build();
55    /// assert_eq!(res.status(), 200);
56    /// ```
57    pub fn status(&self) -> StatusCode {
58        self.status
59    }
60
61    /// Get the HTTP protocol version.
62    ///
63    /// # Examples
64    ///
65    /// ```no_run
66    /// # let res = crux_http::testing::ResponseBuilder::ok().build();
67    /// use crux_http::http::Version;
68    /// assert_eq!(res.version(), Some(Version::Http1_1));
69    /// ```
70    pub fn version(&self) -> Option<Version> {
71        self.version
72    }
73
74    /// Get a header.
75    ///
76    /// # Examples
77    ///
78    /// ```no_run
79    /// # let res = crux_http::testing::ResponseBuilder::ok()
80    /// #   .header("Content-Length", "1")
81    /// #   .build();
82    /// assert!(res.header("Content-Length").is_some());
83    /// ```
84    pub fn header(&self, name: impl Into<HeaderName>) -> Option<&HeaderValues> {
85        self.headers.get(name)
86    }
87
88    /// Get an HTTP header mutably.
89    pub fn header_mut(&mut self, name: impl Into<HeaderName>) -> Option<&mut HeaderValues> {
90        self.headers.get_mut(name)
91    }
92
93    /// Remove a header.
94    pub fn remove_header(&mut self, name: impl Into<HeaderName>) -> Option<HeaderValues> {
95        self.headers.remove(name)
96    }
97
98    /// Insert an HTTP header.
99    pub fn insert_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
100        self.headers.insert(key, value);
101    }
102
103    /// Append an HTTP header.
104    pub fn append_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
105        self.headers.append(key, value);
106    }
107
108    /// An iterator visiting all header pairs in arbitrary order.
109    #[must_use]
110    pub fn iter(&self) -> headers::Iter<'_> {
111        self.headers.iter()
112    }
113
114    /// An iterator visiting all header pairs in arbitrary order, with mutable references to the
115    /// values.
116    #[must_use]
117    pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
118        self.headers.iter_mut()
119    }
120
121    /// An iterator visiting all header names in arbitrary order.
122    #[must_use]
123    pub fn header_names(&self) -> headers::Names<'_> {
124        self.headers.names()
125    }
126
127    /// An iterator visiting all header values in arbitrary order.
128    #[must_use]
129    pub fn header_values(&self) -> headers::Values<'_> {
130        self.headers.values()
131    }
132
133    /// Get the response content type as a `Mime`.
134    ///
135    /// Gets the `Content-Type` header and parses it to a `Mime` type.
136    ///
137    /// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
138    ///
139    /// # Panics
140    ///
141    /// This method will panic if an invalid MIME type was set as a header.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// # let res = crux_http::testing::ResponseBuilder::ok()
147    /// #   .header("Content-Type", "application/json")
148    /// #   .build();
149    /// use crux_http::http::mime;
150    /// assert_eq!(res.content_type(), Some(mime::JSON));
151    /// ```
152    pub fn content_type(&self) -> Option<Mime> {
153        self.header(CONTENT_TYPE)?.last().as_str().parse().ok()
154    }
155
156    pub fn body(&self) -> Option<&Body> {
157        self.body.as_ref()
158    }
159
160    pub fn take_body(&mut self) -> Option<Body> {
161        self.body.take()
162    }
163
164    pub fn with_body<NewBody>(self, body: NewBody) -> Response<NewBody> {
165        Response {
166            body: Some(body),
167            headers: self.headers,
168            status: self.status,
169            version: self.version,
170        }
171    }
172}
173
174impl Response<Vec<u8>> {
175    pub(crate) fn new_with_status(status: http_types::StatusCode) -> Self {
176        let headers = new_headers();
177
178        Response {
179            status,
180            headers,
181            version: None,
182            body: None,
183        }
184    }
185
186    /// Reads the entire request body into a byte buffer.
187    ///
188    /// This method can be called after the body has already been read, but will
189    /// produce an empty buffer.
190    ///
191    /// # Errors
192    ///
193    /// Any I/O error encountered while reading the body is immediately returned
194    /// as an `Err`.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// # fn main() -> crux_http::Result<()> {
200    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
201    /// #   .header("Content-Type", "application/json")
202    /// #   .body(vec![0u8, 1])
203    /// #   .build();
204    /// let bytes: Vec<u8> = res.body_bytes()?;
205    /// # Ok(()) }
206    /// ```
207    pub fn body_bytes(&mut self) -> crate::Result<Vec<u8>> {
208        self.body.take().ok_or_else(|| crate::HttpError::Http {
209            code: self.status(),
210            message: "Body had no bytes".to_string(),
211            body: None,
212        })
213    }
214
215    /// Reads the entire response body into a string.
216    ///
217    /// This method can be called after the body has already been read, but will
218    /// produce an empty buffer.
219    ///
220    /// # Encodings
221    ///
222    /// If the "encoding" feature is enabled, this method tries to decode the body
223    /// with the encoding that is specified in the Content-Type header. If the header
224    /// does not specify an encoding, UTF-8 is assumed. If the "encoding" feature is
225    /// disabled, Surf only supports reading UTF-8 response bodies. The "encoding"
226    /// feature is enabled by default.
227    ///
228    /// # Errors
229    ///
230    /// Any I/O error encountered while reading the body is immediately returned
231    /// as an `Err`.
232    ///
233    /// If the body cannot be interpreted because the encoding is unsupported or
234    /// incorrect, an `Err` is returned.
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// # fn main() -> crux_http::Result<()> {
240    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
241    /// #   .header("Content-Type", "application/json")
242    /// #   .body("hello".to_string().into_bytes())
243    /// #   .build();
244    /// let string: String = res.body_string()?;
245    /// assert_eq!(string, "hello");
246    /// # Ok(()) }
247    /// ```
248    pub fn body_string(&mut self) -> crate::Result<String> {
249        let bytes = self.body_bytes()?;
250
251        let mime = self.content_type();
252        let claimed_encoding = mime
253            .as_ref()
254            .and_then(|mime| mime.param("charset"))
255            .map(|name| name.to_string());
256        Ok(decode_body(bytes, claimed_encoding.as_deref())?)
257    }
258
259    /// Reads and deserialized the entire request body from json.
260    ///
261    /// # Errors
262    ///
263    /// Any I/O error encountered while reading the body is immediately returned
264    /// as an `Err`.
265    ///
266    /// If the body cannot be interpreted as valid json for the target type `T`,
267    /// an `Err` is returned.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// # use serde::{Deserialize, Serialize};
273    /// # fn main() -> crux_http::Result<()> {
274    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
275    /// #   .header("Content-Type", "application/json")
276    /// #   .body("{\"ip\": \"127.0.0.1\"}".to_string().into_bytes())
277    /// #   .build();
278    /// #[derive(Deserialize, Serialize)]
279    /// struct Ip {
280    ///     ip: String
281    /// }
282    ///
283    /// let Ip { ip } = res.body_json()?;
284    /// assert_eq!(ip, "127.0.0.1");
285    /// # Ok(()) }
286    /// ```
287    pub fn body_json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
288        let body_bytes = self.body_bytes()?;
289        serde_json::from_slice(&body_bytes).map_err(crate::HttpError::from)
290    }
291}
292
293impl<Body> AsRef<http_types::Headers> for Response<Body> {
294    fn as_ref(&self) -> &http_types::Headers {
295        &self.headers
296    }
297}
298
299impl<Body> AsMut<http_types::Headers> for Response<Body> {
300    fn as_mut(&mut self) -> &mut http_types::Headers {
301        &mut self.headers
302    }
303}
304
305impl<Body> fmt::Debug for Response<Body> {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        f.debug_struct("Response")
308            .field("version", &self.version)
309            .field("status", &self.status)
310            .field("headers", &self.headers)
311            .finish_non_exhaustive()
312    }
313}
314
315impl<Body> Index<HeaderName> for Response<Body> {
316    type Output = HeaderValues;
317
318    /// Returns a reference to the value corresponding to the supplied name.
319    ///
320    /// # Panics
321    ///
322    /// Panics if the name is not present in `Response`.
323    #[inline]
324    fn index(&self, name: HeaderName) -> &HeaderValues {
325        &self.headers[name]
326    }
327}
328
329impl<Body> Index<&str> for Response<Body> {
330    type Output = HeaderValues;
331
332    /// Returns a reference to the value corresponding to the supplied name.
333    ///
334    /// # Panics
335    ///
336    /// Panics if the name is not present in `Response`.
337    #[inline]
338    fn index(&self, name: &str) -> &HeaderValues {
339        &self.headers[name]
340    }
341}
342
343impl<Body> PartialEq for Response<Body>
344where
345    Body: PartialEq,
346{
347    fn eq(&self, other: &Self) -> bool {
348        self.version == other.version
349            && self.status == other.status
350            && self.headers.iter().zip(other.headers.iter()).all(
351                |((lhs_name, lhs_values), (rhs_name, rhs_values))| {
352                    lhs_name == rhs_name
353                        && lhs_values
354                            .iter()
355                            .zip(rhs_values.iter())
356                            .all(|(lhs, rhs)| lhs == rhs)
357                },
358            )
359            && self.body == other.body
360    }
361}
362
363impl<Body> Eq for Response<Body> where Body: Eq {}
364
365#[cfg(feature = "http-compat")]
366impl<Body> TryInto<http::Response<Body>> for Response<Body> {
367    type Error = ();
368
369    fn try_into(self) -> Result<http::Response<Body>, Self::Error> {
370        let mut response = http::Response::new(self.body.ok_or(())?);
371
372        if let Some(version) = self.version {
373            let version = match version {
374                Version::Http0_9 => Some(http::Version::HTTP_09),
375                Version::Http1_0 => Some(http::Version::HTTP_10),
376                Version::Http1_1 => Some(http::Version::HTTP_11),
377                Version::Http2_0 => Some(http::Version::HTTP_2),
378                Version::Http3_0 => Some(http::Version::HTTP_3),
379                _ => None,
380            };
381
382            if let Some(version) = version {
383                *response.version_mut() = version;
384            }
385        }
386
387        let mut headers = self.headers;
388        headers_to_hyperium_headers(&mut headers, response.headers_mut());
389
390        Ok(response)
391    }
392}
393
394#[cfg(feature = "http-compat")]
395fn headers_to_hyperium_headers(headers: &mut Headers, hyperium_headers: &mut http::HeaderMap) {
396    for (name, values) in headers {
397        let name = format!("{}", name).into_bytes();
398        let name = http::header::HeaderName::from_bytes(&name).unwrap();
399
400        for value in values.iter() {
401            let value = format!("{}", value).into_bytes();
402            let value = http::header::HeaderValue::from_bytes(&value).unwrap();
403            hyperium_headers.append(&name, value);
404        }
405    }
406}
407
408mod header_serde {
409    use crate::{http::Headers, response::new_headers};
410    use http_types::headers::HeaderName;
411    use serde::{de::Error, Deserializer, Serializer};
412
413    pub fn serialize<S>(headers: &Headers, serializer: S) -> Result<S::Ok, S::Error>
414    where
415        S: Serializer,
416    {
417        serializer.collect_map(headers.iter().map(|(name, values)| {
418            (
419                name.as_str(),
420                values.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
421            )
422        }))
423    }
424
425    pub fn deserialize<'de, D>(deserializer: D) -> Result<Headers, D::Error>
426    where
427        D: Deserializer<'de>,
428    {
429        let strs = <Vec<(String, Vec<String>)> as serde::Deserialize>::deserialize(deserializer)?;
430
431        let mut headers = new_headers();
432
433        for (name, values) in strs {
434            let name = HeaderName::from_string(name).map_err(D::Error::custom)?;
435            for value in values {
436                headers.append(&name, value);
437            }
438        }
439
440        Ok(headers)
441    }
442}