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}