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 reqwest::ClientBuilder;
use reqwest::header::{HeaderMap, HeaderValue};
/// Configuration for instantiating RiotApi.
///
///
#[derive(Debug)]
pub struct RiotApiConfig {
pub(crate) api_key: String,
pub(crate) retries: u8,
pub(crate) burst_pct: f32,
pub(crate) duration_overhead: Duration,
@ -16,14 +16,25 @@ pub struct 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`
///
/// `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;
/// `989` ms
///
/// `duration_overhead` used by `preconfig_burst` (and default `with_key`).
pub const PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS: u64 = 989;
/// Default `duration_overhead`, also used by `preconfig_burst`.
pub const PRECONFIG_BURST_DURATION_OVERHEAD: Duration = Duration::from_millis(989);
/// `0.47`
///
@ -32,25 +43,50 @@ impl RiotApiConfig {
/// `10` ms.
///
/// `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
/// configuration:
///
/// * `retries = 3`.
/// * `retries = 3` (`RiotApiConfig::PRECONFIG_RETRIES`).
/// * `purst_pct = 0.99` (`preconfig_burst`).
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
///
/// `api_key` should be a Riot Games API key from
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
/// 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 {
api_key: api_key.into(),
retries: 3,
retries: Self::PRECONFIG_RETRIES,
burst_pct: Self::PRECONFIG_BURST_BURST_PCT,
duration_overhead: Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS),
client_builder: Some(ClientBuilder::new()),
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
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.
pub fn preconfig_burst(mut self) -> Self {
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
}
@ -78,7 +114,7 @@ impl RiotApiConfig {
/// `self`, for chaining.
pub fn preconfig_throughput(mut self) -> Self {
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
}
@ -155,13 +191,4 @@ impl RiotApiConfig {
self.duration_overhead = duration_overhead;
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 log;
use reqwest::{ Client, StatusCode, Url };
use reqwest::{Client, StatusCode, Request};
use crate::Result;
use crate::ResponseInfo;
@ -21,9 +21,6 @@ pub struct 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`.
const NONE_STATUS_CODES: [StatusCode; 2] = [
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,
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
{
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;
loop {
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
@ -57,16 +51,8 @@ impl RegionalRequester {
}
// Send request.
let url_base = format!("https://{}.api.riotgames.com", region_platform);
let mut url = Url::parse(&*url_base)
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
url.set_path(&*path);
url.set_query(query);
let response = client.get(url)
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
.send()
.await
let request_clone = request.try_clone().expect("Failed to clone request.");
let response = client.execute(request_clone).await
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
// Maybe update rate limits (based on response headers).

View File

@ -2,7 +2,7 @@ use std::future::Future;
use std::sync::Arc;
use log;
use reqwest::Client;
use reqwest::{Client, Request, Method, Url};
use crate::Result;
use crate::ResponseInfo;
@ -63,7 +63,7 @@ impl RiotApi {
/// `api_key` should be a Riot Games API key from
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
/// 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))
}
@ -130,12 +130,54 @@ impl RiotApi {
///
/// # Returns
/// 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>)
-> 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)
.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.