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 async_trait::async_trait;
6use bytes::Bytes;
7
8use crate::Error;
9
10/// Trait for types that can be converted from `http::Response`
11#[async_trait]
12pub trait FromHttpResponse {
13    /// Convert from `http::Response` to our `Response`
14    ///
15    /// # Errors
16    ///
17    /// The implementation of this trait will map the response to an error if it should be interpreted as such.
18    /// Typical HTTP status code errors are read by the default implementation of [`crate::HttpRequest::read_response`]
19    /// already, so in most cases additional checks are not necessary here.
20    ///
21    /// Of course if the contents of the response cannot be parsed, this will usually be handled as an
22    /// error as well.
23    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error>
24    where
25        Self: Sized;
26
27    /// Convert from `reqwest::Response` to our `Response`
28    ///
29    /// # Errors
30    ///
31    /// The implementation of this trait will map the response to an error if it should be interpreted as such.
32    /// Typical HTTP status code errors are read by the default implementation of [`crate::HttpRequest::read_response`]
33    /// already, so in most cases additional checks are not necessary here.
34    ///
35    /// Of course if the contents of the response cannot be parsed, this will usually be handled as an
36    /// error as well.
37    #[cfg(feature = "reqwest")]
38    async fn from_reqwest_response(response: reqwest::Response) -> Result<Self, Error>
39    where
40        Self: Sized;
41}
42
43#[cfg(feature = "serde")]
44#[async_trait]
45impl<D> FromHttpResponse for D
46where
47    D: serde::de::DeserializeOwned,
48{
49    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error> {
50        use snafu::ResultExt as _;
51        serde_json::from_slice(http_response.body()).context(crate::error::JsonSnafu)
52    }
53
54    #[cfg(feature = "reqwest")]
55    async fn from_reqwest_response(response: reqwest::Response) -> Result<Self, Error>
56    where
57        Self: Sized,
58    {
59        use snafu::ResultExt as _;
60
61        use crate::error::ReqwestSnafu;
62
63        serde_json::from_slice(&response.bytes().await.context(ReqwestSnafu {
64            message: "Failed to receive success response",
65        })?)
66        .context(crate::error::JsonSnafu)
67    }
68}
69
70#[cfg(all(test, feature = "reqwest", feature = "serde"))]
71mod serde_reqwest_tests {
72    use reqwest::Client;
73    use serde::Deserialize;
74
75    use super::*;
76
77    #[derive(Deserialize, Debug, PartialEq)]
78    struct TestStruct {
79        id: i32,
80        name: String,
81    }
82
83    #[tokio::test]
84    async fn test_from_reqwest_response_success() {
85        let mut server = mockito::Server::new_async().await;
86        let _m = server
87            .mock("GET", "/test")
88            .with_status(200)
89            .with_header("content-type", "application/json")
90            .with_body(r#"{"id": 1, "name": "Test"}"#)
91            .create_async()
92            .await;
93
94        let client = Client::new();
95        let response = client
96            .get(format!("{}/test", server.url()))
97            .send()
98            .await
99            .unwrap();
100
101        let result = TestStruct::from_reqwest_response(response).await;
102        assert!(result.is_ok());
103        assert_eq!(
104            result.unwrap(),
105            TestStruct {
106                id: 1,
107                name: "Test".to_string()
108            }
109        );
110    }
111
112    #[tokio::test]
113    async fn test_from_reqwest_response_invalid_json() {
114        let mut server = mockito::Server::new_async().await;
115        let _m = server
116            .mock("GET", "/test")
117            .with_status(200)
118            .with_header("content-type", "application/json")
119            .with_body(r#"{"id": 1, "name": "Test"#)
120            .create_async()
121            .await;
122
123        let client = Client::new();
124        let response = client
125            .get(format!("{}/test", server.url()))
126            .send()
127            .await
128            .unwrap();
129
130        let result = TestStruct::from_reqwest_response(response).await;
131        assert!(result.is_err());
132        assert!(matches!(result.unwrap_err(), Error::Json { .. }));
133    }
134}