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() })
}
}