forked from mirror/Riven
Compare commits
4 Commits
v/2.x.x
...
users/ming
Author | SHA1 | Date |
---|---|---|
Mingwei Samuel | 30401e6004 | |
Mingwei Samuel | ded6a5af07 | |
Mingwei Samuel | 83a4b456d0 | |
Mingwei Samuel | 31c2794863 |
|
@ -90,31 +90,31 @@ jobs:
|
||||||
RUSTLOG: riven=trace
|
RUSTLOG: riven=trace
|
||||||
RGAPI_KEY: ${{ secrets.RGAPI_KEY }}
|
RGAPI_KEY: ${{ secrets.RGAPI_KEY }}
|
||||||
|
|
||||||
# lints:
|
lints:
|
||||||
# name: Lints
|
name: Lints
|
||||||
# needs: pre_job
|
needs: pre_job
|
||||||
# if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout sources
|
- name: Checkout sources
|
||||||
# uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# - name: Install nightly toolchain
|
- name: Install nightly toolchain
|
||||||
# uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
# with:
|
with:
|
||||||
# profile: minimal
|
profile: minimal
|
||||||
# toolchain: nightly
|
toolchain: nightly
|
||||||
# override: true
|
override: true
|
||||||
# components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
# - name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
# uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
# with:
|
with:
|
||||||
# command: fmt
|
command: fmt
|
||||||
# args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
# - name: Run cargo clippy
|
- name: Run cargo clippy
|
||||||
# uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
# with:
|
with:
|
||||||
# command: clippy
|
command: clippy
|
||||||
# args: -- -D warnings
|
args: -- -D warnings
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use hyper::http;
|
use http::{Method, Request, Response, StatusCode};
|
||||||
use http::{ Method, Request, Response, StatusCode };
|
|
||||||
use hyper::{ Body, Server };
|
|
||||||
use hyper::service::{ make_service_fn, service_fn };
|
|
||||||
use hyper::header::HeaderValue;
|
use hyper::header::HeaderValue;
|
||||||
|
use hyper::http;
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::{Body, Server};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use tracing as log;
|
use tracing as log;
|
||||||
|
|
||||||
use riven::{ RiotApi, RiotApiConfig };
|
|
||||||
use riven::consts::Route;
|
use riven::consts::Route;
|
||||||
|
use riven::{RiotApi, RiotApiConfig};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Create lazy static RiotApi instance.
|
/// Create lazy static RiotApi instance.
|
||||||
|
@ -31,47 +31,64 @@ lazy_static! {
|
||||||
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
||||||
let mut resp = Response::new(Body::from(body));
|
let mut resp = Response::new(Body::from(body));
|
||||||
*resp.status_mut() = status;
|
*resp.status_mut() = status;
|
||||||
resp.headers_mut().insert(hyper::header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
resp.headers_mut().insert(
|
||||||
|
hyper::header::CONTENT_TYPE,
|
||||||
|
HeaderValue::from_static("application/json"),
|
||||||
|
);
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main request handler service.
|
/// Main request handler service.
|
||||||
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let http::request::Parts { method, uri, .. } = parts;
|
let http::request::Parts { method, uri, .. } = parts;
|
||||||
|
|
||||||
// Handle path.
|
// Handle path.
|
||||||
let path_data_opt = parse_path(&method, uri.path());
|
let path_data_opt = parse_path(&method, uri.path());
|
||||||
let ( route, method_id, req_path ) = match path_data_opt {
|
let (route, method_id, req_path) = match path_data_opt {
|
||||||
None => return Ok(create_json_response(
|
None => {
|
||||||
r#"{"error":"Riot API endpoint method not found."}"#, StatusCode::NOT_FOUND)),
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Riot API endpoint method not found."}"#,
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
))
|
||||||
|
}
|
||||||
Some(path_data) => path_data,
|
Some(path_data) => path_data,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("Request to route {:?}, method ID {:?}: {} {:?}.", route, method_id, method, req_path);
|
log::debug!(
|
||||||
|
"Request to route {:?}, method ID {:?}: {} {:?}.",
|
||||||
|
route,
|
||||||
|
method_id,
|
||||||
|
method,
|
||||||
|
req_path
|
||||||
|
);
|
||||||
|
|
||||||
// Convert http:request::Parts from hyper to reqwest's RequestBuilder.
|
// Convert http:request::Parts from hyper to reqwest's RequestBuilder.
|
||||||
let body = match hyper::body::to_bytes(body).await {
|
let body = match hyper::body::to_bytes(body).await {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::info!("Error handling request body: {:#?}", err);
|
log::info!("Error handling request body: {:#?}", err);
|
||||||
return Ok(create_json_response(
|
return Ok(create_json_response(
|
||||||
r#"{"error":"Failed to handle request body."}"#, StatusCode::BAD_REQUEST));
|
r#"{"error":"Failed to handle request body."}"#,
|
||||||
},
|
StatusCode::BAD_REQUEST,
|
||||||
|
));
|
||||||
|
}
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
};
|
};
|
||||||
|
|
||||||
let req_builder = RIOT_API.request(method, route.into(), req_path)
|
let req_builder = RIOT_API.request(method, route.into(), req_path).body(body);
|
||||||
.body(body);
|
|
||||||
|
|
||||||
// Send request to Riot API.
|
// Send request to Riot API.
|
||||||
let resp_result = RIOT_API.execute_raw(method_id, route.into(), req_builder).await;
|
let resp_result = RIOT_API
|
||||||
|
.execute_raw(method_id, route.into(), req_builder)
|
||||||
|
.await;
|
||||||
let resp_info = match resp_result {
|
let resp_info = match resp_result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::info!("Riot API error: {:#?}", err.source_reqwest_error());
|
log::info!("Riot API error: {:#?}", err.source_reqwest_error());
|
||||||
return Ok(create_json_response(
|
return Ok(create_json_response(
|
||||||
r#"{"error":"Riot API request failed."}"#, StatusCode::INTERNAL_SERVER_ERROR));
|
r#"{"error":"Riot API request failed."}"#,
|
||||||
},
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
));
|
||||||
|
}
|
||||||
Ok(resp_info) => resp_info,
|
Ok(resp_info) => resp_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -86,37 +103,43 @@ async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible
|
||||||
}
|
}
|
||||||
// Otherwise copy body.
|
// Otherwise copy body.
|
||||||
else {
|
else {
|
||||||
*out_response.status_mut() = api_response.status();
|
*out_response.status_mut() = api_response.status();
|
||||||
|
|
||||||
// Using streams would be faster.
|
// Using streams would be faster.
|
||||||
let bytes_result = api_response.bytes().await;
|
let bytes_result = api_response.bytes().await;
|
||||||
let bytes = match bytes_result {
|
let bytes = match bytes_result {
|
||||||
Err(_err) => return Ok(create_json_response(
|
Err(_err) => {
|
||||||
r#"{"error":"Failed to get body from Riot API response."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Failed to get body from Riot API response."}"#,
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
))
|
||||||
|
}
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
};
|
};
|
||||||
*out_response.body_mut() = Body::from((&bytes[..]).to_vec());
|
*out_response.body_mut() = Body::from(bytes);
|
||||||
}
|
}
|
||||||
Ok(out_response)
|
Ok(out_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the region, method_id, and Riot API path based on the given http method and path.
|
/// Gets the region, method_id, and Riot API path based on the given http method and path.
|
||||||
fn parse_path<'a>(http_method: &Method, req_path: &'a str) -> Option<( Route, &'static str, &'a str )> {
|
fn parse_path<'a>(
|
||||||
|
http_method: &Method,
|
||||||
|
req_path: &'a str,
|
||||||
|
) -> Option<(Route, &'static str, &'a str)> {
|
||||||
// Split URI into region and rest of path.
|
// Split URI into region and rest of path.
|
||||||
let req_path = req_path.trim_start_matches('/');
|
let req_path = req_path.trim_start_matches('/');
|
||||||
let ( route, req_path ) = req_path.split_at(req_path.find('/')?);
|
let (route, req_path) = req_path.split_at(req_path.find('/')?);
|
||||||
let route: Route = route.to_uppercase().parse().ok()?;
|
let route: Route = route.to_uppercase().parse().ok()?;
|
||||||
|
|
||||||
// Find method_id for given path.
|
// Find method_id for given path.
|
||||||
let method_id = find_matching_method_id(http_method, req_path)?;
|
let method_id = find_matching_method_id(http_method, req_path)?;
|
||||||
|
|
||||||
Some(( route, method_id, req_path ))
|
Some((route, method_id, req_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the method_id given the request path.
|
/// Finds the method_id given the request path.
|
||||||
fn find_matching_method_id(http_method: &Method, req_path: &str) -> Option<&'static str> {
|
fn find_matching_method_id(http_method: &Method, req_path: &str) -> Option<&'static str> {
|
||||||
for ( endpoint_http_method, ref_path, method_id ) in &riven::meta::ALL_ENDPOINTS {
|
for (endpoint_http_method, ref_path, method_id) in &riven::meta::ALL_ENDPOINTS {
|
||||||
if http_method == endpoint_http_method && paths_match(ref_path, req_path) {
|
if http_method == endpoint_http_method && paths_match(ref_path, req_path) {
|
||||||
return Some(method_id);
|
return Some(method_id);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +183,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
||||||
});
|
});
|
||||||
|
|
||||||
let addr = ([ 127, 0, 0, 1 ], 3000).into();
|
let addr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(make_svc);
|
let server = Server::bind(&addr).serve(make_svc);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "riven"
|
name = "riven"
|
||||||
version = "2.17.0"
|
version = "2.18.0"
|
||||||
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
||||||
repository = "https://github.com/MingweiSamuel/Riven"
|
repository = "https://github.com/MingweiSamuel/Riven"
|
||||||
description = "Riot Games API Library"
|
description = "Riot Games API Library"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//! Configuration of RiotApi.
|
//! Configuration of RiotApi.
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
use reqwest::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
use reqwest::header::{ HeaderMap, HeaderValue };
|
|
||||||
|
|
||||||
/// Configuration for instantiating RiotApi.
|
/// Configuration for instantiating RiotApi.
|
||||||
///
|
///
|
||||||
|
@ -72,7 +72,7 @@ impl RiotApiConfig {
|
||||||
let mut default_headers = HeaderMap::new();
|
let mut default_headers = HeaderMap::new();
|
||||||
default_headers.insert(
|
default_headers.insert(
|
||||||
Self::RIOT_KEY_HEADER,
|
Self::RIOT_KEY_HEADER,
|
||||||
HeaderValue::from_bytes(api_key.as_ref()).unwrap()
|
HeaderValue::from_bytes(api_key.as_ref()).unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -82,10 +82,7 @@ impl RiotApiConfig {
|
||||||
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||||
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
||||||
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
||||||
client_builder: Some(
|
client_builder: Some(ClientBuilder::new().default_headers(default_headers)),
|
||||||
ClientBuilder::new()
|
|
||||||
.default_headers(default_headers)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +188,10 @@ impl RiotApiConfig {
|
||||||
self.method_rate_usage_factor = rate_usage_factor;
|
self.method_rate_usage_factor = rate_usage_factor;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
panic!("rate_usage_factor \"{}\" not in range (0, 1].", rate_usage_factor);
|
panic!(
|
||||||
|
"rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
rate_usage_factor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [Self::set_rate_usage_factor]. Setting this is useful if you have multiple
|
/// See [Self::set_rate_usage_factor]. Setting this is useful if you have multiple
|
||||||
|
@ -209,7 +209,10 @@ impl RiotApiConfig {
|
||||||
self.app_rate_usage_factor = app_rate_usage_factor;
|
self.app_rate_usage_factor = app_rate_usage_factor;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
panic!("app_rate_usage_factor \"{}\" not in range (0, 1].", app_rate_usage_factor);
|
panic!(
|
||||||
|
"app_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
app_rate_usage_factor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [Self::set_rate_usage_factor] and [Self::set_app_rate_usage_factor].
|
/// See [Self::set_rate_usage_factor] and [Self::set_app_rate_usage_factor].
|
||||||
|
@ -227,7 +230,10 @@ impl RiotApiConfig {
|
||||||
self.method_rate_usage_factor = method_rate_usage_factor;
|
self.method_rate_usage_factor = method_rate_usage_factor;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
panic!("method_rate_usage_factor \"{}\" not in range (0, 1].", method_rate_usage_factor);
|
panic!(
|
||||||
|
"method_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
method_rate_usage_factor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Burst percentage controls how many burst requests are allowed and
|
/// Burst percentage controls how many burst requests are allowed and
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use serde::{ Serialize, Deserialize };
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
|
|
||||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||||
///
|
///
|
||||||
|
@ -12,25 +12,36 @@ use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
Debug,
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
Copy,
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
Clone,
|
||||||
#[derive(Serialize, Deserialize)]
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Division {
|
pub enum Division {
|
||||||
/// Division 1, the best/highest division in a [`Tier`](crate::consts::Tier), or the only division in
|
/// Division 1, the best/highest division in a [`Tier`](crate::consts::Tier), or the only division in
|
||||||
/// [apex tiers](crate::consts::Tier::is_apex).
|
/// [apex tiers](crate::consts::Tier::is_apex).
|
||||||
I = 1,
|
I = 1,
|
||||||
/// Division 2, the second highest division.
|
/// Division 2, the second highest division.
|
||||||
II = 2,
|
II = 2,
|
||||||
/// Division 3, the third highest division.
|
/// Division 3, the third highest division.
|
||||||
III = 3,
|
III = 3,
|
||||||
/// Division 4, the fourth and lowest division since 2019.
|
/// Division 4, the fourth and lowest division since 2019.
|
||||||
IV = 4,
|
IV = 4,
|
||||||
/// Division 5, the lowest division, only used before 2019.
|
/// Division 5, the lowest division, only used before 2019.
|
||||||
#[deprecated(note="Removed for 2019.")]
|
#[deprecated(note = "Removed for 2019.")]
|
||||||
V = 5,
|
V = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||||
|
@ -39,7 +50,7 @@ pub enum Division {
|
||||||
impl IntoEnumIterator for Division {
|
impl IntoEnumIterator for Division {
|
||||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||||
fn iter() -> Self::Iterator {
|
fn iter() -> Self::Iterator {
|
||||||
[ Self::I, Self::II, Self::III, Self::IV ].iter().copied()
|
[Self::I, Self::II, Self::III, Self::IV].iter().copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,4 +74,4 @@ mod tests {
|
||||||
fn sort() {
|
fn sort() {
|
||||||
assert!(Division::IV < Division::I);
|
assert!(Division::IV < Division::I);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ macro_rules! serde_strum_unknown {
|
||||||
impl<'de> serde::de::Deserialize<'de> for $name {
|
impl<'de> serde::de::Deserialize<'de> for $name {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::de::Deserializer<'de>
|
D: serde::de::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
||||||
{
|
{
|
||||||
|
@ -46,20 +46,19 @@ macro_rules! serde_strum_unknown {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "deny-unknown-enum-variants-strings")]
|
#[cfg(feature = "deny-unknown-enum-variants-strings")]
|
||||||
{
|
{
|
||||||
<&str>::deserialize(deserializer).map(Into::into)
|
<&str>::deserialize(deserializer)
|
||||||
.and_then(|item| {
|
.map(Into::into)
|
||||||
match item {
|
.and_then(|item| match item {
|
||||||
Self::UNKNOWN(unknown) => Err(serde::de::Error::unknown_variant(
|
Self::UNKNOWN(unknown) => Err(serde::de::Error::unknown_variant(
|
||||||
&*unknown,
|
&*unknown,
|
||||||
<Self as strum::VariantNames>::VARIANTS,
|
<Self as strum::VariantNames>::VARIANTS,
|
||||||
)),
|
)),
|
||||||
other => Ok(other),
|
other => Ok(other),
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! arr {
|
macro_rules! arr {
|
||||||
|
|
|
@ -8,35 +8,43 @@
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod champion;
|
mod champion;
|
||||||
pub use champion::*;
|
pub use champion::*;
|
||||||
|
|
||||||
mod division;
|
mod division;
|
||||||
pub use division::*;
|
pub use division::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod game_mode;
|
mod game_mode;
|
||||||
pub use game_mode::*;
|
pub use game_mode::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod game_type;
|
mod game_type;
|
||||||
pub use game_type::*;
|
pub use game_type::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod map;
|
mod map;
|
||||||
pub use map::*;
|
pub use map::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod queue_type;
|
mod queue_type;
|
||||||
pub use queue_type::*;
|
pub use queue_type::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod queue;
|
mod queue;
|
||||||
pub use queue::*;
|
pub use queue::*;
|
||||||
|
|
||||||
pub mod ranks;
|
pub mod ranks;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod route;
|
mod route;
|
||||||
pub use route::*;
|
pub use route::*;
|
||||||
|
|
||||||
mod route_ext;
|
mod route_ext;
|
||||||
pub use route_ext::*;
|
pub use route_ext::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod season;
|
mod season;
|
||||||
pub use season::*;
|
pub use season::*;
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ use std::iter::Peekable;
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use super::{ Tier, Division };
|
use super::{Division, Tier};
|
||||||
|
|
||||||
/// (Tier, Division) tuple representing a rank.
|
/// (Tier, Division) tuple representing a rank.
|
||||||
pub type Rank = ( Tier, Division );
|
pub type Rank = (Tier, Division);
|
||||||
|
|
||||||
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
||||||
pub struct Iter {
|
pub struct Iter {
|
||||||
|
@ -20,13 +20,12 @@ impl Iterator for Iter {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// First find the tier (innermost loop).
|
// First find the tier (innermost loop).
|
||||||
// If none found, we go to next tier (in unwrap_or_else case).
|
// If none found, we go to next tier (in unwrap_or_else case).
|
||||||
let div = self.div_iter.next()
|
let div = self.div_iter.next().unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| {
|
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||||
// If no divisions available, go to next tier, reset the divisions, and return I.
|
self.tier_iter.next();
|
||||||
self.tier_iter.next();
|
self.div_iter = Division::iter();
|
||||||
self.div_iter = Division::iter();
|
self.div_iter.next().unwrap()
|
||||||
self.div_iter.next().unwrap()
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Then find the tier.
|
// Then find the tier.
|
||||||
let tier = *self.tier_iter.peek()?;
|
let tier = *self.tier_iter.peek()?;
|
||||||
|
@ -36,7 +35,7 @@ impl Iterator for Iter {
|
||||||
self.div_iter = Division::iter();
|
self.div_iter = Division::iter();
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(( tier, div ))
|
Some((tier, div))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,33 +65,16 @@ pub fn non_apex_iter() -> Iter {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ Tier, Division };
|
use super::{Division, Tier};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn iter() {
|
fn iter() {
|
||||||
let mut it = super::iter();
|
let mut it = super::iter();
|
||||||
assert_eq!(Some(( Tier::CHALLENGER, Division::I )), it.next());
|
assert_eq!(Some((Tier::CHALLENGER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::GRANDMASTER, Division::I )), it.next());
|
assert_eq!(Some((Tier::GRANDMASTER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::MASTER, Division::I )), it.next());
|
assert_eq!(Some((Tier::MASTER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::DIAMOND, Division::I )), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::DIAMOND, Division::II )), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||||
let mut last = None;
|
|
||||||
for next in &mut it {
|
|
||||||
last = Some(next);
|
|
||||||
}
|
|
||||||
assert_eq!(Some(( Tier::IRON, Division::IV )), last);
|
|
||||||
assert_eq!(None, it.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn non_apex_iter() {
|
|
||||||
let mut it = super::non_apex_iter();
|
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
|
||||||
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
for next in &mut it {
|
for next in &mut it {
|
||||||
last = Some(next);
|
last = Some(next);
|
||||||
|
@ -100,4 +82,20 @@ mod tests {
|
||||||
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||||
assert_eq!(None, it.next());
|
assert_eq!(None, it.next());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[test]
|
||||||
|
fn non_apex_iter() {
|
||||||
|
let mut it = super::non_apex_iter();
|
||||||
|
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||||
|
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||||
|
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
||||||
|
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
||||||
|
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
||||||
|
let mut last = None;
|
||||||
|
for next in &mut it {
|
||||||
|
last = Some(next);
|
||||||
|
}
|
||||||
|
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||||
|
assert_eq!(None, it.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use super::{ RegionalRoute, PlatformRoute, ValPlatformRoute };
|
use super::{PlatformRoute, RegionalRoute, ValPlatformRoute};
|
||||||
|
|
||||||
/// Utility enum containing all routing variants.
|
/// Utility enum containing all routing variants.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)]
|
||||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Route {
|
pub enum Route {
|
||||||
|
@ -40,13 +38,13 @@ impl num_enum::TryFromPrimitive for Route {
|
||||||
|
|
||||||
const NAME: &'static str = stringify!(Route);
|
const NAME: &'static str = stringify!(Route);
|
||||||
|
|
||||||
fn try_from_primitive(number: Self::Primitive) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
fn try_from_primitive(
|
||||||
|
number: Self::Primitive,
|
||||||
|
) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
||||||
RegionalRoute::try_from_primitive(number)
|
RegionalRoute::try_from_primitive(number)
|
||||||
.map(Route::Regional)
|
.map(Route::Regional)
|
||||||
.or_else(|_| PlatformRoute::try_from_primitive(number)
|
.or_else(|_| PlatformRoute::try_from_primitive(number).map(Route::Platform))
|
||||||
.map(Route::Platform))
|
.or_else(|_| ValPlatformRoute::try_from_primitive(number).map(Route::ValPlatform))
|
||||||
.or_else(|_| ValPlatformRoute::try_from_primitive(number)
|
|
||||||
.map(Route::ValPlatform))
|
|
||||||
.map_err(|_| num_enum::TryFromPrimitiveError { number })
|
.map_err(|_| num_enum::TryFromPrimitiveError { number })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,10 +71,8 @@ impl std::str::FromStr for Route {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
RegionalRoute::from_str(s)
|
RegionalRoute::from_str(s)
|
||||||
.map(Self::Regional)
|
.map(Self::Regional)
|
||||||
.or_else(|_| PlatformRoute::from_str(s)
|
.or_else(|_| PlatformRoute::from_str(s).map(Self::Platform))
|
||||||
.map(Self::Platform))
|
.or_else(|_| ValPlatformRoute::from_str(s).map(Self::ValPlatform))
|
||||||
.or_else(|_| ValPlatformRoute::from_str(s)
|
|
||||||
.map(Self::ValPlatform))
|
|
||||||
.map_err(|_| strum::ParseError::VariantNotFound)
|
.map_err(|_| strum::ParseError::VariantNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,16 +83,11 @@ impl Route {
|
||||||
pub fn iter() -> impl Iterator<Item = Self> {
|
pub fn iter() -> impl Iterator<Item = Self> {
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
let regional = RegionalRoute::iter()
|
let regional = RegionalRoute::iter().map(Self::Regional);
|
||||||
.map(Self::Regional);
|
let platform = PlatformRoute::iter().map(Self::Platform);
|
||||||
let platform = PlatformRoute::iter()
|
let val_platform = ValPlatformRoute::iter().map(Self::ValPlatform);
|
||||||
.map(Self::Platform);
|
|
||||||
let val_platform = ValPlatformRoute::iter()
|
|
||||||
.map(Self::ValPlatform);
|
|
||||||
|
|
||||||
regional
|
regional.chain(platform).chain(val_platform)
|
||||||
.chain(platform)
|
|
||||||
.chain(val_platform)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,9 +97,18 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_route_tostring() {
|
fn test_route_tostring() {
|
||||||
assert_eq!("AMERICAS", Into::<&'static str>::into(Route::Regional(RegionalRoute::AMERICAS)));
|
assert_eq!(
|
||||||
assert_eq!("KR", Into::<&'static str>::into(Route::Platform(PlatformRoute::KR)));
|
"AMERICAS",
|
||||||
assert_eq!("KR", Into::<&'static str>::into(Route::ValPlatform(ValPlatformRoute::KR)));
|
Into::<&'static str>::into(Route::Regional(RegionalRoute::AMERICAS))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"KR",
|
||||||
|
Into::<&'static str>::into(Route::Platform(PlatformRoute::KR))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"KR",
|
||||||
|
Into::<&'static str>::into(Route::ValPlatform(ValPlatformRoute::KR))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -132,7 +132,10 @@ mod tests {
|
||||||
assert_eq!("AMERICAS", RegionalRoute::AMERICAS.to_string());
|
assert_eq!("AMERICAS", RegionalRoute::AMERICAS.to_string());
|
||||||
assert_eq!("SEA", RegionalRoute::SEA.to_string());
|
assert_eq!("SEA", RegionalRoute::SEA.to_string());
|
||||||
|
|
||||||
assert_eq!("AMERICAS", Into::<&'static str>::into(RegionalRoute::AMERICAS));
|
assert_eq!(
|
||||||
|
"AMERICAS",
|
||||||
|
Into::<&'static str>::into(RegionalRoute::AMERICAS)
|
||||||
|
);
|
||||||
assert_eq!("SEA", Into::<&'static str>::into(RegionalRoute::SEA));
|
assert_eq!("SEA", Into::<&'static str>::into(RegionalRoute::SEA));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +174,10 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!("AP", Into::<&'static str>::into(ValPlatformRoute::AP));
|
assert_eq!("AP", Into::<&'static str>::into(ValPlatformRoute::AP));
|
||||||
assert_eq!("KR", Into::<&'static str>::into(ValPlatformRoute::KR));
|
assert_eq!("KR", Into::<&'static str>::into(ValPlatformRoute::KR));
|
||||||
assert_eq!("ESPORTS", Into::<&'static str>::into(ValPlatformRoute::ESPORTS));
|
assert_eq!(
|
||||||
|
"ESPORTS",
|
||||||
|
Into::<&'static str>::into(ValPlatformRoute::ESPORTS)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
/// League of Legends team.
|
/// League of Legends team.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(
|
||||||
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd)]
|
Debug,
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
Copy,
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
Ord,
|
||||||
|
PartialOrd,
|
||||||
|
Serialize_repr,
|
||||||
|
Deserialize_repr,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum Team {
|
pub enum Team {
|
||||||
/// Blue team (bottom left on Summoner's Rift).
|
/// Blue team (bottom left on Summoner's Rift).
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use serde::{ Serialize, Deserialize };
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
|
|
||||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||||
///
|
///
|
||||||
|
@ -10,36 +10,49 @@ use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
/// Repr'd as arbitrary `u8` values.
|
/// Repr'd as arbitrary `u8` values.
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(
|
||||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
Debug,
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
Copy,
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
Clone,
|
||||||
#[derive(Serialize, Deserialize)]
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Tier {
|
pub enum Tier {
|
||||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||||
CHALLENGER = 220,
|
CHALLENGER = 220,
|
||||||
/// Grand Master, an apex tier. Repr: `200_u8`.
|
/// Grand Master, an apex tier. Repr: `200_u8`.
|
||||||
GRANDMASTER = 200,
|
GRANDMASTER = 200,
|
||||||
/// Master, an apex tier. Repr: `180_u8`.
|
/// Master, an apex tier. Repr: `180_u8`.
|
||||||
MASTER = 180,
|
MASTER = 180,
|
||||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||||
DIAMOND = 140,
|
DIAMOND = 140,
|
||||||
/// Platinum. Repr: `120_u8`.
|
/// Platinum. Repr: `120_u8`.
|
||||||
PLATINUM = 120,
|
PLATINUM = 120,
|
||||||
/// Gold. Repr: `100_u8`.
|
/// Gold. Repr: `100_u8`.
|
||||||
GOLD = 100,
|
GOLD = 100,
|
||||||
/// Silver. Repr: `80_u8`.
|
/// Silver. Repr: `80_u8`.
|
||||||
SILVER = 80,
|
SILVER = 80,
|
||||||
/// Bronze. Repr: `60_u8`.
|
/// Bronze. Repr: `60_u8`.
|
||||||
BRONZE = 60,
|
BRONZE = 60,
|
||||||
/// Iron, the lowest tier. Repr: `40_u8`.
|
/// Iron, the lowest tier. Repr: `40_u8`.
|
||||||
IRON = 40,
|
IRON = 40,
|
||||||
|
|
||||||
/// Unranked, no tier. Repr: `0_u8`.
|
/// Unranked, no tier. Repr: `0_u8`.
|
||||||
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
||||||
#[serde(alias = "NONE")]
|
#[serde(alias = "NONE")]
|
||||||
UNRANKED = 0,
|
UNRANKED = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tier {
|
impl Tier {
|
||||||
|
@ -63,7 +76,7 @@ impl Tier {
|
||||||
|
|
||||||
/// If this tier is ranked. Returns true for iron through challenger, false for unranked.
|
/// If this tier is ranked. Returns true for iron through challenger, false for unranked.
|
||||||
pub const fn is_ranked(self) -> bool {
|
pub const fn is_ranked(self) -> bool {
|
||||||
// Casts needed for const.
|
// Casts needed for const.
|
||||||
(Self::UNRANKED as u8) < (self as u8)
|
(Self::UNRANKED as u8) < (self as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +90,11 @@ impl Tier {
|
||||||
|
|
||||||
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
||||||
pub fn to_ranked(self) -> Option<Self> {
|
pub fn to_ranked(self) -> Option<Self> {
|
||||||
if self.is_unranked() { None } else { Some(self) }
|
if self.is_unranked() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,10 +105,18 @@ impl IntoEnumIterator for Tier {
|
||||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||||
fn iter() -> Self::Iterator {
|
fn iter() -> Self::Iterator {
|
||||||
[
|
[
|
||||||
Self::CHALLENGER, Self::GRANDMASTER, Self::MASTER,
|
Self::CHALLENGER,
|
||||||
Self::DIAMOND, Self::PLATINUM, Self::GOLD,
|
Self::GRANDMASTER,
|
||||||
Self::SILVER, Self::BRONZE, Self::IRON
|
Self::MASTER,
|
||||||
].iter().copied()
|
Self::DIAMOND,
|
||||||
|
Self::PLATINUM,
|
||||||
|
Self::GOLD,
|
||||||
|
Self::SILVER,
|
||||||
|
Self::BRONZE,
|
||||||
|
Self::IRON,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use reqwest::{ Error, Response, StatusCode };
|
use reqwest::{Error, Response, StatusCode};
|
||||||
|
|
||||||
/// Result containing RiotApiError on failure.
|
/// Result containing RiotApiError on failure.
|
||||||
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
||||||
|
@ -17,7 +17,12 @@ pub struct RiotApiError {
|
||||||
status_code: Option<StatusCode>,
|
status_code: Option<StatusCode>,
|
||||||
}
|
}
|
||||||
impl RiotApiError {
|
impl RiotApiError {
|
||||||
pub(crate) fn new(reqwest_error: Error, retries: u8, response: Option<Response>, status_code: Option<StatusCode>) -> Self {
|
pub(crate) fn new(
|
||||||
|
reqwest_error: Error,
|
||||||
|
retries: u8,
|
||||||
|
response: Option<Response>,
|
||||||
|
status_code: Option<StatusCode>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
reqwest_error,
|
reqwest_error,
|
||||||
retries,
|
retries,
|
||||||
|
|
|
@ -19,46 +19,46 @@
|
||||||
//! <!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
//! <!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
||||||
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||||
//! </p>
|
//! </p>
|
||||||
//!
|
//!
|
||||||
//! Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
//! Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
||||||
//!
|
//!
|
||||||
//! Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
//! Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
||||||
//! Data structs and endpoints are automatically generated from the
|
//! Data structs and endpoints are automatically generated from the
|
||||||
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||||
//!
|
//!
|
||||||
//! # Design
|
//! # Design
|
||||||
//!
|
//!
|
||||||
//! * Fast, asynchronous, thread-safe.
|
//! * Fast, asynchronous, thread-safe.
|
||||||
//! * Automatically retries failed requests.
|
//! * Automatically retries failed requests.
|
||||||
//! * Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
//! * Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Usage
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use riven::RiotApi;
|
//! use riven::RiotApi;
|
||||||
//! use riven::consts::PlatformRoute;
|
//! use riven::consts::PlatformRoute;
|
||||||
//!
|
//!
|
||||||
//! // Enter tokio async runtime.
|
//! // Enter tokio async runtime.
|
||||||
//! let rt = tokio::runtime::Runtime::new().unwrap();
|
//! let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
//! rt.block_on(async {
|
//! rt.block_on(async {
|
||||||
//! // Create RiotApi instance from key string.
|
//! // Create RiotApi instance from key string.
|
||||||
//! let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
//! let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||||
//! let riot_api = RiotApi::new(api_key);
|
//! let riot_api = RiotApi::new(api_key);
|
||||||
//!
|
//!
|
||||||
//! // Get summoner data.
|
//! // Get summoner data.
|
||||||
//! let summoner = riot_api.summoner_v4()
|
//! let summoner = riot_api.summoner_v4()
|
||||||
//! .get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
//! .get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||||
//! .expect("Get summoner failed.")
|
//! .expect("Get summoner failed.")
|
||||||
//! .expect("There is no summoner with that name.");
|
//! .expect("There is no summoner with that name.");
|
||||||
//!
|
//!
|
||||||
//! // Print summoner name.
|
//! // Print summoner name.
|
||||||
//! println!("{} Champion Masteries:", summoner.name);
|
//! println!("{} Champion Masteries:", summoner.name);
|
||||||
//!
|
//!
|
||||||
//! // Get champion mastery data.
|
//! // Get champion mastery data.
|
||||||
//! let masteries = riot_api.champion_mastery_v4()
|
//! let masteries = riot_api.champion_mastery_v4()
|
||||||
//! .get_all_champion_masteries(PlatformRoute::NA1, &summoner.id).await
|
//! .get_all_champion_masteries(PlatformRoute::NA1, &summoner.id).await
|
||||||
//! .expect("Get champion masteries failed.");
|
//! .expect("Get champion masteries failed.");
|
||||||
//!
|
//!
|
||||||
//! // Print champion masteries.
|
//! // Print champion masteries.
|
||||||
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||||
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||||
|
@ -85,104 +85,104 @@
|
||||||
//! contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
//! contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
||||||
//! and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/example/proxy)
|
//! and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/example/proxy)
|
||||||
//! provide more example usage.
|
//! provide more example usage.
|
||||||
//!
|
//!
|
||||||
//! ## Feature Flags
|
//! ## Feature Flags
|
||||||
//!
|
//!
|
||||||
//! ### Nightly vs Stable
|
//! ### Nightly vs Stable
|
||||||
//!
|
//!
|
||||||
//! Enable the `nightly` feature to use nightly-only functionality. This enables
|
//! Enable the `nightly` feature to use nightly-only functionality. This enables
|
||||||
//! [nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
//! [nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! riven = { version = "...", features = [ "nightly" ] }
|
//! riven = { version = "...", features = [ "nightly" ] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### rustls
|
//! ### rustls
|
||||||
//!
|
//!
|
||||||
//! Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
//! Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
||||||
//! If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
//! If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
||||||
//! and specifying the `rustls-tls` feature:
|
//! and specifying the `rustls-tls` feature:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
//! riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
//! Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
||||||
//!
|
//!
|
||||||
//! ## Docs
|
//! ## Docs
|
||||||
//!
|
//!
|
||||||
//! [On docs.rs](https://docs.rs/riven/).
|
//! [On docs.rs](https://docs.rs/riven/).
|
||||||
//!
|
//!
|
||||||
//! ## Error Handling
|
//! ## Error Handling
|
||||||
//!
|
//!
|
||||||
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||||
//!
|
//!
|
||||||
//! If the `Result` is errored, this indicates that the API request failed to
|
//! If the `Result` is errored, this indicates that the API request failed to
|
||||||
//! complete successfully, which may be due to bad user input, Riot server errors,
|
//! complete successfully, which may be due to bad user input, Riot server errors,
|
||||||
//! incorrect API key, etc.
|
//! incorrect API key, etc.
|
||||||
//!
|
//!
|
||||||
//! If the `Option` is `None`, this indicates that the request completed
|
//! If the `Option` is `None`, this indicates that the request completed
|
||||||
//! successfully but no data was returned. This happens in several situations, such
|
//! successfully but no data was returned. This happens in several situations, such
|
||||||
//! as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
//! as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
||||||
//! spectator data for a summoner who is not in-game.
|
//! spectator data for a summoner who is not in-game.
|
||||||
//! Specifically, the API returned a 404 HTTP status code in this situation.
|
//! Specifically, the API returned a 404 HTTP status code in this situation.
|
||||||
//!
|
//!
|
||||||
//! The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
//! The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
||||||
//! diagnostic information, such as the source Reqwest error, the number of retries
|
//! diagnostic information, such as the source Reqwest error, the number of retries
|
||||||
//! attempted, and the Reqwest `Response` object.
|
//! attempted, and the Reqwest `Response` object.
|
||||||
//!
|
//!
|
||||||
//! You can configure the number of time Riven retries using
|
//! You can configure the number of time Riven retries using
|
||||||
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
||||||
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||||
//! Some errors, such as 400 client errors, are not retried as they would
|
//! Some errors, such as 400 client errors, are not retried as they would
|
||||||
//! inevitably fail again.
|
//! inevitably fail again.
|
||||||
//!
|
//!
|
||||||
//! ## Semantic Versioning
|
//! ## Semantic Versioning
|
||||||
//!
|
//!
|
||||||
//! This package follows semantic versioning to an extent. However, the Riot API
|
//! This package follows semantic versioning to an extent. However, the Riot API
|
||||||
//! itself changes often and does not follow semantic versioning, which makes
|
//! itself changes often and does not follow semantic versioning, which makes
|
||||||
//! things difficult. Out-of-date versions will slowly partially cease to work due
|
//! things difficult. Out-of-date versions will slowly partially cease to work due
|
||||||
//! to this.
|
//! to this.
|
||||||
//!
|
//!
|
||||||
//! When the API changes, this may result in breaking changes in the `models`
|
//! When the API changes, this may result in breaking changes in the `models`
|
||||||
//! module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
//! module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
||||||
//! methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
//! methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
||||||
//! from the Riot API. These breaking changes will increment the **MINOR** version,
|
//! from the Riot API. These breaking changes will increment the **MINOR** version,
|
||||||
//! not the major version.
|
//! not the major version.
|
||||||
//!
|
//!
|
||||||
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||||
//! versioning.
|
//! versioning.
|
||||||
//!
|
//!
|
||||||
//! ## Additional Help
|
//! ## Additional Help
|
||||||
//!
|
//!
|
||||||
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||||
//! if you are have any questions or trouble with Riven.
|
//! if you are have any questions or trouble with Riven.
|
||||||
//!
|
//!
|
||||||
//! # Development
|
//! # Development
|
||||||
//!
|
//!
|
||||||
//! NodeJS is used to generate code for Riven. The
|
//! NodeJS is used to generate code for Riven. The
|
||||||
//! [`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
//! [`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||||
//! folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
//! folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||||
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||||
//! code.
|
//! code.
|
||||||
//!
|
//!
|
||||||
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||||
//! `riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
//! `riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
||||||
//! dependencies.
|
//! dependencies.
|
||||||
//!
|
//!
|
||||||
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
// Re-exported reqwest types.
|
// Re-exported reqwest types.
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::RiotApiConfig;
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -190,6 +190,7 @@ pub use error::*;
|
||||||
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod models;
|
pub mod models;
|
||||||
mod models_impls;
|
mod models_impls;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::models::match_v5::Participant;
|
|
||||||
use crate::consts::Champion;
|
use crate::consts::Champion;
|
||||||
|
use crate::models::match_v5::Participant;
|
||||||
|
|
||||||
impl Participant {
|
impl Participant {
|
||||||
/// This method takes the [`Self::champion_id`] field if it is valid
|
/// This method takes the [`Self::champion_id`] field if it is valid
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::time::{ Duration, Instant };
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[cfg(not(feature="tracing"))]
|
#[cfg(feature = "tracing")]
|
||||||
use log as log;
|
|
||||||
#[cfg(feature="tracing")]
|
|
||||||
use tracing as log;
|
use tracing as log;
|
||||||
|
|
||||||
use parking_lot::{ RwLock, RwLockUpgradableReadGuard };
|
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||||
use reqwest::{ StatusCode, Response };
|
use reqwest::{Response, StatusCode};
|
||||||
use scan_fmt::scan_fmt;
|
use scan_fmt::scan_fmt;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
use crate::RiotApiConfig;
|
|
||||||
use super::{ TokenBucket, VectorTokenBucket };
|
|
||||||
use super::RateLimitType;
|
use super::RateLimitType;
|
||||||
|
use super::{TokenBucket, VectorTokenBucket};
|
||||||
|
use crate::RiotApiConfig;
|
||||||
|
|
||||||
pub struct RateLimit {
|
pub struct RateLimit {
|
||||||
rate_limit_type: RateLimitType,
|
rate_limit_type: RateLimitType,
|
||||||
|
@ -42,8 +40,8 @@ impl RateLimit {
|
||||||
const HEADER_XRATELIMITTYPE_SERVICE: &'static str = "service";
|
const HEADER_XRATELIMITTYPE_SERVICE: &'static str = "service";
|
||||||
|
|
||||||
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
||||||
let initial_bucket = VectorTokenBucket::new(
|
let initial_bucket =
|
||||||
Duration::from_secs(1), 1, Duration::new(0, 0), 1.0, 1.0);
|
VectorTokenBucket::new(Duration::from_secs(1), 1, Duration::new(0, 0), 1.0, 1.0);
|
||||||
RateLimit {
|
RateLimit {
|
||||||
rate_limit_type,
|
rate_limit_type,
|
||||||
// Rate limit before getting from response: 1/s.
|
// Rate limit before getting from response: 1/s.
|
||||||
|
@ -65,13 +63,19 @@ impl RateLimit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn acquire_both_or_duration(app_rate_limit: &Self, method_rate_limit: &Self) -> Option<Duration> {
|
fn acquire_both_or_duration(
|
||||||
|
app_rate_limit: &Self,
|
||||||
|
method_rate_limit: &Self,
|
||||||
|
) -> Option<Duration> {
|
||||||
// Check retry after.
|
// Check retry after.
|
||||||
{
|
{
|
||||||
let retry_after_delay = app_rate_limit.get_retry_after_delay()
|
let retry_after_delay = app_rate_limit.get_retry_after_delay().and_then(|a| {
|
||||||
.and_then(|a| method_rate_limit.get_retry_after_delay().map(|m| cmp::max(a, m)));
|
method_rate_limit
|
||||||
|
.get_retry_after_delay()
|
||||||
|
.map(|m| cmp::max(a, m))
|
||||||
|
});
|
||||||
if retry_after_delay.is_some() {
|
if retry_after_delay.is_some() {
|
||||||
return retry_after_delay
|
return retry_after_delay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check buckets.
|
// Check buckets.
|
||||||
|
@ -88,12 +92,18 @@ impl RateLimit {
|
||||||
bucket.get_tokens(1);
|
bucket.get_tokens(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Tokens obtained, buckets: APP {:?} METHOD {:?}", app_buckets, method_buckets);
|
log::trace!(
|
||||||
|
"Tokens obtained, buckets: APP {:?} METHOD {:?}",
|
||||||
|
app_buckets,
|
||||||
|
method_buckets
|
||||||
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
||||||
self.retry_after.read().and_then(|i| Instant::now().checked_duration_since(i))
|
self.retry_after
|
||||||
|
.read()
|
||||||
|
.and_then(|i| Instant::now().checked_duration_since(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update retry-after and rate limits based on an API response.
|
/// Update retry-after and rate limits based on an API response.
|
||||||
|
@ -153,13 +163,25 @@ impl RateLimit {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retry after header. Only care if it exists.
|
// Get retry after header. Only care if it exists.
|
||||||
let retry_after_header = response.headers()
|
let retry_after_header = response
|
||||||
|
.headers()
|
||||||
.get(reqwest::header::RETRY_AFTER)
|
.get(reqwest::header::RETRY_AFTER)
|
||||||
.and_then(|h| h
|
.and_then(|h| {
|
||||||
.to_str()
|
h.to_str()
|
||||||
.map_err(|e| log::error!("Failed to read retry-after header as visible ASCII string: {:?}.", e)).ok())?;
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read retry-after header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})?;
|
||||||
|
|
||||||
log::info!("429 response, rate limit {:?}, retry-after {} secs.", self.rate_limit_type, retry_after_header);
|
log::info!(
|
||||||
|
"429 response, rate limit {:?}, retry-after {} secs.",
|
||||||
|
self.rate_limit_type,
|
||||||
|
retry_after_header
|
||||||
|
);
|
||||||
|
|
||||||
// Header currently only returns ints, but float is more general. Can be zero.
|
// Header currently only returns ints, but float is more general. Can be zero.
|
||||||
let retry_after_secs = retry_after_header
|
let retry_after_secs = retry_after_header
|
||||||
|
@ -179,21 +201,42 @@ impl RateLimit {
|
||||||
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
||||||
// Check if rate limits changed.
|
// Check if rate limits changed.
|
||||||
let headers = response.headers();
|
let headers = response.headers();
|
||||||
let limit_header_opt = headers.get(self.rate_limit_type.limit_header())
|
let limit_header_opt = headers
|
||||||
.and_then(|h| h.to_str().map_err(|e| log::error!("Failed to read limit header as visible ASCII string: {:?}.", e)).ok());
|
.get(self.rate_limit_type.limit_header())
|
||||||
let count_header_opt = headers.get(self.rate_limit_type.count_header())
|
.and_then(|h| {
|
||||||
.and_then(|h| h.to_str().map_err(|e| log::error!("Failed to read count header as visible ASCII string: {:?}.", e)).ok());
|
h.to_str()
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read limit header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
let count_header_opt = headers
|
||||||
|
.get(self.rate_limit_type.count_header())
|
||||||
|
.and_then(|h| {
|
||||||
|
h.to_str()
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read count header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
|
||||||
if let (Some(limit_header), Some(count_header)) = (limit_header_opt, count_header_opt) {
|
if let (Some(limit_header), Some(count_header)) = (limit_header_opt, count_header_opt) {
|
||||||
{
|
{
|
||||||
let buckets = self.buckets.upgradable_read();
|
let buckets = self.buckets.upgradable_read();
|
||||||
if !buckets_require_updating(limit_header, &*buckets) {
|
if !buckets_require_updating(limit_header, &buckets) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buckets require updating. Upgrade to write lock.
|
// Buckets require updating. Upgrade to write lock.
|
||||||
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
||||||
*buckets = buckets_from_header(config, limit_header, count_header, self.rate_limit_type);
|
*buckets =
|
||||||
|
buckets_from_header(config, limit_header, count_header, self.rate_limit_type);
|
||||||
}
|
}
|
||||||
// Notify waiters that buckets have updated (after unlocking).
|
// Notify waiters that buckets have updated (after unlocking).
|
||||||
self.update_notify.notify_waiters();
|
self.update_notify.notify_waiters();
|
||||||
|
@ -207,7 +250,11 @@ fn buckets_require_updating(limit_header: &str, buckets: &[VectorTokenBucket]) -
|
||||||
}
|
}
|
||||||
for (limit_header_entry, bucket) in limit_header.split(',').zip(buckets) {
|
for (limit_header_entry, bucket) in limit_header.split(',').zip(buckets) {
|
||||||
// limit_header_entry "100:60" means 100 req per 60 sec.
|
// limit_header_entry "100:60" means 100 req per 60 sec.
|
||||||
let bucket_entry = format!("{}:{}", bucket.get_total_limit(), bucket.get_bucket_duration().as_secs());
|
let bucket_entry = format!(
|
||||||
|
"{}:{}",
|
||||||
|
bucket.get_total_limit(),
|
||||||
|
bucket.get_bucket_duration().as_secs()
|
||||||
|
);
|
||||||
if limit_header_entry != bucket_entry {
|
if limit_header_entry != bucket_entry {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -215,7 +262,12 @@ fn buckets_require_updating(limit_header: &str, buckets: &[VectorTokenBucket]) -
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buckets_from_header(config: &RiotApiConfig, limit_header: &str, count_header: &str, rate_limit_type: RateLimitType) -> Vec<VectorTokenBucket> {
|
fn buckets_from_header(
|
||||||
|
config: &RiotApiConfig,
|
||||||
|
limit_header: &str,
|
||||||
|
count_header: &str,
|
||||||
|
rate_limit_type: RateLimitType,
|
||||||
|
) -> Vec<VectorTokenBucket> {
|
||||||
// Limits: "20000:10,1200000:600"
|
// Limits: "20000:10,1200000:600"
|
||||||
// Counts: "7:10,58:600"
|
// Counts: "7:10,58:600"
|
||||||
let size = limit_header.split(',').count();
|
let size = limit_header.split(',').count();
|
||||||
|
@ -238,11 +290,20 @@ fn buckets_from_header(config: &RiotApiConfig, limit_header: &str, count_header:
|
||||||
let limit_f32 = limit as f32;
|
let limit_f32 = limit as f32;
|
||||||
let scaled_burst_factor = config.burst_factor * limit_f32 / (limit_f32 + 1.0);
|
let scaled_burst_factor = config.burst_factor * limit_f32 / (limit_f32 + 1.0);
|
||||||
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_secs(limit_secs), limit,
|
let bucket = VectorTokenBucket::new(
|
||||||
config.duration_overhead, scaled_burst_factor, rate_usage_factor);
|
Duration::from_secs(limit_secs),
|
||||||
|
limit,
|
||||||
|
config.duration_overhead,
|
||||||
|
scaled_burst_factor,
|
||||||
|
rate_usage_factor,
|
||||||
|
);
|
||||||
bucket.get_tokens(count);
|
bucket.get_tokens(count);
|
||||||
out.push(bucket);
|
out.push(bucket);
|
||||||
}
|
}
|
||||||
log::debug!("Set buckets to {} limit, {} count.", limit_header, count_header);
|
log::debug!(
|
||||||
|
"Set buckets to {} limit, {} count.",
|
||||||
|
limit_header,
|
||||||
|
count_header
|
||||||
|
);
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(not(feature="tracing"))]
|
#[cfg(feature = "tracing")]
|
||||||
use log as log;
|
|
||||||
#[cfg(feature="tracing")]
|
|
||||||
use tracing as log;
|
use tracing as log;
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
use reqwest::{ StatusCode, RequestBuilder };
|
use reqwest::{RequestBuilder, StatusCode};
|
||||||
|
|
||||||
use crate::util::InsertOnlyCHashMap;
|
use crate::util::InsertOnlyCHashMap;
|
||||||
use crate::ResponseInfo;
|
use crate::ResponseInfo;
|
||||||
|
@ -40,19 +38,21 @@ impl RegionalRequester {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute<'a>(self: Arc<Self>,
|
pub fn execute<'a>(
|
||||||
|
self: Arc<Self>,
|
||||||
config: &'a RiotApiConfig,
|
config: &'a RiotApiConfig,
|
||||||
method_id: &'static str, request: RequestBuilder)
|
method_id: &'static str,
|
||||||
-> impl Future<Output = Result<ResponseInfo>> + 'a
|
request: RequestBuilder,
|
||||||
{
|
) -> impl Future<Output = Result<ResponseInfo>> + 'a {
|
||||||
async move {
|
async move {
|
||||||
let mut retries: u8 = 0;
|
let mut retries: u8 = 0;
|
||||||
loop {
|
loop {
|
||||||
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
|
let method_rate_limit: Arc<RateLimit> = self
|
||||||
|
.method_rate_limits
|
||||||
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
||||||
|
|
||||||
// Rate limit.
|
// Rate limit.
|
||||||
let rate_limit = RateLimit::acquire_both(&self.app_rate_limit, &*method_rate_limit);
|
let rate_limit = RateLimit::acquire_both(&self.app_rate_limit, &method_rate_limit);
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let rate_limit = rate_limit.instrument(tracing::info_span!("rate_limit"));
|
let rate_limit = rate_limit.instrument(tracing::info_span!("rate_limit"));
|
||||||
rate_limit.await;
|
rate_limit.await;
|
||||||
|
@ -79,24 +79,40 @@ impl RegionalRequester {
|
||||||
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
||||||
// Success case.
|
// Success case.
|
||||||
if status.is_success() || status_none {
|
if status.is_success() || status_none {
|
||||||
log::trace!("Response {} (retried {} times), success, returning result.", status, retries);
|
log::trace!(
|
||||||
|
"Response {} (retried {} times), success, returning result.",
|
||||||
|
status,
|
||||||
|
retries
|
||||||
|
);
|
||||||
break Ok(ResponseInfo {
|
break Ok(ResponseInfo {
|
||||||
response,
|
response,
|
||||||
retries,
|
retries,
|
||||||
status_none,
|
status_none,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let err = response.error_for_status_ref().err().unwrap_or_else(
|
let err = response.error_for_status_ref().err().unwrap_or_else(|| {
|
||||||
|| panic!("Unhandlable response status code, neither success nor failure: {}.", status));
|
panic!(
|
||||||
|
"Unhandlable response status code, neither success nor failure: {}.",
|
||||||
|
status
|
||||||
|
)
|
||||||
|
});
|
||||||
// Failure, may or may not be retryable.
|
// Failure, may or may not be retryable.
|
||||||
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
||||||
// Retryable: retries remaining, and 429 or 5xx.
|
// Retryable: retries remaining, and 429 or 5xx.
|
||||||
if retries >= config.retries ||
|
if retries >= config.retries
|
||||||
(StatusCode::TOO_MANY_REQUESTS != status
|
|| (StatusCode::TOO_MANY_REQUESTS != status && !status.is_server_error())
|
||||||
&& !status.is_server_error())
|
|
||||||
{
|
{
|
||||||
log::debug!("Response {} (retried {} times), failure, returning error.", status, retries);
|
log::debug!(
|
||||||
break Err(RiotApiError::new(err, retries, Some(response), Some(status)));
|
"Response {} (retried {} times), failure, returning error.",
|
||||||
|
status,
|
||||||
|
retries
|
||||||
|
);
|
||||||
|
break Err(RiotApiError::new(
|
||||||
|
err,
|
||||||
|
retries,
|
||||||
|
Some(response),
|
||||||
|
Some(status),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is retryable, do exponential backoff if retry-after wasn't specified.
|
// Is retryable, do exponential backoff if retry-after wasn't specified.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::fmt;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
@ -58,32 +58,41 @@ pub struct VectorTokenBucket {
|
||||||
/// Limit allowed per burst_duration, for burst factor.
|
/// Limit allowed per burst_duration, for burst factor.
|
||||||
burst_limit: usize,
|
burst_limit: usize,
|
||||||
|
|
||||||
|
|
||||||
/// Record of timestamps (synchronized).
|
/// Record of timestamps (synchronized).
|
||||||
timestamps: Mutex<VecDeque<Instant>>,
|
timestamps: Mutex<VecDeque<Instant>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VectorTokenBucket {
|
impl VectorTokenBucket {
|
||||||
pub fn new(duration: Duration, given_total_limit: usize,
|
pub fn new(
|
||||||
duration_overhead: Duration, burst_factor: f32,
|
duration: Duration,
|
||||||
rate_usage_factor: f32) -> Self
|
given_total_limit: usize,
|
||||||
{
|
duration_overhead: Duration,
|
||||||
debug_assert!(0.0 < rate_usage_factor && rate_usage_factor <= 1.0,
|
burst_factor: f32,
|
||||||
"BAD rate_usage_factor {}.", rate_usage_factor);
|
rate_usage_factor: f32,
|
||||||
debug_assert!(0.0 < burst_factor && burst_factor <= 1.0,
|
) -> Self {
|
||||||
"BAD burst_factor {}.", burst_factor);
|
debug_assert!(
|
||||||
|
0.0 < rate_usage_factor && rate_usage_factor <= 1.0,
|
||||||
|
"BAD rate_usage_factor {}.",
|
||||||
|
rate_usage_factor
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
0.0 < burst_factor && burst_factor <= 1.0,
|
||||||
|
"BAD burst_factor {}.",
|
||||||
|
burst_factor
|
||||||
|
);
|
||||||
// Float ops may lose precision, but nothing should be that precise.
|
// Float ops may lose precision, but nothing should be that precise.
|
||||||
// API always uses round numbers, burst_factor is frac of 256.
|
// API always uses round numbers, burst_factor is frac of 256.
|
||||||
|
|
||||||
// Adjust everything by rate_usage_factor.
|
// Adjust everything by rate_usage_factor.
|
||||||
let total_limit = std::cmp::max(1,
|
let total_limit = std::cmp::max(
|
||||||
(given_total_limit as f32 * rate_usage_factor).floor() as usize);
|
1,
|
||||||
|
(given_total_limit as f32 * rate_usage_factor).floor() as usize,
|
||||||
|
);
|
||||||
|
|
||||||
// Effective duration.
|
// Effective duration.
|
||||||
let d_eff = duration + duration_overhead;
|
let d_eff = duration + duration_overhead;
|
||||||
let burst_duration = d_eff.mul_f32(burst_factor);
|
let burst_duration = d_eff.mul_f32(burst_factor);
|
||||||
let burst_limit = std::cmp::max(1,
|
let burst_limit = std::cmp::max(1, (total_limit as f32 * burst_factor).floor() as usize);
|
||||||
(total_limit as f32 * burst_factor).floor() as usize);
|
|
||||||
debug_assert!(burst_limit <= total_limit);
|
debug_assert!(burst_limit <= total_limit);
|
||||||
|
|
||||||
VectorTokenBucket {
|
VectorTokenBucket {
|
||||||
|
@ -113,21 +122,23 @@ impl VectorTokenBucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenBucket for VectorTokenBucket {
|
impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
fn get_delay(&self) -> Option<Duration> {
|
fn get_delay(&self) -> Option<Duration> {
|
||||||
let timestamps = self.update_get_timestamps();
|
let timestamps = self.update_get_timestamps();
|
||||||
|
|
||||||
// Full rate limit.
|
// Full rate limit.
|
||||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now().checked_duration_since(*ts)
|
Instant::now()
|
||||||
.and_then(|passed_dur| (self.duration + self.duration_overhead)
|
.checked_duration_since(*ts)
|
||||||
.checked_sub(passed_dur))
|
.and_then(|passed_dur| {
|
||||||
|
(self.duration + self.duration_overhead).checked_sub(passed_dur)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// Otherwise burst rate limit.
|
// Otherwise burst rate limit.
|
||||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now().checked_duration_since(*ts)
|
Instant::now()
|
||||||
|
.checked_duration_since(*ts)
|
||||||
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
||||||
}
|
}
|
||||||
// No delay needed.
|
// No delay needed.
|
||||||
|
@ -173,6 +184,12 @@ impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
impl fmt::Debug for VectorTokenBucket {
|
impl fmt::Debug for VectorTokenBucket {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "({}/{}:{})", self.timestamps.lock().len(), self.total_limit, self.duration.as_secs())
|
write!(
|
||||||
|
f,
|
||||||
|
"({}/{}:{})",
|
||||||
|
self.timestamps.lock().len(),
|
||||||
|
self.total_limit,
|
||||||
|
self.duration.as_secs()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(not(feature="tracing"))]
|
#[cfg(feature = "tracing")]
|
||||||
use log as log;
|
|
||||||
#[cfg(feature="tracing")]
|
|
||||||
use tracing as log;
|
use tracing as log;
|
||||||
|
|
||||||
use reqwest::{ Client, RequestBuilder, Method };
|
use reqwest::{Client, Method, RequestBuilder};
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
use crate::ResponseInfo;
|
|
||||||
use crate::RiotApiConfig;
|
|
||||||
use crate::RiotApiError;
|
|
||||||
use crate::req::RegionalRequester;
|
use crate::req::RegionalRequester;
|
||||||
use crate::util::InsertOnlyCHashMap;
|
use crate::util::InsertOnlyCHashMap;
|
||||||
|
use crate::ResponseInfo;
|
||||||
|
use crate::Result;
|
||||||
|
use crate::RiotApiConfig;
|
||||||
|
use crate::RiotApiError;
|
||||||
|
|
||||||
/// For retrieving data from the Riot Games API.
|
/// For retrieving data from the Riot Games API.
|
||||||
///
|
///
|
||||||
|
@ -59,11 +57,15 @@ impl RiotApi {
|
||||||
/// Constructs a new instance from an API key (e.g. `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`) or a [RiotApiConfig].
|
/// Constructs a new instance from an API key (e.g. `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`) or a [RiotApiConfig].
|
||||||
pub fn new(config: impl Into<RiotApiConfig>) -> Self {
|
pub fn new(config: impl Into<RiotApiConfig>) -> Self {
|
||||||
let mut config = config.into();
|
let mut config = config.into();
|
||||||
let client_builder = config.client_builder.take()
|
let client_builder = config
|
||||||
|
.client_builder
|
||||||
|
.take()
|
||||||
.expect("CLIENT_BUILDER IN CONFIG SHOULD NOT BE NONE.");
|
.expect("CLIENT_BUILDER IN CONFIG SHOULD NOT BE NONE.");
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
client: client_builder.build().expect("Failed to create client from builder."),
|
client: client_builder
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create client from builder."),
|
||||||
regional_requesters: InsertOnlyCHashMap::new(),
|
regional_requesters: InsertOnlyCHashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +80,8 @@ impl RiotApi {
|
||||||
/// * `path` - The URL path, appended to the base URL.
|
/// * `path` - The URL path, appended to the base URL.
|
||||||
pub fn request(&self, method: Method, region_platform: &str, path: &str) -> RequestBuilder {
|
pub fn request(&self, method: Method, region_platform: &str, path: &str) -> RequestBuilder {
|
||||||
let base_url_platform = self.config.base_url.replace("{}", region_platform);
|
let base_url_platform = self.config.base_url.replace("{}", region_platform);
|
||||||
self.client.request(method, format!("{}{}", base_url_platform, path))
|
self.client
|
||||||
|
.request(method, format!("{}{}", base_url_platform, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
@ -92,11 +95,15 @@ impl RiotApi {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
|
/// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
|
||||||
pub async fn execute_val<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
pub async fn execute_val<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||||
method_id: &'static str, region_platform: &'static str, request: RequestBuilder)
|
&'a self,
|
||||||
-> Result<T>
|
method_id: &'static str,
|
||||||
{
|
region_platform: &'static str,
|
||||||
let rinfo = self.execute_raw(method_id, region_platform, request).await?;
|
request: RequestBuilder,
|
||||||
|
) -> Result<T> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
let retries = rinfo.retries;
|
let retries = rinfo.retries;
|
||||||
let status = rinfo.response.status();
|
let status = rinfo.response.status();
|
||||||
let value = rinfo.response.json::<T>().await;
|
let value = rinfo.response.json::<T>().await;
|
||||||
|
@ -114,11 +121,15 @@ impl RiotApi {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
|
/// A future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
|
||||||
pub async fn execute_opt<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
pub async fn execute_opt<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||||
method_id: &'static str, region_platform: &'static str, request: RequestBuilder)
|
&'a self,
|
||||||
-> Result<Option<T>>
|
method_id: &'static str,
|
||||||
{
|
region_platform: &'static str,
|
||||||
let rinfo = self.execute_raw(method_id, region_platform, request).await?;
|
request: RequestBuilder,
|
||||||
|
) -> Result<Option<T>> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
if rinfo.status_none {
|
if rinfo.status_none {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -139,14 +150,20 @@ impl RiotApi {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
|
/// A future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
|
||||||
pub async fn execute(&self,
|
pub async fn execute(
|
||||||
method_id: &'static str, region_platform: &'static str, request: RequestBuilder)
|
&self,
|
||||||
-> Result<()>
|
method_id: &'static str,
|
||||||
{
|
region_platform: &'static str,
|
||||||
let rinfo = self.execute_raw(method_id, region_platform, request).await?;
|
request: RequestBuilder,
|
||||||
|
) -> Result<()> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
let retries = rinfo.retries;
|
let retries = rinfo.retries;
|
||||||
let status = rinfo.response.status();
|
let status = rinfo.response.status();
|
||||||
rinfo.response.error_for_status()
|
rinfo
|
||||||
|
.response
|
||||||
|
.error_for_status()
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||||
}
|
}
|
||||||
|
@ -164,18 +181,25 @@ impl RiotApi {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
|
/// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
|
||||||
pub fn execute_raw(&self, method_id: &'static str, region_platform: &'static str, request: RequestBuilder)
|
pub fn execute_raw(
|
||||||
-> impl Future<Output = Result<ResponseInfo>> + '_
|
&self,
|
||||||
{
|
method_id: &'static str,
|
||||||
|
region_platform: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> impl Future<Output = Result<ResponseInfo>> + '_ {
|
||||||
self.regional_requester(region_platform)
|
self.regional_requester(region_platform)
|
||||||
.execute(&self.config, method_id, request)
|
.execute(&self.config, method_id, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get or create the RegionalRequester for the given region.
|
/// Get or create the RegionalRequester for the given region.
|
||||||
fn regional_requester(&self, region_platform: &'static str) -> Arc<RegionalRequester> {
|
fn regional_requester(&self, region_platform: &'static str) -> Arc<RegionalRequester> {
|
||||||
self.regional_requesters.get_or_insert_with(region_platform, || {
|
self.regional_requesters
|
||||||
log::debug!("Creating requester for region platform {}.", region_platform);
|
.get_or_insert_with(region_platform, || {
|
||||||
RegionalRequester::new()
|
log::debug!(
|
||||||
})
|
"Creating requester for region platform {}.",
|
||||||
|
region_platform
|
||||||
|
);
|
||||||
|
RegionalRequester::new()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: Mutex::new(HashMap::new())
|
base: Mutex::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,12 @@ impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V>
|
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V> {
|
||||||
{
|
Arc::clone(
|
||||||
Arc::clone(self.base.lock()
|
self.base
|
||||||
.entry(key)
|
.lock()
|
||||||
.or_insert_with(|| Arc::new(default())))
|
.entry(key)
|
||||||
|
.or_insert_with(|| Arc::new(default())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
{{~ readme :line }}
|
{{~ readme :line }}
|
||||||
//! {{= line }}
|
//!{{= line ? (' ' + line) : '' }}
|
||||||
{{~}}
|
{{~}}
|
||||||
|
|
||||||
// Re-exported reqwest types.
|
// Re-exported reqwest types.
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::RiotApiConfig;
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -26,6 +26,7 @@ pub use error::*;
|
||||||
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod models;
|
pub mod models;
|
||||||
mod models_impls;
|
mod models_impls;
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,13 @@ static MATCHES: &[&str] = &[
|
||||||
"NA1_4052515784",
|
"NA1_4052515784",
|
||||||
"NA1_4062578191",
|
"NA1_4062578191",
|
||||||
"NA1_4097036960",
|
"NA1_4097036960",
|
||||||
|
|
||||||
// New games with `match-v5.ParticipantDto.challenges` field.
|
// New games with `match-v5.ParticipantDto.challenges` field.
|
||||||
"NA1_4209556127",
|
"NA1_4209556127",
|
||||||
"NA1_4212715433",
|
"NA1_4212715433",
|
||||||
"NA1_4265913704", // `match-v5.ParticipantDto.challenges.mejaisFullStackInTime`
|
"NA1_4265913704", // `match-v5.ParticipantDto.challenges.mejaisFullStackInTime`
|
||||||
];
|
];
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
// TODO FAILING since 2022/11/28 https://github.com/MingweiSamuel/Riven/actions/runs/3571320200/jobs/6003088646
|
// TODO FAILING since 2022/11/28 https://github.com/MingweiSamuel/Riven/actions/runs/3571320200/jobs/6003088646
|
||||||
// // Champion Mastery tests.
|
// // Champion Mastery tests.
|
||||||
|
|
|
@ -11,14 +11,14 @@ use riven::consts::*;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
// Champion Mastery tests.
|
// Champion Mastery tests.
|
||||||
championmastery_getscore_ma5tery: async {
|
championmastery_getscore_ma5tery: async {
|
||||||
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||||
let sum = sum.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get summoner".to_owned())?;
|
let sum = sum.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get summoner".to_owned())?;
|
||||||
|
|
||||||
let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score(ROUTE, &*sum.id);
|
let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score(ROUTE, &sum.id);
|
||||||
let s = p.await.map_err(|e| e.to_string())?;
|
let s = p.await.map_err(|e| e.to_string())?;
|
||||||
rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -27,7 +27,7 @@ async_tests!{
|
||||||
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||||
let sum = sum.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get summoner".to_owned())?;
|
let sum = sum.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get summoner".to_owned())?;
|
||||||
|
|
||||||
let p = RIOT_API.champion_mastery_v4().get_all_champion_masteries(ROUTE, &*sum.id);
|
let p = RIOT_API.champion_mastery_v4().get_all_champion_masteries(ROUTE, &sum.id);
|
||||||
let s = p.await.map_err(|e| e.to_string())?;
|
let s = p.await.map_err(|e| e.to_string())?;
|
||||||
rassert!(s.len() >= 142, "Expected masteries: {}.", s.len());
|
rassert!(s.len() >= 142, "Expected masteries: {}.", s.len());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -53,13 +53,13 @@ async_tests! {
|
||||||
tft_combo: async {
|
tft_combo: async {
|
||||||
let top_players = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
let top_players = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||||
let top_players = top_players.await.map_err(|e| e.to_string())?;
|
let top_players = top_players.await.map_err(|e| e.to_string())?;
|
||||||
rassert!(0 < top_players.len());
|
rassert!(!top_players.is_empty());
|
||||||
let top_player_entry = &top_players[0];
|
let top_player_entry = &top_players[0];
|
||||||
let top_player = RIOT_API.tft_summoner_v1().get_by_summoner_id(ROUTE, &*top_player_entry.summoner_id);
|
let top_player = RIOT_API.tft_summoner_v1().get_by_summoner_id(ROUTE, &top_player_entry.summoner_id);
|
||||||
let top_player = top_player.await.map_err(|e| e.to_string())?;
|
let top_player = top_player.await.map_err(|e| e.to_string())?;
|
||||||
println!("Top player is {} with `puuid` {}.", top_player.name, top_player.puuid);
|
println!("Top player is {} with `puuid` {}.", top_player.name, top_player.puuid);
|
||||||
let match_ids = RIOT_API.tft_match_v1().get_match_ids_by_puuid(
|
let match_ids = RIOT_API.tft_match_v1().get_match_ids_by_puuid(
|
||||||
ROUTE.to_regional(), &*top_player.puuid, Some(10), None, None, None);
|
ROUTE.to_regional(), &top_player.puuid, Some(10), None, None, None);
|
||||||
let match_ids = match_ids.await.map_err(|e| e.to_string())?;
|
let match_ids = match_ids.await.map_err(|e| e.to_string())?;
|
||||||
tft_match_v1_get(ROUTE.to_regional(), &*match_ids).await?;
|
tft_match_v1_get(ROUTE.to_regional(), &*match_ids).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -40,7 +40,7 @@ async_tests! {
|
||||||
// Spot check 10% for `player-data`.
|
// Spot check 10% for `player-data`.
|
||||||
for entry in leaderboard.iter().step_by(10)
|
for entry in leaderboard.iter().step_by(10)
|
||||||
{
|
{
|
||||||
let _player_data = RIOT_API.lol_challenges_v1().get_player_data(ROUTE, &*entry.puuid)
|
let _player_data = RIOT_API.lol_challenges_v1().get_player_data(ROUTE, &entry.puuid)
|
||||||
.await.map_err(|e| format!("Failed to get player data PUUID {}: {}", entry.puuid, e))?;
|
.await.map_err(|e| format!("Failed to get player data PUUID {}: {}", entry.puuid, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,18 @@ use riven::models::summoner_v4::*;
|
||||||
fn validate_summoners(s1: Summoner, s2: Summoner) -> Result<(), String> {
|
fn validate_summoners(s1: Summoner, s2: Summoner) -> Result<(), String> {
|
||||||
rassert_eq!(s1.name, s2.name, "Names didn't match {}.", "");
|
rassert_eq!(s1.name, s2.name, "Names didn't match {}.", "");
|
||||||
rassert_eq!(s1.id, s2.id, "SummonerId didn't match {}.", "");
|
rassert_eq!(s1.id, s2.id, "SummonerId didn't match {}.", "");
|
||||||
rassert_eq!(s1.account_id, s2.account_id, "AccountId didn't match {}.", "");
|
rassert_eq!(
|
||||||
|
s1.account_id,
|
||||||
|
s2.account_id,
|
||||||
|
"AccountId didn't match {}.",
|
||||||
|
""
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::NA1;
|
const ROUTE: PlatformRoute = PlatformRoute::NA1;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
// Summoner tests.
|
// Summoner tests.
|
||||||
summoner_double: async {
|
summoner_double: async {
|
||||||
|
@ -53,10 +58,10 @@ async_tests!{
|
||||||
// summoner must have double-up rank.
|
// summoner must have double-up rank.
|
||||||
league_getforsummoner_tftbug: async {
|
league_getforsummoner_tftbug: async {
|
||||||
// TODO(mingwei): get summoner from leaderboard to avoid updating this all the time.
|
// TODO(mingwei): get summoner from leaderboard to avoid updating this all the time.
|
||||||
const SUMMONER_NAME: &'static str = "Vincentscc";
|
const SUMMONER_NAME: &str = "Vincentscc";
|
||||||
let summoner_fut = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, SUMMONER_NAME);
|
let summoner_fut = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, SUMMONER_NAME);
|
||||||
let summoner = summoner_fut.await.map_err(|e| e.to_string())?.ok_or_else(|| format!("Failed to get \"{}\"", SUMMONER_NAME))?;
|
let summoner = summoner_fut.await.map_err(|e| e.to_string())?.ok_or_else(|| format!("Failed to get \"{}\"", SUMMONER_NAME))?;
|
||||||
let league_fut = RIOT_API.league_v4().get_league_entries_for_summoner(ROUTE, &*summoner.id);
|
let league_fut = RIOT_API.league_v4().get_league_entries_for_summoner(ROUTE, &summoner.id);
|
||||||
let leagues = league_fut.await.map_err(|e| e.to_string())?;
|
let leagues = league_fut.await.map_err(|e| e.to_string())?;
|
||||||
let tft_league = leagues.iter().find(|league| QueueType::RANKED_TFT_DOUBLE_UP == league.queue_type);
|
let tft_league = leagues.iter().find(|league| QueueType::RANKED_TFT_DOUBLE_UP == league.queue_type);
|
||||||
rassert!(tft_league.is_some());
|
rassert!(tft_league.is_some());
|
||||||
|
|
|
@ -11,7 +11,7 @@ use riven::consts::*;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::PH2;
|
const ROUTE: PlatformRoute = PlatformRoute::PH2;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
status: async {
|
status: async {
|
||||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use riven::consts::*;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::SG2;
|
const ROUTE: PlatformRoute = PlatformRoute::SG2;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
status: async {
|
status: async {
|
||||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use riven::consts::*;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::TH2;
|
const ROUTE: PlatformRoute = PlatformRoute::TH2;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
status: async {
|
status: async {
|
||||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
|
|
@ -12,8 +12,7 @@ use riven::models::summoner_v4::Summoner;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::TR1;
|
const ROUTE: PlatformRoute = PlatformRoute::TR1;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
async_tests!{
|
|
||||||
my_runner {
|
my_runner {
|
||||||
league_summoner_bulk_test: async {
|
league_summoner_bulk_test: async {
|
||||||
let p = RIOT_API.league_v4().get_challenger_league(ROUTE, QueueType::RANKED_SOLO_5x5);
|
let p = RIOT_API.league_v4().get_challenger_league(ROUTE, QueueType::RANKED_SOLO_5x5);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use riven::consts::*;
|
||||||
|
|
||||||
const ROUTE: PlatformRoute = PlatformRoute::VN2;
|
const ROUTE: PlatformRoute = PlatformRoute::VN2;
|
||||||
|
|
||||||
async_tests!{
|
async_tests! {
|
||||||
my_runner {
|
my_runner {
|
||||||
status: async {
|
status: async {
|
||||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub async fn league_v4_match_v5_latest_combo(route: PlatformRoute) -> Result<(),
|
||||||
.get_challenger_league(route, QueueType::RANKED_SOLO_5x5);
|
.get_challenger_league(route, QueueType::RANKED_SOLO_5x5);
|
||||||
let challenger_league = challenger_future.await.map_err(|e| e.to_string())?;
|
let challenger_league = challenger_future.await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
if &QueueType::RANKED_SOLO_5x5 != &challenger_league.queue {
|
if QueueType::RANKED_SOLO_5x5 != challenger_league.queue {
|
||||||
return Err(format!("Unexpected `queue`: {}", challenger_league.queue));
|
return Err(format!("Unexpected `queue`: {}", challenger_league.queue));
|
||||||
}
|
}
|
||||||
if challenger_league.entries.is_empty() {
|
if challenger_league.entries.is_empty() {
|
||||||
|
@ -42,7 +42,7 @@ pub async fn league_v4_match_v5_latest_combo(route: PlatformRoute) -> Result<(),
|
||||||
|
|
||||||
let match_ids_future = RIOT_API.match_v5().get_match_ids_by_puuid(
|
let match_ids_future = RIOT_API.match_v5().get_match_ids_by_puuid(
|
||||||
route.to_regional(),
|
route.to_regional(),
|
||||||
&*summoner_info.puuid,
|
&summoner_info.puuid,
|
||||||
Some(5),
|
Some(5),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
Loading…
Reference in New Issue