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
105
106
107
108
109
110
111
112
113
114
115
116
117
use std::future::Future;
use std::sync::Arc;
use log;
use reqwest::{ Client, StatusCode, Url };
use tokio::timer::delay_for;
use crate::Result;
use crate::RiotApiError;
use crate::RiotApiConfig;
use crate::util::InsertOnlyCHashMap;
use super::RateLimit;
use super::RateLimitType;
pub struct RegionalRequester {
app_rate_limit: RateLimit,
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
}
impl RegionalRequester {
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
const NONE_STATUS_CODES: [u16; 3] = [ 204, 404, 422 ];
pub fn new() -> Self {
Self {
app_rate_limit: RateLimit::new(RateLimitType::Application),
method_rate_limits: InsertOnlyCHashMap::new(),
}
}
pub fn get<'a, T: serde::de::DeserializeOwned>(self: Arc<Self>,
config: &'a RiotApiConfig, client: &'a Client,
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>)
-> impl Future<Output = Result<Option<T>>> + 'a
{
async move {
let query = query.as_deref();
let mut retries: u8 = 0;
loop {
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
while let Some(delay) = RateLimit::get_both_or_delay(&self.app_rate_limit, &*method_rate_limit) {
delay_for(delay).await;
}
let url_base = format!("https://{}.api.riotgames.com", region_platform);
let mut url = Url::parse(&*url_base)
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
url.set_path(&*path);
url.set_query(query);
let response = client.get(url)
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
.send()
.await
.map_err(|e| RiotApiError::new(e, retries, None))?;
self.app_rate_limit.on_response(&config, &response);
method_rate_limit.on_response(&config, &response);
let status = response.status();
if Self::is_none_status_code(&status) {
log::trace!("Response {} (retried {} times), None result.", status, retries);
break Ok(None);
}
match response.error_for_status_ref() {
Ok(_) => {
log::trace!("Response {} (retried {} times), parsed result.", status, retries);
let value = response.json::<T>().await;
break value.map(|v| Some(v))
.map_err(|e| RiotApiError::new(e, retries, None));
},
Err(err) => {
if retries >= config.retries ||
(StatusCode::TOO_MANY_REQUESTS != status
&& !status.is_server_error())
{
log::debug!("Response {} (retried {} times), returning error.", status, retries);
break Err(RiotApiError::new(err, retries, Some(response)));
}
log::debug!("Response {} (retried {} times), retrying.", status, retries);
},
};
retries += 1;
}
}
}
fn is_none_status_code(status: &StatusCode) -> bool {
Self::NONE_STATUS_CODES.contains(&status.as_u16())
}
}
#[cfg(test)]
mod tests {
}