http_request_derive/
from_http_response.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use bytes::Bytes;
6
7use crate::Error;
8
9/// Trait for types that can be converted from `http::Response`
10pub trait FromHttpResponse {
11    /// Convert from `http::Response` to our `Response`
12    ///
13    /// # Errors
14    ///
15    /// The implementation of this trait will map the response to an error if it should be interpreted as such.
16    /// Typical HTTP status code errors are read by the default implementation of [`crate::HttpRequest::read_response`]
17    /// already, so in most cases additional checks are not necessary here.
18    ///
19    /// Of course if the contents of the response cannot be parsed, this will usually be handled as an
20    /// error as well.
21    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error>
22    where
23        Self: Sized;
24}
25
26#[cfg(feature = "serde")]
27impl<D> FromHttpResponse for D
28where
29    D: serde::de::DeserializeOwned,
30{
31    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error> {
32        use snafu::ResultExt as _;
33
34        let status = http_response.status();
35
36        snafu::ensure!(
37            status.is_success(),
38            crate::error::NonSuccessStatusSnafu {
39                status,
40                body: http_response.into_body()
41            }
42        );
43        serde_json::from_slice(http_response.body()).context(crate::error::JsonSnafu)
44    }
45}
46
47#[cfg(all(test, feature = "serde"))]
48mod serde_tests {
49    use http::StatusCode;
50    use serde::Deserialize;
51
52    use super::*;
53
54    #[derive(Deserialize, Debug, PartialEq)]
55    struct TestStruct {
56        id: i32,
57        name: String,
58    }
59
60    #[tokio::test]
61    async fn test_from_response_success() {
62        let response = http::Response::builder()
63            .status(StatusCode::OK)
64            .header("content-type", "application/json")
65            .body(Bytes::from_static(r#"{"id":1,"name":"Test"}"#.as_bytes()))
66            .expect("valid response required");
67
68        let result = TestStruct::from_http_response(response);
69        assert!(result.is_ok());
70        assert_eq!(
71            result.unwrap(),
72            TestStruct {
73                id: 1,
74                name: "Test".to_string()
75            }
76        );
77    }
78
79    #[tokio::test]
80    async fn test_from_response_invalid_json() {
81        let response = http::Response::builder()
82            .status(StatusCode::OK)
83            .header("content-type", "application/json")
84            .body(Bytes::from_static(r#"{"id":1,"name":"Test"#.as_bytes()))
85            .expect("valid response required");
86
87        let result = TestStruct::from_http_response(response);
88        assert!(result.is_err());
89        assert!(matches!(result.unwrap_err(), Error::Json { .. }));
90    }
91}