api_key_pool/
lib.rs

1//! Pool of API keys to circumvent rate-limits.
2//!
3//! This package provides an easy way to use multiple API keys to bypass draconian rate-limit policies.
4//!
5//! # Example
6//!
7//! ```
8//! use chrono::Duration;
9//! use tokio::time;
10//!
11//! use api_key_pool::*;
12//!
13//! #[tokio::main]
14//! async fn main() {
15//!     // Create a RateLimitPolicy to be applied to all API keys.
16//!     // Note: An APIPool can have APIKeys with different RateLimitPolicies.
17//!     //       For the sake of simplicity, this example assumes identical policies.
18//!     let pol = RateLimitPolicy::new(1, Duration::seconds(2));
19//!
20//!     // Create the APIKeys.
21//!     let api1 = APIKey::new("1", pol);
22//!     let api2 = APIKey::new("2", pol);
23//!     let api3 = APIKey::new("3", pol);
24//!
25//!     // Create the APIKeyPool.
26//!     let mut pool = APIKeyPool::new();
27//!     pool.add_key(api1).await;
28//!     pool.add_key(api2).await;
29//!     pool.add_key(api3).await;
30//!
31//!     // Simulate 20 requests.
32//!     let mut ctr = 0;
33//!     while ctr < 20 {
34//!         // Use the APIKey if available (according to its respective RateLimitPolicy) or sleep.
35//!         if let Some(key) = pool.poll_for_key().await {
36//!             println!("{}", key);
37//!             ctr += 1;
38//!         } else {
39//!             println!("Have to sleep.");
40//!             time::sleep(time::Duration::from_millis(500)).await;
41//!         }
42//!     }
43//! }
44//! ```
45
46
47use std::cmp::Reverse;
48use std::collections::BinaryHeap;
49use std::sync::Arc;
50
51use chrono::{DateTime, Utc};
52use tokio::sync::Mutex;
53
54/// A pool of API keys.
55#[derive(Default)]
56pub struct APIKeyPool {
57    /// Collection holding the API keys.
58    api_keys: Arc<Mutex<Vec<APIKey>>>,
59}
60
61impl APIKeyPool {
62    /// Returns an empty API key pool.
63    pub fn new() -> Self {
64        Self {
65            api_keys: Arc::new(Mutex::new(Vec::new())),
66        }
67    }
68
69    /// Adds an API key to an API key pool.
70    ///
71    /// # Arguments
72    ///
73    /// * `key` - the API key to be added.
74    pub async fn add_key(&mut self, key: APIKey) {
75        self.api_keys.lock().await.push(key);
76    }
77
78    /// Checks the API key pool for any available API keys, and returns the API key if available.
79    pub async fn poll_for_key(&mut self) -> Option<String> {
80        // TODO: Performance can be improved by keeping track of index of last used key.
81        for key in &mut self.api_keys.lock().await.iter_mut() {
82            if key.is_ready().await {
83                return Some(key.use_key().await);
84            }
85        }
86        None
87    }
88}
89
90/// An API key, with its associated RateLimitPolicy
91pub struct APIKey {
92    /// The API key code.
93    key: String,
94    /// The rate limit policy that governs this API key.
95    policy: RateLimitPolicy,
96    /// Min-heap used to calculate if the key is available.
97    times: Arc<Mutex<BinaryHeap<Reverse<DateTime<Utc>>>>>,
98}
99
100impl APIKey {
101    /// Returns an API key with the given policy and code.
102    ///
103    /// # Arguments
104    ///
105    /// * `key` - the API key code.
106    /// * `policy` - the rate limit policy governing the API key.
107    pub fn new(key: &str, policy: RateLimitPolicy) -> Self {
108        let mut _times = BinaryHeap::new();
109        _times.reserve(policy.count);
110        let times = Arc::new(Mutex::new(_times));
111        Self {
112            key: String::from(key),
113            policy,
114            times,
115        }
116    }
117
118    /// Returns the code of an API key.
119    fn get_key(&self) -> String {
120        self.key.clone()
121    }
122
123    /// Checks to see if the API key is available for use.
124    async fn is_ready(&self) -> bool {
125        // If we have used the API key less than N times, we can use it again.
126        if self.times.lock().await.len() < self.policy.count {
127            return true;
128        }
129        if let Some(oldest) = self.times.lock().await.peek() {
130            // If the oldest time used is at least D duration ago.
131            if oldest.0 < Utc::now() - self.policy.per {
132                return true;
133            }
134        }
135        false
136    }
137
138    /// Uses the key.
139    async fn use_key(&mut self) -> String {
140        if self.times.lock().await.len() >= self.policy.count {
141            self.times.lock().await.pop();
142        }
143        self.times.lock().await.push(Reverse(Utc::now()));
144        self.get_key().clone()
145    }
146}
147
148/// A policy for rate-limiting an API key.
149#[derive(Clone, Copy)]
150pub struct RateLimitPolicy {
151    /// The number of times an API key can be used in the specified duration.
152    pub count: usize,
153    /// The duration.
154    pub per: chrono::Duration,
155}
156
157impl RateLimitPolicy {
158    /// Returns a rate-limit policy with the parameters.
159    ///
160    /// # Arguments
161    ///
162    /// * `count` - N times
163    /// * `per` - per D duration
164    pub fn new(count: usize, per: chrono::Duration) -> Self {
165        Self { count, per }
166    }
167}