Adding execution of arbitrary Reequests

pull/27/head
Mingwei Samuel 2021-05-21 15:35:31 -07:00
parent 869216aab5
commit 7f046e99f7
3 changed files with 101 additions and 46 deletions

View File

@ -2,13 +2,13 @@
use std::time::Duration; use std::time::Duration;
use reqwest::ClientBuilder; use reqwest::ClientBuilder;
use reqwest::header::{HeaderMap, HeaderValue};
/// Configuration for instantiating RiotApi. /// Configuration for instantiating RiotApi.
/// ///
/// ///
#[derive(Debug)] #[derive(Debug)]
pub struct RiotApiConfig { pub struct RiotApiConfig {
pub(crate) api_key: String,
pub(crate) retries: u8, pub(crate) retries: u8,
pub(crate) burst_pct: f32, pub(crate) burst_pct: f32,
pub(crate) duration_overhead: Duration, pub(crate) duration_overhead: Duration,
@ -16,14 +16,25 @@ pub struct RiotApiConfig {
} }
impl RiotApiConfig { impl RiotApiConfig {
/// Request header name for the Riot API key.
///
/// When using `set_client_builder`, the supplied builder should include
/// this default header with the Riot API key as the value.
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
/// `3`
///
/// Default number of retries.
pub const PRECONFIG_RETRIES: u8 = 3;
/// `0.99` /// `0.99`
/// ///
/// `burst_pct` used by `preconfig_burst` (and default `with_key`). /// Default `burst_pct`, also used by `preconfig_burst`.
pub const PRECONFIG_BURST_BURST_PCT: f32 = 0.99; pub const PRECONFIG_BURST_BURST_PCT: f32 = 0.99;
/// `989` ms /// `989` ms
/// ///
/// `duration_overhead` used by `preconfig_burst` (and default `with_key`). /// Default `duration_overhead`, also used by `preconfig_burst`.
pub const PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS: u64 = 989; pub const PRECONFIG_BURST_DURATION_OVERHEAD: Duration = Duration::from_millis(989);
/// `0.47` /// `0.47`
/// ///
@ -32,25 +43,50 @@ impl RiotApiConfig {
/// `10` ms. /// `10` ms.
/// ///
/// `duration_overhead` used by `preconfig_throughput`. /// `duration_overhead` used by `preconfig_throughput`.
pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS: u64 = 10; pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD: Duration = Duration::from_millis(10);
/// Creates a new `RiotApiConfig` with the given `api_key` with the following /// Creates a new `RiotApiConfig` with the given `api_key` with the following
/// configuration: /// configuration:
/// ///
/// * `retries = 3`. /// * `retries = 3` (`RiotApiConfig::PRECONFIG_RETRIES`).
/// * `purst_pct = 0.99` (`preconfig_burst`). /// * `purst_pct = 0.99` (`preconfig_burst`).
/// * `duration_overhead = 989 ms` (`preconfig_burst`). /// * `duration_overhead = 989 ms` (`preconfig_burst`).
/// ///
/// `api_key` should be a Riot Games API key from /// `api_key` should be a Riot Games API key from
/// [https://developer.riotgames.com/](https://developer.riotgames.com/), /// [https://developer.riotgames.com/](https://developer.riotgames.com/),
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`. /// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
pub fn with_key<T: Into<String>>(api_key: T) -> Self { pub fn with_key<T: AsRef<[u8]>>(api_key: T) -> Self {
let mut default_headers = HeaderMap::new();
default_headers.insert(
Self::RIOT_KEY_HEADER,
HeaderValue::from_bytes(api_key.as_ref()).unwrap()
);
Self { Self {
api_key: api_key.into(), retries: Self::PRECONFIG_RETRIES,
retries: 3,
burst_pct: Self::PRECONFIG_BURST_BURST_PCT, burst_pct: Self::PRECONFIG_BURST_BURST_PCT,
duration_overhead: Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS), duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
client_builder: Some(ClientBuilder::new()), client_builder: Some(
ClientBuilder::new()
.default_headers(default_headers)
),
}
}
/// Creates a new `RiotApiConfig` with the given client builder.
///
/// The client builder default headers should include a value for
/// `RiotApiConfig::RIOT_KEY_HEADER`, otherwise authentication will fail.
///
/// * `retries = 3` (`RiotApiConfig::PRECONFIG_RETRIES`).
/// * `purst_pct = 0.99` (`preconfig_burst`).
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
pub fn with_client_builder(client_builder: ClientBuilder) -> Self {
Self {
retries: Self::PRECONFIG_RETRIES,
burst_pct: Self::PRECONFIG_BURST_BURST_PCT,
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
client_builder: Some(client_builder),
} }
} }
@ -64,7 +100,7 @@ impl RiotApiConfig {
/// `self`, for chaining. /// `self`, for chaining.
pub fn preconfig_burst(mut self) -> Self { pub fn preconfig_burst(mut self) -> Self {
self.burst_pct = Self::PRECONFIG_BURST_BURST_PCT; self.burst_pct = Self::PRECONFIG_BURST_BURST_PCT;
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS); self.duration_overhead = Self::PRECONFIG_BURST_DURATION_OVERHEAD;
self self
} }
@ -78,7 +114,7 @@ impl RiotApiConfig {
/// `self`, for chaining. /// `self`, for chaining.
pub fn preconfig_throughput(mut self) -> Self { pub fn preconfig_throughput(mut self) -> Self {
self.burst_pct = Self::PRECONFIG_THROUGHPUT_BURST_PCT; self.burst_pct = Self::PRECONFIG_THROUGHPUT_BURST_PCT;
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS); self.duration_overhead = Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD;
self self
} }
@ -155,13 +191,4 @@ impl RiotApiConfig {
self.duration_overhead = duration_overhead; self.duration_overhead = duration_overhead;
self self
} }
/// Sets the reqwest `ClientBuilder`.
///
/// # Returns
/// `self`, for chaining.
pub fn set_client_builder(mut self, client_builder: ClientBuilder) -> Self {
self.client_builder = Some(client_builder);
self
}
} }

View File

@ -2,7 +2,7 @@ use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use log; use log;
use reqwest::{ Client, StatusCode, Url }; use reqwest::{Client, StatusCode, Request};
use crate::Result; use crate::Result;
use crate::ResponseInfo; use crate::ResponseInfo;
@ -21,9 +21,6 @@ pub struct RegionalRequester {
} }
impl RegionalRequester { impl RegionalRequester {
/// Request header name for the Riot API key.
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
/// HTTP status codes which are considered a success but will results in `None`. /// HTTP status codes which are considered a success but will results in `None`.
const NONE_STATUS_CODES: [StatusCode; 2] = [ const NONE_STATUS_CODES: [StatusCode; 2] = [
StatusCode::NO_CONTENT, // 204 StatusCode::NO_CONTENT, // 204
@ -37,15 +34,12 @@ impl RegionalRequester {
} }
} }
pub fn get<'a>(self: Arc<Self>, pub fn execute_raw<'a>(self: Arc<Self>,
config: &'a RiotApiConfig, client: &'a Client, config: &'a RiotApiConfig, client: &'a Client,
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>) method_id: &'static str, request: Request)
-> impl Future<Output = Result<ResponseInfo>> + 'a -> impl Future<Output = Result<ResponseInfo>> + 'a
{ {
async move { async move {
#[cfg(feature = "nightly")] let query = query.as_deref();
#[cfg(not(feature = "nightly"))] let query = query.as_ref().map(|s| s.as_ref());
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
@ -57,16 +51,8 @@ impl RegionalRequester {
} }
// Send request. // Send request.
let url_base = format!("https://{}.api.riotgames.com", region_platform); let request_clone = request.try_clone().expect("Failed to clone request.");
let mut url = Url::parse(&*url_base) let response = client.execute(request_clone).await
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
url.set_path(&*path);
url.set_query(query);
let response = client.get(url)
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
.send()
.await
.map_err(|e| RiotApiError::new(e, retries, None, None))?; .map_err(|e| RiotApiError::new(e, retries, None, None))?;
// Maybe update rate limits (based on response headers). // Maybe update rate limits (based on response headers).

View File

@ -2,7 +2,7 @@ use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use log; use log;
use reqwest::Client; use reqwest::{Client, Request, Method, Url};
use crate::Result; use crate::Result;
use crate::ResponseInfo; use crate::ResponseInfo;
@ -63,7 +63,7 @@ impl RiotApi {
/// `api_key` should be a Riot Games API key from /// `api_key` should be a Riot Games API key from
/// [https://developer.riotgames.com/](https://developer.riotgames.com/), /// [https://developer.riotgames.com/](https://developer.riotgames.com/),
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`. /// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
pub fn with_key<T: Into<String>>(api_key: T) -> Self { pub fn with_key<T: AsRef<[u8]>>(api_key: T) -> Self {
Self::with_config(RiotApiConfig::with_key(api_key)) Self::with_config(RiotApiConfig::with_key(api_key))
} }
@ -130,12 +130,54 @@ 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 get_raw_response<'a>(&'a self, pub fn get_raw_response(&self,
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>) method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
-> impl Future<Output = Result<ResponseInfo>> + 'a -> impl Future<Output = Result<ResponseInfo>> + '_
{
let url_base = format!("https://{}.api.riotgames.com", region_platform);
let mut url = Url::parse(&*url_base)
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
url.set_path(&*path);
url.set_query(query.as_deref());
let request = Request::new(Method::GET, url);
self.execute_raw(method_id, region_platform, request)
}
pub async fn execute_optional<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
method_id: &'static str, region_platform: &'static str, request: Request)
-> Result<Option<T>>
{
let rinfo = self.execute_raw(method_id, region_platform, request).await?;
if rinfo.status_none {
return Ok(None);
}
let retries = rinfo.retries;
let status = rinfo.response.status();
let value = rinfo.response.json::<Option<T>>().await;
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
}
pub async fn execute<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
method_id: &'static str, region_platform: &'static str, request: Request)
-> Result<T>
{
let rinfo = self.execute_raw(method_id, region_platform, request).await?;
let retries = rinfo.retries;
let status = rinfo.response.status();
let value = rinfo.response.json::<T>().await;
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
}
pub fn execute_raw(&self, method_id: &'static str, region_platform: &'static str, request: Request)
-> impl Future<Output = Result<ResponseInfo>> + '_
{ {
self.regional_requester(region_platform) self.regional_requester(region_platform)
.get(&self.config, &self.client, method_id, region_platform, path, query) .execute_raw(&self.config, &self.client, method_id, request)
} }
/// Get or create the RegionalRequester for the given region. /// Get or create the RegionalRequester for the given region.