rustify/clients/
reqwest.rs

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
//! Contains an implementation of [Client][crate::client::Client] being backed
//! by the [reqwest](https://docs.rs/reqwest/) crate.

use crate::{client::Client as RustifyClient, errors::ClientError};
use async_trait::async_trait;
use http::{Request, Response};
use std::convert::TryFrom;

/// A client based on the
/// [reqwest::Client][1] which can be used for executing
/// [Endpoints][crate::endpoint::Endpoint]. A backing instance of a
/// [reqwest::Client][1] is used to increase performance and to save certain
/// characteristics across sessions. A base URL is required and is used to
/// qualify the full path of any [Endpoints][crate::endpoint::Endpoint] which
/// are executed by this client.
///
/// # Example
/// ```
/// use rustify::clients::reqwest::Client;
/// use rustify::Endpoint;
/// use rustify_derive::Endpoint;
/// use serde::Serialize;
///
/// #[derive(Debug, Endpoint, Serialize)]
/// #[endpoint(path = "my/endpoint")]
/// struct MyEndpoint {}
///
/// # tokio_test::block_on(async {
/// let client = Client::default("http://myapi.com");
/// let endpoint = MyEndpoint {};
/// let result = endpoint.exec(&client).await;
/// # })
/// ```
///
/// [1]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
pub struct Client {
    pub http: reqwest::Client,
    pub base: String,
}

impl Client {
    /// Creates a new instance of [Client] using the provided parameters.
    pub fn new(base: &str, http: reqwest::Client) -> Self {
        Client {
            base: base.to_string(),
            http,
        }
    }

    /// Creates a new instance of [Client] with a default instance of
    /// [reqwest::Client][1].
    ///
    /// [1]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
    pub fn default(base: &str) -> Self {
        Client {
            base: base.to_string(),
            http: reqwest::Client::default(),
        }
    }
}

#[async_trait]
impl RustifyClient for Client {
    fn base(&self) -> &str {
        self.base.as_str()
    }

    // TODO: remove the allow when the upstream clippy issue is fixed:
    // <https://github.com/rust-lang/rust-clippy/issues/12281>
    #[allow(clippy::blocks_in_conditions)]
    #[instrument(skip(self, req), err)]
    async fn send(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, ClientError> {
        let request = reqwest::Request::try_from(req)
            .map_err(|e| ClientError::ReqwestBuildError { source: e })?;

        let url_err = request.url().to_string();
        let method_err = request.method().to_string();
        let response = self
            .http
            .execute(request)
            .await
            .map_err(|e| ClientError::RequestError {
                source: e.into(),
                url: url_err,
                method: method_err,
            })?;

        let status_code = response.status().as_u16();
        let mut http_resp = http::Response::builder().status(status_code);
        for v in response.headers().into_iter() {
            http_resp = http_resp.header(v.0, v.1);
        }

        http_resp
            .body(
                response
                    .bytes()
                    .await
                    .map_err(|e| ClientError::ResponseError { source: e.into() })?
                    .to_vec(),
            )
            .map_err(|e| ClientError::ResponseError { source: e.into() })
    }
}