rustify/
client.rs

1//! Contains the [Client] trait for executing
2//! [Endpoints][crate::endpoint::Endpoint].
3use crate::errors::ClientError;
4use async_trait::async_trait;
5use http::{Request, Response};
6use std::ops::RangeInclusive;
7
8/// An array of HTTP response codes which indicate a successful response
9pub const HTTP_SUCCESS_CODES: RangeInclusive<u16> = 200..=208;
10
11/// Represents an HTTP client which is capable of executing
12/// [Endpoints][crate::endpoint::Endpoint] by sending the [Request] generated
13/// by the Endpoint and returning a [Response].
14#[async_trait]
15pub trait Client: Sync + Send {
16    /// Sends the given [Request] and returns a [Response]. Implementations
17    /// should consolidate all errors into the [ClientError] type.
18    async fn send(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, ClientError>;
19
20    /// Returns the base URL the client is configured with. This is used for
21    /// creating the fully qualified URLs used when executing
22    /// [Endpoints][crate::endpoint::Endpoint].
23    fn base(&self) -> &str;
24
25    /// This method provides a common interface to
26    /// [Endpoints][crate::endpoint::Endpoint] for execution.
27    // TODO: remove the allow when the upstream clippy issue is fixed:
28    // <https://github.com/rust-lang/rust-clippy/issues/12281>
29    #[allow(clippy::blocks_in_conditions)]
30    #[instrument(skip(self, req), err)]
31    async fn execute(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, ClientError> {
32        debug!(
33            "Client sending {} request to {} with {} bytes of data",
34            req.method().to_string(),
35            req.uri(),
36            req.body().len(),
37        );
38        let response = self.send(req).await?;
39
40        debug!(
41            "Client received {} response with {} bytes of body data",
42            response.status().as_u16(),
43            response.body().len()
44        );
45
46        // Check response
47        if !HTTP_SUCCESS_CODES.contains(&response.status().as_u16()) {
48            return Err(ClientError::ServerResponseError {
49                code: response.status().as_u16(),
50                content: String::from_utf8(response.body().to_vec()).ok(),
51            });
52        }
53
54        // Parse response content
55        Ok(response)
56    }
57}