1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use async_trait::async_trait;
use bytes::Bytes;

use crate::Error;

/// Trait for types that can be converted from `http::Response`
#[async_trait]
pub trait FromHttpResponse {
    /// Convert from `http::Response` to our `Response`
    ///
    /// # Errors
    ///
    /// The implementation of this trait will map the response to an error if it should be interpreted as such.
    /// Typical HTTP status code errors are read by the default implementation of [`crate::HttpRequest::read_response`]
    /// already, so in most cases additional checks are not necessary here.
    ///
    /// Of course if the contents of the response cannot be parsed, this will usually be handled as an
    /// error as well.
    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error>
    where
        Self: Sized;

    /// Convert from `reqwest::Response` to our `Response`
    ///
    /// # Errors
    ///
    /// The implementation of this trait will map the response to an error if it should be interpreted as such.
    /// Typical HTTP status code errors are read by the default implementation of [`crate::HttpRequest::read_response`]
    /// already, so in most cases additional checks are not necessary here.
    ///
    /// Of course if the contents of the response cannot be parsed, this will usually be handled as an
    /// error as well.
    #[cfg(feature = "reqwest")]
    async fn from_reqwest_response(response: reqwest::Response) -> Result<Self, Error>
    where
        Self: Sized;
}

#[cfg(feature = "serde")]
#[async_trait]
impl<D> FromHttpResponse for D
where
    D: serde::de::DeserializeOwned,
{
    fn from_http_response(http_response: http::Response<Bytes>) -> Result<Self, Error> {
        use snafu::ResultExt as _;
        serde_json::from_slice(http_response.body()).context(crate::error::JsonSnafu)
    }

    #[cfg(feature = "reqwest")]
    async fn from_reqwest_response(response: reqwest::Response) -> Result<Self, Error>
    where
        Self: Sized,
    {
        use snafu::ResultExt as _;

        use crate::error::ReqwestSnafu;

        serde_json::from_slice(&response.bytes().await.context(ReqwestSnafu {
            message: "Failed to receive success response",
        })?)
        .context(crate::error::JsonSnafu)
    }
}

#[cfg(all(test, feature = "reqwest", feature = "serde"))]
mod serde_reqwest_tests {
    use reqwest::Client;
    use serde::Deserialize;

    use super::*;

    #[derive(Deserialize, Debug, PartialEq)]
    struct TestStruct {
        id: i32,
        name: String,
    }

    #[tokio::test]
    async fn test_from_reqwest_response_success() {
        let mut server = mockito::Server::new_async().await;
        let _m = server
            .mock("GET", "/test")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"id": 1, "name": "Test"}"#)
            .create_async()
            .await;

        let client = Client::new();
        let response = client
            .get(format!("{}/test", server.url()))
            .send()
            .await
            .unwrap();

        let result = TestStruct::from_reqwest_response(response).await;
        assert!(result.is_ok());
        assert_eq!(
            result.unwrap(),
            TestStruct {
                id: 1,
                name: "Test".to_string()
            }
        );
    }

    #[tokio::test]
    async fn test_from_reqwest_response_invalid_json() {
        let mut server = mockito::Server::new_async().await;
        let _m = server
            .mock("GET", "/test")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"id": 1, "name": "Test"#)
            .create_async()
            .await;

        let client = Client::new();
        let response = client
            .get(format!("{}/test", server.url()))
            .send()
            .await
            .unwrap();

        let result = TestStruct::from_reqwest_response(response).await;
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), Error::Json { .. }));
    }
}