Skip to main content

riven/
config.rs

1//! Configuration of RiotApi.
2use reqwest::header::{HeaderMap, HeaderValue};
3use reqwest::ClientBuilder;
4
5use crate::time::Duration;
6
7/// Configuration for instantiating RiotApi.
8#[derive(Debug)]
9pub struct RiotApiConfig {
10    pub(crate) base_url: String,
11    pub(crate) retries: u8,
12    pub(crate) app_rate_usage_factor: f32,
13    pub(crate) method_rate_usage_factor: f32,
14    pub(crate) burst_factor: f32,
15    pub(crate) duration_overhead: Duration,
16    pub(crate) client_builder: Option<ClientBuilder>,
17    pub(crate) rso_clear_header: Option<String>,
18}
19
20impl RiotApiConfig {
21    /// Request header name for the Riot API key, `"X-Riot-Token"`.
22    ///
23    /// When using `set_client_builder`, the supplied builder should include
24    /// this default header with the Riot API key as the value.
25    pub const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
26
27    /// `"https://{}.api.riotgames.com"`
28    ///
29    /// Default base URL, including `{}` placeholder for region platform.
30    pub const DEFAULT_BASE_URL: &'static str = "https://{}.api.riotgames.com";
31
32    /// `3`
33    ///
34    /// Default number of retries.
35    pub const DEFAULT_RETRIES: u8 = 3;
36
37    /// `1.0`
38    ///
39    /// Default rate limit usage factor.
40    pub const DEFAULT_RATE_USAGE_FACTOR: f32 = 1.0;
41
42    /// `0.99`
43    ///
44    /// Default `burst_factor`, also used by `preconfig_burst`.
45    pub const PRECONFIG_BURST_BURST_FACTOR: f32 = 0.99;
46    /// `989` ms
47    ///
48    /// Default `duration_overhead`, also used by `preconfig_burst`.
49    pub const PRECONFIG_BURST_DURATION_OVERHEAD: Duration = Duration::from_millis(989);
50
51    /// `0.47`
52    ///
53    /// `burst_factor` used by `preconfig_throughput`.
54    pub const PRECONFIG_THROUGHPUT_BURST_FACTOR: f32 = 0.47;
55    /// `10` ms.
56    ///
57    /// `duration_overhead` used by `preconfig_throughput`.
58    pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD: Duration = Duration::from_millis(10);
59
60    /// Creates a new `RiotApiConfig` with the given `api_key` with the following
61    /// configuration:
62    ///
63    /// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
64    /// * `burst_factor = 0.99` (`preconfig_burst`).
65    /// * `duration_overhead = 989 ms` (`preconfig_burst`).
66    ///
67    /// `api_key` should be a Riot Games API key from
68    /// [https://developer.riotgames.com/](https://developer.riotgames.com/),
69    /// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
70    pub fn with_key(api_key: impl AsRef<[u8]>) -> Self {
71        let mut default_headers = HeaderMap::new();
72        default_headers.insert(
73            Self::RIOT_KEY_HEADER,
74            HeaderValue::from_bytes(api_key.as_ref()).unwrap(),
75        );
76
77        Self {
78            base_url: Self::DEFAULT_BASE_URL.into(),
79            retries: Self::DEFAULT_RETRIES,
80            app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
81            method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
82            burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
83            duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
84            client_builder: Some(ClientBuilder::new().default_headers(default_headers)),
85            rso_clear_header: Some(Self::RIOT_KEY_HEADER.to_owned()),
86        }
87    }
88
89    /// Creates a new `RiotApiConfig` with the given client builder.
90    ///
91    /// The client builder default headers should include a value for
92    /// [`RiotApiConfig::RIOT_KEY_HEADER`] (`"X-Riot-Token"`), otherwise authentication will fail.
93    ///
94    /// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
95    /// * `burst_factor = 0.99` (`preconfig_burst`).
96    /// * `duration_overhead = 989 ms` (`preconfig_burst`).
97    pub fn with_client_builder(client_builder: ClientBuilder) -> Self {
98        Self {
99            base_url: Self::DEFAULT_BASE_URL.to_owned(),
100            retries: Self::DEFAULT_RETRIES,
101            app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
102            method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
103            burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
104            duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
105            client_builder: Some(client_builder),
106            rso_clear_header: Some(Self::RIOT_KEY_HEADER.to_owned()),
107        }
108    }
109
110    /// Sets rate limiting settings to preconfigured values optimized for burst,
111    /// low latency:
112    ///
113    /// * `burst_factor = 0.99` (`PRECONFIG_BURST_BURST_FACTOR`).
114    /// * `duration_overhead = 989 ms` (`PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS`).
115    ///
116    /// # Returns
117    /// `self`, for chaining.
118    pub fn preconfig_burst(mut self) -> Self {
119        self.burst_factor = Self::PRECONFIG_BURST_BURST_FACTOR;
120        self.duration_overhead = Self::PRECONFIG_BURST_DURATION_OVERHEAD;
121        self
122    }
123
124    /// Sets the rate limiting settings to preconfigured values  optimized for
125    /// high throughput:
126    ///
127    /// * `burst_factor = 0.47` (`PRECONFIG_THROUGHPUT_BURST_FACTOR`).
128    /// * `duration_overhead = 10 ms` (`PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS`).
129    ///
130    /// # Returns
131    /// `self`, for chaining.
132    pub fn preconfig_throughput(mut self) -> Self {
133        self.burst_factor = Self::PRECONFIG_THROUGHPUT_BURST_FACTOR;
134        self.duration_overhead = Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD;
135        self
136    }
137
138    /// Set the base url for requests. The string should contain a `"{}"`
139    /// literal which will be replaced with the region platform name. (However
140    /// multiple or zero `"{}"`s may be included if needed).
141    ///
142    /// # Returns
143    /// `self`, for chaining.
144    pub fn set_base_url(mut self, base_url: impl Into<String>) -> Self {
145        self.base_url = base_url.into();
146        self
147    }
148
149    /// Set number of times to retry requests. Naturally, only retryable requests
150    /// will be retried: responses with status codes 5xx or 429 (after waiting
151    /// for retry-after headers). A value of `0` means one request will be sent
152    /// and it will not be retried if it fails.
153    ///
154    /// # Returns
155    /// `self`, for chaining.
156    pub fn set_retries(mut self, retries: u8) -> Self {
157        self.retries = retries;
158        self
159    }
160
161    /// The rate limit usage percentage controls how much of the API key's rate
162    /// limit will be used. The default value of `1.0` means the entirety of
163    /// the rate limit may be used if it is needed. This applies to both the
164    /// API key's rate limit (per route) _and_ to endpoint method rate limits.
165    ///
166    /// Setting a value lower than `1.0` can be useful if you are running
167    /// multiple API instances on the same API key.
168    ///
169    /// For example, four instances, possibly running on different machines,
170    /// could each have a value of `0.25` to share an API key's rate limit
171    /// evenly.
172    ///
173    /// Note that if you have multiple instances hitting _different_ methods,
174    /// you should use [Self::set_app_rate_usage_factor()] and [Self::set_method_rate_usage_factor()]
175    /// separately, as this sets both.
176    ///
177    /// This also can be used to reduce the chance of hitting 429s, although
178    /// 429s should be rare even with this set to `1.0`.
179    ///
180    /// # Panics
181    /// If `rate_usage_factor` is not in range (0, 1].
182    ///
183    /// # Returns
184    /// `self`, for chaining.
185    pub fn set_rate_usage_factor(mut self, rate_usage_factor: f32) -> Self {
186        // Use inverted check to handle NaN.
187        if 0.0 < rate_usage_factor && rate_usage_factor <= 1.0 {
188            self.app_rate_usage_factor = rate_usage_factor;
189            self.method_rate_usage_factor = rate_usage_factor;
190            return self;
191        }
192        panic!(
193            "rate_usage_factor \"{}\" not in range (0, 1].",
194            rate_usage_factor
195        );
196    }
197
198    /// See [Self::set_rate_usage_factor]. Setting this is useful if you have multiple
199    /// instances sharing the app rate limit, but are hitting distinct methods
200    /// and therefore do not need their method usage decreased.
201    ///
202    /// # Panics
203    /// If `app_rate_usage_factor` is not in range (0, 1\].
204    ///
205    /// # Returns
206    /// `self`, for chaining.
207    pub fn set_app_rate_usage_factor(mut self, app_rate_usage_factor: f32) -> Self {
208        // Use inverted check to handle NaN.
209        if 0.0 < app_rate_usage_factor && app_rate_usage_factor <= 1.0 {
210            self.app_rate_usage_factor = app_rate_usage_factor;
211            return self;
212        }
213        panic!(
214            "app_rate_usage_factor \"{}\" not in range (0, 1].",
215            app_rate_usage_factor
216        );
217    }
218
219    /// See [Self::set_rate_usage_factor] and [Self::set_app_rate_usage_factor].
220    /// This method is mainly provided for completeness, though it may be
221    /// useful in advanced use cases.
222    ///
223    /// # Panics
224    /// If `method_rate_usage_factor` is not in range (0, 1\].
225    ///
226    /// # Returns
227    /// `self`, for chaining.
228    pub fn set_method_rate_usage_factor(mut self, method_rate_usage_factor: f32) -> Self {
229        // Use inverted check to handle NaN.
230        if 0.0 < method_rate_usage_factor && method_rate_usage_factor <= 1.0 {
231            self.method_rate_usage_factor = method_rate_usage_factor;
232            return self;
233        }
234        panic!(
235            "method_rate_usage_factor \"{}\" not in range (0, 1].",
236            method_rate_usage_factor
237        );
238    }
239
240    /// Burst percentage controls how many burst requests are allowed and
241    /// therefore how requests are spread out. Higher equals more burst,
242    /// less spread. Lower equals less burst, more spread.
243    ///
244    /// The value must be in the range (0, 1];
245    /// Between 0, exclusive, and 1, inclusive. However values should generally
246    /// be larger than 0.25.
247    ///
248    /// Burst percentage behaves as follows:<br>
249    /// A burst percentage of x% means, for each token bucket, "x% of the
250    /// tokens can be used in x% of the bucket duration." So, for example, if x
251    /// is 90%, a bucket would allow 90% of the requests to be made without
252    /// any delay. Then, after waiting 90% of the bucket's duration, the
253    /// remaining 10% of requests could be made.
254    ///
255    /// A burst percentage of 100% results in no request spreading, which would
256    /// allow for the largest bursts and lowest latency, but could result in
257    /// 429s as bucket boundaries occur.
258    ///
259    /// A burst percentage of near 0% results in high spreading causing
260    /// temporally equidistant requests. This prevents 429s but has the highest
261    /// latency. Additionally, if the number of tokens is high, this may lower
262    /// the overall throughput due to the rate at which requests can be
263    /// scheduled.
264    ///
265    /// Therefore, for interactive applications like summoner & match history
266    /// lookup, a higher percentage may be better. For data-collection apps
267    /// like champion winrate aggregation, a medium-low percentage may be
268    /// better.
269    ///
270    /// # Panics
271    /// If `burst_factor` is not in range (0, 1\].
272    ///
273    /// # Returns
274    /// `self`, for chaining.
275    pub fn set_burst_factor(mut self, burst_factor: f32) -> Self {
276        // Use inverted check to handle NaN.
277        if 0.0 < burst_factor && burst_factor <= 1.0 {
278            self.burst_factor = burst_factor;
279            return self;
280        }
281        panic!("burst_factor \"{}\" not in range (0, 1].", burst_factor);
282    }
283
284    /// Sets the additional bucket duration to consider when rate limiting.
285    /// Increasing this value will decrease the chances of 429s, but will lower
286    /// the overall throughput.
287    ///
288    /// In a sense, the `duration_overhead` is how much to "widen" the temporal
289    /// width of buckets.
290    ///
291    /// Given a particular Riot Game API rate limit bucket that allows N requests
292    /// per D duration, when counting requests this library will consider requests
293    /// sent in the past `D + duration_overhead` duration.
294    ///
295    /// # Returns
296    /// `self`, for chaining.
297    pub fn set_duration_overhead(mut self, duration_overhead: Duration) -> Self {
298        self.duration_overhead = duration_overhead;
299        self
300    }
301
302    /// Sets the header to clear for RSO requests (if `Some`), or will not override any headers (if
303    /// `None`).
304    ///
305    /// This is a bit of a hack. The client used by Riven is expected to include the API key as a
306    /// default header. However, if the API key is included in an [RSO](https://developer.riotgames.com/docs/lol#rso-integration)
307    /// request the server responds with a 400 "Bad request - Invalid authorization specified"
308    /// error. To avoid this the `rso_clear_header` header is overridden to be empty for RSO
309    /// requests.
310    ///
311    /// This is set to `Some(`[`Self::RIOT_KEY_HEADER`]`)` by default.
312    ///
313    /// # Returns
314    /// `self`, for chaining.
315    pub fn set_rso_clear_header(mut self, rso_clear_header: Option<String>) -> Self {
316        self.rso_clear_header = rso_clear_header;
317        self
318    }
319}
320
321impl<T: AsRef<[u8]>> From<T> for RiotApiConfig {
322    fn from(api_key: T) -> Self {
323        Self::with_key(api_key)
324    }
325}