riven 2.78.0

Riot Games API Library
Documentation
use core::panic;
use std::future::Future;

use memo_map::MemoMap;
use reqwest::{Client, Method, RequestBuilder};
#[cfg(feature = "tracing")]
use tracing as log;

use crate::req::RegionalRequester;
use crate::{ResponseInfo, Result, RiotApiConfig, RiotApiError, TryRequestError, TryRequestResult};

/// For retrieving data from the Riot Games API.
///
/// # Usage
///
/// Construct an instance using [`RiotApi::new(api_key or config)`](RiotApi::new).
/// The parameter may be a Riot API key string or a [`RiotApiConfig`]. Riot API
/// keys are obtained from the [Riot Developer Portal](https://developer.riotgames.com/)
/// and look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
///
/// An instance provides access to "endpoint handles" which in turn provide
/// access to individual API method calls. For example, to get a user by Riot
/// ID we first access the [`account_v1()`](RiotApi::account_v1) endpoints
/// then call the [`get_by_riot_id()`](crate::endpoints::AccountV1::get_by_riot_id)
/// method:
/// ```ignore
/// riot_api.account_v1().get_by_riot_id(Region::NA, "LugnutsK", "000")
/// ```
///
/// # Rate Limiting
///
/// The Riot Game API enforces _dynamic_ rate limiting, meaning that rate limits are
/// specified in response headers and can change at any time.
/// Riven keeps track of changing rate limits seamlessly, preventing you from
/// getting blacklisted.
///
/// Riven's rate limiting is highly efficient; it can use the full throughput
/// of your rate limit without triggering 429 errors.
///
/// To adjust rate limiting, see [RiotApiConfig] and use
/// [`RiotApi::new(config)`](RiotApi::new) to construct an instance.
pub struct RiotApi {
    /// Configuration settings.
    config: RiotApiConfig,
    /// Client for making requests.
    client: Client,

    /// Per-region requesters.
    regional_requesters: MemoMap<&'static str, RegionalRequester>,
}

impl RiotApi {
    /// 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 {
        let mut config = config.into();
        let client_builder = config
            .client_builder
            .take()
            .expect("CLIENT_BUILDER IN CONFIG SHOULD NOT BE NONE.");
        Self {
            config,
            client: client_builder
                .build()
                .expect("Failed to create client from builder."),
            regional_requesters: MemoMap::new(),
        }
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// Creates a `RequestBuilder` instance with the given parameters, for use with the `execute*()` methods.
    ///
    /// # Parameters
    /// * `method` - The HTTP method for this request.
    /// * `region_platform` - The stringified platform, used to create the base URL.
    /// * `path` - The URL path, appended to the base URL.
    pub fn request(&self, method: Method, region_platform: &str, path: &str) -> RequestBuilder {
        let base_url_platform = self.config.base_url.replace("{}", region_platform);
        self.client
            .request(method, format!("{}{}", base_url_platform, path))
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters and returns a parsed result.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
    pub async fn execute_val<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
        &'a self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
    ) -> Result<T> {
        let rinfo = self
            .execute_raw(method_id, region_platform, request)
            .await?;
        rinfo.json::<T>().await
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters and returns an optional parsed result.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
    pub async fn execute_opt<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
        &'a self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
    ) -> Result<Option<T>> {
        let rinfo = self
            .execute_raw(method_id, region_platform, request)
            .await?;
        if rinfo.status_none {
            return Ok(None);
        }
        rinfo.json::<Option<T>>().await
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters but does not deserialize any response body.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
    pub async fn execute(
        &self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
    ) -> Result<()> {
        let rinfo = self
            .execute_raw(method_id, region_platform, request)
            .await?;
        let retries = rinfo.retries;
        let status = rinfo.response.status();
        if status.is_client_error() || status.is_server_error() {
            Err(RiotApiError::new(
                rinfo.reqwest_errors,
                None,
                retries,
                Some(rinfo.response),
                Some(status),
            ))
        } else {
            Ok(())
        }
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters and returns a parsed result.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    /// * `min_capacity` - Minimum capacity required as a float from 1.0 (all capacity) to 0.0 (no capacity) excluding burst
    ///
    /// # Returns
    /// None if min_capacity is not met, otherwise a future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
    pub async fn try_execute_val<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
        &'a self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
        min_capacity: f32,
    ) -> TryRequestResult<T> {
        let rinfo = self
            .try_execute_raw(method_id, region_platform, request, min_capacity)
            .await?;
        Ok(rinfo.json::<T>().await?)
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters and returns an optional parsed result.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    /// * `min_capacity` - Minimum capacity required as a float from 1.0 (all capacity) to 0.0 (no capacity) excluding burst
    ///
    /// # Returns
    /// None if min_capacity is not met, otherwise a future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
    pub async fn try_execute_opt<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
        &'a self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
        min_capacity: f32,
    ) -> TryRequestResult<Option<T>> {
        let rinfo = self
            .try_execute_raw(method_id, region_platform, request, min_capacity)
            .await?;
        if rinfo.status_none {
            return Ok(None);
        }
        Ok(rinfo.json::<Option<T>>().await?)
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters but does not deserialize any response body.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    ///
    /// # Returns
    /// None if min_capacity is not met, otherwise a future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
    pub async fn try_execute(
        &self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
        min_capacity: f32,
    ) -> TryRequestResult<()> {
        let rinfo = self
            .try_execute_raw(method_id, region_platform, request, min_capacity)
            .await?;
        let retries = rinfo.retries;
        let status = rinfo.response.status();
        if status.is_client_error() || status.is_server_error() {
            Err(TryRequestError::RiotApiError(RiotApiError::new(
                rinfo.reqwest_errors,
                None,
                retries,
                Some(rinfo.response),
                Some(status),
            )))
        } else {
            Ok(())
        }
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// This sends a request based on the given parameters and returns a raw `ResponseInfo`.
    ///
    /// This can be used to implement a Riot API proxy without needing to deserialize and reserialize JSON responses.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
    pub async fn execute_raw(
        &self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
    ) -> Result<ResponseInfo> {
        self.regional_requester(region_platform)
            .execute(&self.config, method_id, request, None)
            .await
            .map_err(|e| match e {
                TryRequestError::NotEnoughCapacity => {
                    panic!("execute called with no capacity requirement, should not happen.")
                }
                TryRequestError::RiotApiError(e) => e,
            })
    }

    /// This method should generally not be used directly. Consider using endpoint wrappers instead.
    ///
    /// A variabion of `execute_raw` that allows for a minimum capacity to be specified for load shedding.
    ///
    /// # Parameters
    /// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
    /// * `region_platform` - The stringified platform, used in rate limiting.
    /// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
    /// * `min_capacity` - Minimum capacity required as a float from 1.0 (all capacity) to 0.0 (no capacity) excluding burst
    ///
    /// # Returns
    /// None if there is not enough capacity to make the request, otherwise a future resolving to a `Result` containg
    /// either a `ResponseInfo` (success) or a `RiotApiError` (failure).
    pub fn try_execute_raw(
        &self,
        method_id: &'static str,
        region_platform: &'static str,
        request: RequestBuilder,
        min_capacity: f32,
    ) -> impl Future<Output = TryRequestResult<ResponseInfo>> + '_ {
        self.regional_requester(region_platform).execute(
            &self.config,
            method_id,
            request,
            Some(min_capacity),
        )
    }

    /// Gets the [`RiotApiConfig::rso_clear_header`] for use in RSO endpoints.
    pub(crate) fn get_rso_clear_header(&self) -> Option<&str> {
        self.config.rso_clear_header.as_deref()
    }

    /// Get or create the RegionalRequester for the given region.
    fn regional_requester(&self, region_platform: &'static str) -> &RegionalRequester {
        self.regional_requesters
            .get_or_insert(&region_platform, || {
                log::debug!(
                    "Creating requester for region platform {}.",
                    region_platform
                );
                RegionalRequester::new()
            })
    }
}