rustify/clients/
reqwest.rs

1//! Contains an implementation of [Client][crate::client::Client] being backed
2//! by the [reqwest](https://docs.rs/reqwest/) crate.
3
4use crate::{client::Client as RustifyClient, errors::ClientError};
5use async_trait::async_trait;
6use http::{Request, Response};
7use std::convert::TryFrom;
8
9/// A client based on the
10/// [reqwest::Client][1] which can be used for executing
11/// [Endpoints][crate::endpoint::Endpoint]. A backing instance of a
12/// [reqwest::Client][1] is used to increase performance and to save certain
13/// characteristics across sessions. A base URL is required and is used to
14/// qualify the full path of any [Endpoints][crate::endpoint::Endpoint] which
15/// are executed by this client.
16///
17/// # Example
18/// ```
19/// use rustify::clients::reqwest::Client;
20/// use rustify::Endpoint;
21/// use rustify_derive::Endpoint;
22/// use serde::Serialize;
23///
24/// #[derive(Debug, Endpoint, Serialize)]
25/// #[endpoint(path = "my/endpoint")]
26/// struct MyEndpoint {}
27///
28/// # tokio_test::block_on(async {
29/// let client = Client::default("http://myapi.com");
30/// let endpoint = MyEndpoint {};
31/// let result = endpoint.exec(&client).await;
32/// # })
33/// ```
34///
35/// [1]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
36pub struct Client {
37    pub http: reqwest::Client,
38    pub base: String,
39}
40
41impl Client {
42    /// Creates a new instance of [Client] using the provided parameters.
43    pub fn new(base: &str, http: reqwest::Client) -> Self {
44        Client {
45            base: base.to_string(),
46            http,
47        }
48    }
49
50    /// Creates a new instance of [Client] with a default instance of
51    /// [reqwest::Client][1].
52    ///
53    /// [1]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
54    pub fn default(base: &str) -> Self {
55        Client {
56            base: base.to_string(),
57            http: reqwest::Client::default(),
58        }
59    }
60}
61
62#[async_trait]
63impl RustifyClient for Client {
64    fn base(&self) -> &str {
65        self.base.as_str()
66    }
67
68    // TODO: remove the allow when the upstream clippy issue is fixed:
69    // <https://github.com/rust-lang/rust-clippy/issues/12281>
70    #[allow(clippy::blocks_in_conditions)]
71    #[instrument(skip(self, req), err)]
72    async fn send(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, ClientError> {
73        let request = reqwest::Request::try_from(req)
74            .map_err(|e| ClientError::ReqwestBuildError { source: e })?;
75
76        let url_err = request.url().to_string();
77        let method_err = request.method().to_string();
78        let response = self
79            .http
80            .execute(request)
81            .await
82            .map_err(|e| ClientError::RequestError {
83                source: e.into(),
84                url: url_err,
85                method: method_err,
86            })?;
87
88        let status_code = response.status().as_u16();
89        let mut http_resp = http::Response::builder().status(status_code);
90        for v in response.headers().into_iter() {
91            http_resp = http_resp.header(v.0, v.1);
92        }
93
94        http_resp
95            .body(
96                response
97                    .bytes()
98                    .await
99                    .map_err(|e| ClientError::ResponseError { source: e.into() })?
100                    .to_vec(),
101            )
102            .map_err(|e| ClientError::ResponseError { source: e.into() })
103    }
104}