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}