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<'a, Body> IntoIterator for &'a Response<Body> {
175    type Item = (&'a headers::HeaderName, &'a headers::HeaderValues);
176    type IntoIter = headers::Iter<'a>;
177    fn into_iter(self) -> Self::IntoIter {
178        self.iter()
179    }
180}
181
182impl<'a, Body> IntoIterator for &'a mut Response<Body> {
183    type Item = (&'a headers::HeaderName, &'a mut headers::HeaderValues);
184    type IntoIter = headers::IterMut<'a>;
185    fn into_iter(self) -> Self::IntoIter {
186        self.iter_mut()
187    }
188}
189
190impl Response<Vec<u8>> {
191    pub(crate) fn new_with_status(status: http_types::StatusCode) -> Self {
192        let headers = new_headers();
193
194        Response {
195            status,
196            headers,
197            version: None,
198            body: None,
199        }
200    }
201
202    /// Reads the entire request body into a byte buffer.
203    ///
204    /// This method can be called after the body has already been read, but will
205    /// produce an empty buffer.
206    ///
207    /// # Errors
208    ///
209    /// Any I/O error encountered while reading the body is immediately returned
210    /// as an `Err`.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// # fn main() -> crux_http::Result<()> {
216    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
217    /// #   .header("Content-Type", "application/json")
218    /// #   .body(vec![0u8, 1])
219    /// #   .build();
220    /// let bytes: Vec<u8> = res.body_bytes()?;
221    /// # Ok(()) }
222    /// ```
223    pub fn body_bytes(&mut self) -> crate::Result<Vec<u8>> {
224        self.body.take().ok_or_else(|| crate::HttpError::Http {
225            code: self.status(),
226            message: "Body had no bytes".to_string(),
227            body: None,
228        })
229    }
230
231    /// Reads the entire response body into a string.
232    ///
233    /// This method can be called after the body has already been read, but will
234    /// produce an empty buffer.
235    ///
236    /// # Encodings
237    ///
238    /// If the "encoding" feature is enabled, this method tries to decode the body
239    /// with the encoding that is specified in the Content-Type header. If the header
240    /// does not specify an encoding, UTF-8 is assumed. If the "encoding" feature is
241    /// disabled, Surf only supports reading UTF-8 response bodies. The "encoding"
242    /// feature is enabled by default.
243    ///
244    /// # Errors
245    ///
246    /// Any I/O error encountered while reading the body is immediately returned
247    /// as an `Err`.
248    ///
249    /// If the body cannot be interpreted because the encoding is unsupported or
250    /// incorrect, an `Err` is returned.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// # fn main() -> crux_http::Result<()> {
256    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
257    /// #   .header("Content-Type", "application/json")
258    /// #   .body("hello".to_string().into_bytes())
259    /// #   .build();
260    /// let string: String = res.body_string()?;
261    /// assert_eq!(string, "hello");
262    /// # Ok(()) }
263    /// ```
264    pub fn body_string(&mut self) -> crate::Result<String> {
265        let bytes = self.body_bytes()?;
266
267        let mime = self.content_type();
268        let claimed_encoding = mime
269            .as_ref()
270            .and_then(|mime| mime.param("charset"))
271            .map(std::string::ToString::to_string);
272        Ok(decode_body(bytes, claimed_encoding.as_deref())?)
273    }
274
275    /// Reads and deserialized the entire request body from json.
276    ///
277    /// # Errors
278    ///
279    /// Any I/O error encountered while reading the body is immediately returned
280    /// as an `Err`.
281    ///
282    /// If the body cannot be interpreted as valid json for the target type `T`,
283    /// an `Err` is returned.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// # use serde::{Deserialize, Serialize};
289    /// # fn main() -> crux_http::Result<()> {
290    /// # let mut res = crux_http::testing::ResponseBuilder::ok()
291    /// #   .header("Content-Type", "application/json")
292    /// #   .body("{\"ip\": \"127.0.0.1\"}".to_string().into_bytes())
293    /// #   .build();
294    /// #[derive(Deserialize, Serialize)]
295    /// struct Ip {
296    ///     ip: String
297    /// }
298    ///
299    /// let Ip { ip } = res.body_json()?;
300    /// assert_eq!(ip, "127.0.0.1");
301    /// # Ok(()) }
302    /// ```
303    pub fn body_json<T: DeserializeOwned>(&mut self) -> crate::Result<T> {
304        let body_bytes = self.body_bytes()?;
305        serde_json::from_slice(&body_bytes).map_err(crate::HttpError::from)
306    }
307}
308
309impl<Body> AsRef<http_types::Headers> for Response<Body> {
310    fn as_ref(&self) -> &http_types::Headers {
311        &self.headers
312    }
313}
314
315impl<Body> AsMut<http_types::Headers> for Response<Body> {
316    fn as_mut(&mut self) -> &mut http_types::Headers {
317        &mut self.headers
318    }
319}
320
321impl<Body> fmt::Debug for Response<Body> {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        f.debug_struct("Response")
324            .field("version", &self.version)
325            .field("status", &self.status)
326            .field("headers", &self.headers)
327            .finish_non_exhaustive()
328    }
329}
330
331impl<Body> Index<HeaderName> for Response<Body> {
332    type Output = HeaderValues;
333
334    /// Returns a reference to the value corresponding to the supplied name.
335    ///
336    /// # Panics
337    ///
338    /// Panics if the name is not present in `Response`.
339    #[inline]
340    fn index(&self, name: HeaderName) -> &HeaderValues {
341        &self.headers[name]
342    }
343}
344
345impl<Body> Index<&str> for Response<Body> {
346    type Output = HeaderValues;
347
348    /// Returns a reference to the value corresponding to the supplied name.
349    ///
350    /// # Panics
351    ///
352    /// Panics if the name is not present in `Response`.
353    #[inline]
354    fn index(&self, name: &str) -> &HeaderValues {
355        &self.headers[name]
356    }
357}
358
359impl<Body> PartialEq for Response<Body>
360where
361    Body: PartialEq,
362{
363    fn eq(&self, other: &Self) -> bool {
364        self.version == other.version
365            && self.status == other.status
366            && self.headers.iter().zip(other.headers.iter()).all(
367                |((lhs_name, lhs_values), (rhs_name, rhs_values))| {
368                    lhs_name == rhs_name
369                        && lhs_values
370                            .iter()
371                            .zip(rhs_values.iter())
372                            .all(|(lhs, rhs)| lhs == rhs)
373                },
374            )
375            && self.body == other.body
376    }
377}
378
379impl<Body> Eq for Response<Body> where Body: Eq {}
380
381#[cfg(feature = "http-compat")]
382impl<Body> TryInto<http::Response<Body>> for Response<Body> {
383    type Error = ();
384
385    fn try_into(self) -> Result<http::Response<Body>, Self::Error> {
386        let mut response = http::Response::new(self.body.ok_or(())?);
387
388        if let Some(version) = self.version {
389            let version = match version {
390                Version::Http0_9 => Some(http::Version::HTTP_09),
391                Version::Http1_0 => Some(http::Version::HTTP_10),
392                Version::Http1_1 => Some(http::Version::HTTP_11),
393                Version::Http2_0 => Some(http::Version::HTTP_2),
394                Version::Http3_0 => Some(http::Version::HTTP_3),
395                _ => None,
396            };
397
398            if let Some(version) = version {
399                *response.version_mut() = version;
400            }
401        }
402
403        let mut headers = self.headers;
404        headers_to_hyperium_headers(&mut headers, response.headers_mut());
405
406        Ok(response)
407    }
408}
409
410#[cfg(feature = "http-compat")]
411fn headers_to_hyperium_headers(headers: &mut Headers, hyperium_headers: &mut http::HeaderMap) {
412    for (name, values) in headers {
413        let name = format!("{name}").into_bytes();
414        let name = http::header::HeaderName::from_bytes(&name).unwrap();
415
416        for value in values.iter() {
417            let value = format!("{value}").into_bytes();
418            let value = http::header::HeaderValue::from_bytes(&value).unwrap();
419            hyperium_headers.append(&name, value);
420        }
421    }
422}
423
424mod header_serde {
425    use crate::{http::Headers, response::new_headers};
426    use http_types::headers::{HeaderName, HeaderValue};
427    use serde::{de::Error, Deserializer, Serializer};
428
429    pub fn serialize<S>(headers: &Headers, serializer: S) -> Result<S::Ok, S::Error>
430    where
431        S: Serializer,
432    {
433        serializer.collect_map(headers.iter().map(|(name, values)| {
434            (
435                name.as_str(),
436                values.iter().map(HeaderValue::as_str).collect::<Vec<_>>(),
437            )
438        }))
439    }
440
441    pub fn deserialize<'de, D>(deserializer: D) -> Result<Headers, D::Error>
442    where
443        D: Deserializer<'de>,
444    {
445        let strs = <Vec<(String, Vec<String>)> as serde::Deserialize>::deserialize(deserializer)?;
446
447        let mut headers = new_headers();
448
449        for (name, values) in strs {
450            let name = HeaderName::from_string(name).map_err(D::Error::custom)?;
451            for value in values {
452                headers.append(&name, value);
453            }
454        }
455
456        Ok(headers)
457    }
458}