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
use reqwest::{
    header::{HeaderMap, HeaderValue, CONTENT_TYPE},
    Client,
};

use crate::error::TankerkoenigError;

#[async_trait::async_trait]
#[cfg_attr(test, mockall::automock)]
pub(crate) trait HttpClient: std::fmt::Debug {
    async fn get(&self, url: &reqwest::Url) -> Result<String, TankerkoenigError>;
}

#[derive(Debug)]
pub(crate) struct HttpReqwestClientImpl {
    client: reqwest::Client,
}

impl HttpReqwestClientImpl {
    pub(crate) fn new(user_agent: &str) -> Result<Self, TankerkoenigError> {
        let mut headers = HeaderMap::new();
        headers.insert(
            CONTENT_TYPE,
            HeaderValue::from_str("application/json").unwrap(),
        );
        let client = Client::builder()
            .user_agent(user_agent)
            .default_headers(headers)
            .build()
            .map_err(|err| TankerkoenigError::ClientConstruction { source: err })
            .unwrap();

        Ok(Self { client })
    }
}

impl Default for HttpReqwestClientImpl {
    fn default() -> Self {
        let mut headers = HeaderMap::new();
        headers.insert(
            CONTENT_TYPE,
            HeaderValue::from_str("application/json").unwrap(),
        );
        let agent = format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
        let client = Client::builder()
            .user_agent(agent)
            .default_headers(headers)
            .build()
            .unwrap();

        Self { client }
    }
}

#[async_trait::async_trait]
impl HttpClient for HttpReqwestClientImpl {
    async fn get(&self, url: &reqwest::Url) -> Result<String, TankerkoenigError> {
        self.client
            .get(url.clone())
            .send()
            .await
            .map_err(|err| TankerkoenigError::RequestError { source: err })?
            .text()
            .await
            .map_err(|err| TankerkoenigError::RequestError { source: err })
    }
}

#[cfg(test)]
mod test {
    use wiremock::{matchers::path, MockServer};

    use super::*;

    #[tokio::test]
    async fn test_get() {
        // Start Http Server in Background
        let mock_server = MockServer::start().await;

        // Define behaviour
        wiremock::Mock::given(wiremock::matchers::method("GET"))
            .and(path("/hello"))
            .respond_with(wiremock::ResponseTemplate::new(200).set_body_string("Hello World!"))
            .mount(&mock_server)
            .await;

        // Create Client
        let client = HttpReqwestClientImpl::default();
        let url = reqwest::Url::parse(&format!("{}/hello", &mock_server.uri())).unwrap();

        // Send Request
        let res = client.get(&url).await.unwrap();

        // Assert Response
        assert_eq!(res, "Hello World!")
    }

    #[tokio::test]
    async fn test_get_with_useragent() {
        // Start Http Server in Background
        let mock_server = MockServer::start().await;

        // Define behaviour
        wiremock::Mock::given(wiremock::matchers::method("GET"))
            .and(path("/hello"))
            .respond_with(wiremock::ResponseTemplate::new(200).set_body_string("Hello World!"))
            .mount(&mock_server)
            .await;

        // Create Client
        let client = HttpReqwestClientImpl::new("my-user-agent").unwrap();
        let url = reqwest::Url::parse(&format!("{}/hello", &mock_server.uri())).unwrap();

        // Send Request
        let res = client.get(&url).await.unwrap();

        // Assert Response
        assert_eq!(res, "Hello World!")
    }
}