diff --git a/src/config.rs b/src/config.rs index 88a68c4..1587bf0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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>(api_key: T) -> Self { + pub fn with_key>(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 - } } diff --git a/src/req/regional_requester.rs b/src/req/regional_requester.rs index 54632ba..1820c6d 100644 --- a/src/req/regional_requester.rs +++ b/src/req/regional_requester.rs @@ -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, + pub fn execute_raw<'a>(self: Arc, config: &'a RiotApiConfig, client: &'a Client, - method_id: &'static str, region_platform: &'a str, path: String, query: Option) + method_id: &'static str, request: Request) -> impl Future> + '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 = 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). diff --git a/src/riot_api.rs b/src/riot_api.rs index 5a9b0a3..d47b773 100644 --- a/src/riot_api.rs +++ b/src/riot_api.rs @@ -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>(api_key: T) -> Self { + pub fn with_key>(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) - -> impl Future> + 'a + -> impl Future> + '_ + { + 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> + { + 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::>().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 + { + 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::().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> + '_ { 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.