1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::future::Future;
use std::sync::Arc;

use log;
use reqwest::Client;

use crate::Result;
use crate::ResponseInfo;
use crate::RiotApiConfig;
use crate::RiotApiError;
use crate::req::RegionalRequester;
use crate::util::InsertOnlyCHashMap;

/// For retrieving data from the Riot Games API.
///
/// # Usage
///
/// Construct an instance using [`with_key(api_key)`](RiotApi::with_key) or
/// [`with_config(config)`](RiotApi::with_config).
///
/// An instance provides access to "endpoint handles" which in turn provide access
/// to individual API method calls. For example, getting a summoner by name:
/// ```ignore
/// riot_api.summoner_v4().get_by_summoner_name(Region::NA, "LugnutsK")
/// ```
///
/// # Rate Limiting
///
/// The Riot Game API enforces _dynamic_ rate limiting, meaning that rate limits are
/// specified in response headers and (theoretically) could change at any time.
/// Riven keeps track of changing rate limits seamlessly, preventing you from
/// getting blacklisted.
///
/// Riven's rate limiting is highly efficient, meaning that it can reach the limits
/// of your rate limit without going over.
///
/// To adjust rate limiting, see [RiotApiConfig](crate::RiotApiConfig) and use
/// [`with_config(config)`](RiotApi::with_config) to construct an instance.
pub struct RiotApi {
    /// Configuration settings.
    config: RiotApiConfig,
    /// Client for making requests.
    client: Client,

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

impl RiotApi {
    /// Constructs a new instance from the given [RiotApiConfig](crate::RiotApiConfig), consuming it.
    pub fn with_config(mut config: RiotApiConfig) -> Self {
        let client_builder = config.client_builder.take()
            .expect("!NONE CLIENT_BUILDER IN CONFIG.");
        Self {
            config: config,
            client: client_builder.build().expect("Failed to create client from builder."),
            regional_requesters: InsertOnlyCHashMap::new(),
        }
    }

    /// Constructs a new instance from the given API key, using default configuration.
    ///
    /// `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 {
        Self::with_config(RiotApiConfig::with_key(api_key))
    }

    /// This method is not meant to be used directly.
    ///
    /// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
    /// * `path` - The path relative to the hostname.
    /// * `query` - An optional query string.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either a `Option<T>` (success) or a `RiotApiError` (failure).
    pub async fn get_optional<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
        method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
        -> Result<Option<T>>
    {
        let rinfo = self.get_raw_response(method_id, region_platform, path, query).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)))
    }

    /// This method is not meant to be used directly.
    ///
    /// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
    /// * `path` - The path relative to the hostname.
    /// * `query` - An optional query string.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
    pub async fn get<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
        method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
        -> Result<T>
    {
        let rinfo = self.get_raw_response(method_id, region_platform, path, query).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)))
    }

    /// This method is not meant to be used directly.
    ///
    /// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
    /// * `path` - The path relative to the hostname.
    /// * `query` - An optional query string.
    ///
    /// # Returns
    /// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
    pub fn get_raw_response<'a>(&'a self,
        method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
        -> impl Future<Output = Result<ResponseInfo>> + 'a
    {
        self.regional_requester(region_platform)
            .get(&self.config, &self.client, method_id, region_platform, path, query)
    }

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