Skip to main content

volter_testing/
response.rs

1//! [`TestResponse`] — wraps an HTTP response with ergonomic body-read helpers.
2//!
3//! Body-acccess methods consume `self` because the body can be read only
4//! once.  Callers in test code typically chain `.unwrap()` on the returned
5//! [`Result`].
6
7use std::fmt;
8use std::str::Utf8Error;
9use std::string::FromUtf8Error;
10
11use bytes::Bytes;
12use http::HeaderMap;
13use http::StatusCode;
14use http_body_util::BodyExt;
15use serde::de::DeserializeOwned;
16
17use volter_core::BoxError;
18
19// ---------------------------------------------------------------------------
20// BodyError
21// ---------------------------------------------------------------------------
22
23/// Errors that can occur when consuming a test response body.
24#[derive(Debug)]
25pub enum BodyError {
26    /// The body stream produced an error.
27    Stream(BoxError),
28    /// The body bytes are not valid UTF-8.
29    Utf8(Utf8Error),
30    /// JSON deserialization failed.
31    Json(serde_json::Error),
32}
33
34impl fmt::Display for BodyError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            BodyError::Stream(e) => write!(f, "body stream error: {e}"),
38            BodyError::Utf8(e) => write!(f, "body UTF-8 error: {e}"),
39            BodyError::Json(e) => write!(f, "body JSON error: {e}"),
40        }
41    }
42}
43
44impl std::error::Error for BodyError {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        match self {
47            BodyError::Stream(e) => Some(e.as_ref()),
48            BodyError::Utf8(e) => Some(e),
49            BodyError::Json(e) => Some(e),
50        }
51    }
52}
53
54impl From<Utf8Error> for BodyError {
55    fn from(e: Utf8Error) -> Self {
56        BodyError::Utf8(e)
57    }
58}
59
60impl From<FromUtf8Error> for BodyError {
61    fn from(e: FromUtf8Error) -> Self {
62        BodyError::Utf8(e.utf8_error())
63    }
64}
65
66impl From<serde_json::Error> for BodyError {
67    fn from(e: serde_json::Error) -> Self {
68        BodyError::Json(e)
69    }
70}
71
72impl From<BoxError> for BodyError {
73    fn from(e: BoxError) -> Self {
74        BodyError::Stream(e)
75    }
76}
77
78// ---------------------------------------------------------------------------
79// TestResponse
80// ---------------------------------------------------------------------------
81
82/// The response returned by [`TestRequestBuilder::send`](crate::TestRequestBuilder).
83///
84/// Wraps an `http::Response<BoxBody>` and provides convenience methods for
85/// inspecting the status, headers, and body in tests.
86pub struct TestResponse {
87    /// The underlying HTTP response.
88    inner: volter_core::Response,
89}
90
91impl TestResponse {
92    /// Wrap an `http::Response<BoxBody>` into a `TestResponse`.
93    pub(crate) fn new(inner: volter_core::Response) -> Self {
94        Self { inner }
95    }
96
97    /// Return the response status code.
98    pub fn status(&self) -> StatusCode {
99        self.inner.status()
100    }
101
102    /// Return a reference to the response headers.
103    pub fn headers(&self) -> &HeaderMap {
104        self.inner.headers()
105    }
106
107    /// Consume the response and collect the full body as [`Bytes`].
108    ///
109    /// # Errors
110    ///
111    /// Returns [`BodyError::Stream`] if the body stream produces an error.
112    pub async fn bytes(self) -> Result<Bytes, BodyError> {
113        let collected = self
114            .inner
115            .into_body()
116            .collect()
117            .await
118            .map_err(BodyError::Stream)?;
119        Ok(collected.to_bytes())
120    }
121
122    /// Consume the response and decode the body as a UTF-8 string.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`BodyError::Stream`] if the body stream produces an error,
127    /// or [`BodyError::Utf8`] if the bytes are not valid UTF-8.
128    pub async fn text(self) -> Result<String, BodyError> {
129        let bytes = self.bytes().await?;
130        let text = String::from_utf8(bytes.to_vec())?;
131        Ok(text)
132    }
133
134    /// Consume the response and deserialize the body as JSON.
135    ///
136    /// # Errors
137    ///
138    /// Returns [`BodyError::Stream`] if the body stream produces an error,
139    /// [`BodyError::Json`] if deserialization fails.
140    pub async fn json<T: DeserializeOwned>(self) -> Result<T, BodyError> {
141        let bytes = self.bytes().await?;
142        let value = serde_json::from_slice(&bytes)?;
143        Ok(value)
144    }
145}