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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Pool of API keys to circumvent rate-limits.
//!
//! This package provides an easy way to use multiple API keys to bypass draconian rate-limit policies.
//!
//! # Example
//!
//! ```
//! use chrono::Duration;
//! use tokio::time;
//!
//! use api_key_pool::*;
//!
//! #[tokio::main]
//! async fn main() {
//!     // Create a RateLimitPolicy to be applied to all API keys.
//!     // Note: An APIPool can have APIKeys with different RateLimitPolicies.
//!     //       For the sake of simplicity, this example assumes identical policies.
//!     let pol = RateLimitPolicy::new(1, Duration::seconds(2));
//!
//!     // Create the APIKeys.
//!     let api1 = APIKey::new("1", pol);
//!     let api2 = APIKey::new("2", pol);
//!     let api3 = APIKey::new("3", pol);
//!
//!     // Create the APIKeyPool.
//!     let mut pool = APIKeyPool::new();
//!     pool.add_key(api1).await;
//!     pool.add_key(api2).await;
//!     pool.add_key(api3).await;
//!
//!     // Simulate 20 requests.
//!     let mut ctr = 0;
//!     while ctr < 20 {
//!         // Use the APIKey if available (according to its respective RateLimitPolicy) or sleep.
//!         if let Some(key) = pool.poll_for_key().await {
//!             println!("{}", key);
//!             ctr += 1;
//!         } else {
//!             println!("Have to sleep.");
//!             time::sleep(time::Duration::from_millis(500)).await;
//!         }
//!     }
//! }
//! ```


use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::sync::Arc;

use chrono::{DateTime, Utc};
use tokio::sync::Mutex;

/// A pool of API keys.
#[derive(Default)]
pub struct APIKeyPool {
    /// Collection holding the API keys.
    api_keys: Arc<Mutex<Vec<APIKey>>>,
}

impl APIKeyPool {
    /// Returns an empty API key pool.
    pub fn new() -> Self {
        Self {
            api_keys: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Adds an API key to an API key pool.
    ///
    /// # Arguments
    ///
    /// * `key` - the API key to be added.
    pub async fn add_key(&mut self, key: APIKey) {
        self.api_keys.lock().await.push(key);
    }

    /// Checks the API key pool for any available API keys, and returns the API key if available.
    pub async fn poll_for_key(&mut self) -> Option<String> {
        // TODO: Performance can be improved by keeping track of index of last used key.
        for key in &mut self.api_keys.lock().await.iter_mut() {
            if key.is_ready().await {
                return Some(key.use_key().await);
            }
        }
        None
    }
}

/// An API key, with its associated RateLimitPolicy
pub struct APIKey {
    /// The API key code.
    key: String,
    /// The rate limit policy that governs this API key.
    policy: RateLimitPolicy,
    /// Min-heap used to calculate if the key is available.
    times: Arc<Mutex<BinaryHeap<Reverse<DateTime<Utc>>>>>,
}

impl APIKey {
    /// Returns an API key with the given policy and code.
    ///
    /// # Arguments
    ///
    /// * `key` - the API key code.
    /// * `policy` - the rate limit policy governing the API key.
    pub fn new(key: &str, policy: RateLimitPolicy) -> Self {
        let mut _times = BinaryHeap::new();
        _times.reserve(policy.count);
        let times = Arc::new(Mutex::new(_times));
        Self {
            key: String::from(key),
            policy,
            times,
        }
    }

    /// Returns the code of an API key.
    fn get_key(&self) -> String {
        self.key.clone()
    }

    /// Checks to see if the API key is available for use.
    async fn is_ready(&self) -> bool {
        // If we have used the API key less than N times, we can use it again.
        if self.times.lock().await.len() < self.policy.count {
            return true;
        }
        if let Some(oldest) = self.times.lock().await.peek() {
            // If the oldest time used is at least D duration ago.
            if oldest.0 < Utc::now() - self.policy.per {
                return true;
            }
        }
        false
    }

    /// Uses the key.
    async fn use_key(&mut self) -> String {
        if self.times.lock().await.len() >= self.policy.count {
            self.times.lock().await.pop();
        }
        self.times.lock().await.push(Reverse(Utc::now()));
        self.get_key().clone()
    }
}

/// A policy for rate-limiting an API key.
#[derive(Clone, Copy)]
pub struct RateLimitPolicy {
    /// The number of times an API key can be used in the specified duration.
    pub count: usize,
    /// The duration.
    pub per: chrono::Duration,
}

impl RateLimitPolicy {
    /// Returns a rate-limit policy with the parameters.
    ///
    /// # Arguments
    ///
    /// * `count` - N times
    /// * `per` - per D duration
    pub fn new(count: usize, per: chrono::Duration) -> Self {
        Self { count, per }
    }
}