generic_api_client/
http.rs

1use std::time::Duration;
2use serde::Serialize;
3use thiserror::Error;
4pub use reqwest::{Request, RequestBuilder, StatusCode, Method, header::{self, HeaderMap}};
5pub use bytes::Bytes;
6
7/// The User Agent string
8pub static USER_AGENT: &str = concat!("generic-api-client/", env!("CARGO_PKG_VERSION"));
9
10/// Client for communicating with APIs through HTTP/HTTPS.
11///
12/// When making a HTTP request or starting a websocket connection with this client,
13/// a handler that implements [RequestHandler] is required.
14#[derive(Debug, Clone)]
15pub struct Client {
16    client: reqwest::Client,
17}
18
19impl Client {
20    /// Constructs a new `Client`.
21    #[inline(always)]
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    /// Makes an HTTP request with the given [RequestHandler] and returns the response.
27    ///
28    /// It is recommended to use methods like [get()][Self::get()] because this method takes many type parameters and parameters.
29    ///
30    /// The request is passed to `handler` before being sent, and the response is passed to `handler` before being returned.
31    /// Note, that as stated in the docs for [RequestBuilder::query()], parameter `query` only accepts a **sequence of** key-value pairs.
32    pub async fn request<Q, B, H>(
33        &self, method: Method, url: &str, query: Option<&Q>, body: Option<B>, handler: &H,
34    ) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
35    where
36        Q: Serialize + ?Sized,
37        H: RequestHandler<B>,
38    {
39        let config = handler.request_config();
40        config.verify();
41        let url = config.url_prefix + url;
42        let mut count = 1;
43        loop {
44            // create RequestBuilder
45            let mut request_builder = self.client.request(method.clone(), url.clone())
46                .timeout(config.timeout);
47            if let Some(query) = query {
48                request_builder = request_builder.query(query);
49            }
50            let request = handler.build_request(request_builder, &body, count).map_err(RequestError::BuildRequestError)?;
51            // send the request
52            match self.client.execute(request).await {
53                Ok(mut response) => {
54                    let status = response.status();
55                    let headers = std::mem::take(response.headers_mut());
56                    let body = response.bytes().await.map_err(RequestError::ReceiveResponse)?;
57                    return handler.handle_response(status, headers, body).map_err(RequestError::ResponseHandleError);
58                },
59                Err(error) => {
60                    if count >= config.max_try {
61                        // max retry count
62                        return Err(RequestError::SendRequest(error));
63                    }
64                    log::warn!("Retrying sending reqeust, count: {}", count);
65                    // else, continue
66                    count += 1;
67                    tokio::time::sleep(config.retry_cooldown).await;
68                },
69            }
70        }
71    }
72
73    /// Makes an GET request with the given [RequestHandler].
74    ///
75    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
76    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
77    ///
78    /// For more information, see [request()][Self::request()].
79    #[inline(always)]
80    pub async fn get<Q, H>(&self, url: &str, query: Option<&Q>, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
81    where
82        Q: Serialize + ?Sized,
83        H: RequestHandler<()>,
84    {
85        self.request::<Q, (), H>(Method::GET, url, query, None, handler).await
86    }
87
88    /// Makes an GET request with the given [RequestHandler], without queries.
89    ///
90    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
91    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
92    ///
93    /// For more information, see [request()][Self::request()].
94    #[inline(always)]
95    pub async fn get_no_query<H>(&self, url: &str, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
96    where
97        H: RequestHandler<()>,
98    {
99        self.request::<&[(&str, &str)], (), H>(Method::GET, url, None, None, handler).await
100    }
101
102    /// Makes an POST request with the given [RequestHandler].
103    ///
104    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
105    ///
106    /// For more information, see [request()][Self::request()].
107    #[inline(always)]
108    pub async fn post<B, H>(&self, url: &str, body: Option<B>, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
109    where
110        H: RequestHandler<B>,
111    {
112        self.request::<(), B, H>(Method::POST, url, None, body, handler).await
113    }
114
115    /// Makes an POST request with the given [RequestHandler], without a body.
116    ///
117    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
118    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
119    ///
120    /// For more information, see [request()][Self::request()].
121    #[inline(always)]
122    pub async fn post_no_body<H>(&self, url: &str, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
123    where
124        H: RequestHandler<()>,
125    {
126        self.request::<(), (), H>(Method::POST, url, None, None, handler).await
127    }
128
129    /// Makes an PUT request with the given [RequestHandler].
130    ///
131    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
132    ///
133    /// For more information, see [request()][Self::request()].
134    #[inline(always)]
135    pub async fn put<B, H>(&self, url: &str, body: Option<B>, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
136    where
137        H: RequestHandler<B>,
138    {
139        self.request::<(), B, H>(Method::PUT, url, None, body, handler).await
140    }
141
142    /// Makes an PUT request with the given [RequestHandler], without a body.
143    ///
144    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
145    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
146    ///
147    /// For more information, see [request()][Self::request()].
148    #[inline(always)]
149    pub async fn put_no_body<H>(&self, url: &str, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
150    where
151        H: RequestHandler<()>,
152    {
153        self.request::<(), (), H>(Method::PUT, url, None, None, handler).await
154    }
155
156    /// Makes an DELETE request with the given [RequestHandler].
157    ///
158    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
159    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
160    ///
161    /// For more information, see [request()][Self::request()].
162    #[inline(always)]
163    pub async fn delete<Q, H>(&self, url: &str, query: Option<&Q>, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
164    where
165        Q: Serialize + ?Sized,
166        H: RequestHandler<()>,
167    {
168        self.request::<Q, (), H>(Method::DELETE, url, query, None, handler).await
169    }
170
171    /// Makes an DELETE request with the given [RequestHandler], without queries.
172    ///
173    /// This method just calls [request()][Self::request()]. It requires less typing for type parameters and parameters.
174    /// This method requires that `handler` can handle a request with a body of type `()`. The actual body passed will be `None`.
175    ///
176    /// For more information, see [request()][Self::request()].
177    #[inline(always)]
178    pub async fn delete_no_query<H>(&self, url: &str, handler: &H) -> Result<H::Successful, RequestError<H::BuildError, H::Unsuccessful>>
179    where
180        H: RequestHandler<()>,
181    {
182        self.request::<&[(&str, &str)], (), H>(Method::DELETE, url, None, None, handler).await
183    }
184}
185
186impl Default for Client {
187    fn default() -> Self {
188        let client = reqwest::ClientBuilder::new()
189            .user_agent(USER_AGENT)
190            .build()
191            .unwrap(); // user agent should be valid
192        Self {
193            client,
194        }
195    }
196}
197
198/// A `trait` which is used to process requests and responses for the [Client].
199pub trait RequestHandler<B> {
200    /// The type which is returned to the caller of [Client::request()] when the response was successful.
201    type Successful;
202    /// The type which is returned to the caller of [Client::request()] when the response was unsuccessful.
203    type Unsuccessful;
204    /// The type that represents an error occurred in [build_request()][Self::build_request()].
205    type BuildError;
206
207    /// Returns a [RequestConfig] that will be used to send a HTTP reqeust.
208    fn request_config(&self) -> RequestConfig {
209        RequestConfig::default()
210    }
211
212    /// Build a HTTP request to be sent.
213    ///
214    /// Implementors have to decide how to include the `request_body` into the `builder`. Implementors can
215    /// also perform other operations (such as authorization) on the request.
216    fn build_request(&self, builder: RequestBuilder, request_body: &Option<B>, attempt_count: u8) -> Result<Request, Self::BuildError>;
217
218    /// Handle a HTTP response before it is returned to the caller of [Client::request()].
219    ///
220    /// You can verify, parse, etc... the response here before it is returned to the caller.
221    ///
222    /// # Examples
223    /// ```
224    /// # use bytes::Bytes;
225    /// # use reqwest::{StatusCode, header::HeaderMap};
226    /// # trait Ignore {
227    /// fn handle_response(&self, status: StatusCode, _: HeaderMap, response_body: Bytes) -> Result<String, ()> {
228    ///     if status.is_success() {
229    ///         let body = std::str::from_utf8(&response_body).expect("body should be valid UTF-8").to_owned();
230    ///         Ok(body)
231    ///     } else {
232    ///         Err(())
233    ///     }
234    /// }
235    /// # }
236    /// ```
237    fn handle_response(&self, status: StatusCode, headers: HeaderMap, response_body: Bytes) -> Result<Self::Successful, Self::Unsuccessful>;
238}
239
240/// Configuration when sending a request using [Client].
241///
242/// Should be returned by [RequestHandler::request_config()].
243#[derive(Debug, Clone)]
244#[non_exhaustive]
245pub struct RequestConfig {
246    /// [Client] will retry sending a request if it failed to send. `max_try` can be used limit the number of attempts.
247    ///
248    /// Do not set this to `0` or [Client::request()] will **panic**. [Default]s to `1` (which means no retry).
249    pub max_try: u8,
250    /// Duration that should elapse after retrying sending a request.
251    ///
252    /// [Default]s to 500ms. See also: `max_try`.
253    pub retry_cooldown: Duration,
254    /// The timeout set when sending a request. [Default]s to 3s.
255    ///
256    /// It is possible for the [RequestHandler] to override this in [RequestHandler::build_request()].
257    /// See also: [RequestBuilder::timeout()].
258    pub timeout: Duration,
259    /// The prefix which will be used for requests sent using this configuration. [Default]s to `""`.
260    ///
261    /// Example usage: `"https://example.com"`
262    pub url_prefix: String,
263}
264
265impl RequestConfig {
266    /// Constructs a new `RequestConfig` with its fields set to [default][RequestConfig::default()].
267    #[inline(always)]
268    pub fn new() -> Self {
269        Self::default()
270    }
271
272    #[inline(always)]
273    fn verify(&self) {
274        assert_ne!(self.max_try, 0, "RequestConfig.max_try must not be equal to 0");
275    }
276}
277
278impl Default for RequestConfig {
279    fn default() -> Self {
280        Self {
281            max_try: 1,
282            retry_cooldown: Duration::from_millis(500),
283            timeout: Duration::from_secs(3),
284            url_prefix: String::new(),
285        }
286    }
287}
288
289/// An `enum` that represents errors that could be returned by [Client::request()]
290///
291/// Type parameter `R` is [RequestHandler::Unsuccessful].
292#[derive(Error, Debug)]
293pub enum RequestError<E, R> {
294    /// An error which occurred while sending a HTTP request.
295    #[error("failed to send reqeust")]
296    SendRequest(#[source] reqwest::Error),
297    /// An error which occurred while receiving a HTTP response.
298    #[error("failed to receive response")]
299    ReceiveResponse(#[source] reqwest::Error),
300    /// Error occurred in [RequestHandler::build_request()].
301    #[error("the handler failed to build a request")]
302    BuildRequestError(E),
303    /// An error which was returned by [RequestHandler].
304    #[error("the response handler returned an error")]
305    ResponseHandleError(R),
306}