Skip to main content

crux_http/response/
response.rs

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