boardgamegeek/
protocol.rs1use crate::result::{Error, Result};
2use backoff::{future, ExponentialBackoff};
3use log::warn;
4use reqwest::StatusCode;
5
6const LOG_TARGET: &str = "boardgamegeek::Client";
7
8pub struct Client {
10 http_client: reqwest::Client,
11 no_redirect_client: reqwest::Client,
12}
13
14async fn collect(response: reqwest::Response) -> Result<String> {
15 match response.text().await {
16 Ok(text) => Ok(text),
17 Err(_err) => Err(Error::BadResponse),
18 }
19}
20
21impl Client {
22 pub fn new() -> Self {
23 Self {
24 http_client: reqwest::Client::builder().build().unwrap(),
25 no_redirect_client: reqwest::Client::builder()
26 .redirect(reqwest::redirect::Policy::none())
27 .build()
28 .unwrap(),
29 }
30 }
31
32 pub async fn get(&self, url: &str) -> Result<String> {
33 future::retry(ExponentialBackoff::default(), || async {
34 let result = self.http_client.get(url).send().await;
35
36 if let Err(_err) = result {
37 return Err(backoff::Error::Permanent(Error::ConnectionFailed));
38 }
39
40 let response = result.unwrap();
41 match response.status() {
42 StatusCode::TOO_MANY_REQUESTS => {
43 warn!(target: LOG_TARGET, "Received 429: {:?}", response);
44 Err(backoff::Error::Transient(Error::TooManyRequests))
45 }
46 StatusCode::OK => Ok(collect(response).await?),
47 code => Err(backoff::Error::Permanent(Error::RequestFailed(
48 code.as_u16(),
49 ))),
50 }
51 })
52 .await
53 }
54
55 pub async fn get_redirect_location(&self, url: &str) -> Result<Option<String>> {
56 let result = self.no_redirect_client.get(url).send().await;
57
58 if let Err(_err) = result {
59 return Err(Error::ConnectionFailed);
60 }
61
62 let response = result.unwrap();
63 match response.status() {
64 StatusCode::FOUND => match response.headers().get("Location") {
65 Some(v) => Ok(Some(v.to_str().unwrap().to_owned())),
66 None => Ok(None),
67 },
68 code => Err(Error::RequestFailed(code.as_u16())),
69 }
70 }
71
72 pub async fn get_with_202_check(&self, url: &str) -> Result<String> {
73 future::retry(backoff::ExponentialBackoff::default(), || async {
74 match self.get(url).await {
75 Err(Error::RequestFailed(202)) => {
76 warn!(target: LOG_TARGET, "Received 202. Retrying...");
77 Err(backoff::Error::Transient(Error::RequestFailed(202)))
78 }
79
80 Err(e) => Err(backoff::Error::Permanent(e)),
81 Ok(r) => Ok(r),
82 }
83 })
84 .await
85 }
86}