Skip to main content

cg_common/
rate_limit.rs

1//! Rate limiting for CoinGecko API requests
2
3use governor::{Quota, RateLimiter};
4use std::num::NonZeroU32;
5use std::sync::Arc;
6use tracing::debug;
7
8/// Type alias for the rate limiter
9pub type CgRateLimiter = RateLimiter<
10    governor::state::NotKeyed,
11    governor::state::InMemoryState,
12    governor::clock::DefaultClock,
13>;
14
15/// Default requests per minute for CoinGecko free tier
16/// CoinGecko free tier allows ~10-30 requests/minute depending on endpoint.
17/// Being conservative to avoid 429s across multiple tool invocations.
18pub const DEFAULT_REQUESTS_PER_MINUTE: u32 = 10;
19
20/// Configuration for rate limiting
21#[derive(Debug, Clone)]
22pub struct RateLimitConfig {
23    /// Requests allowed per minute
24    pub requests_per_minute: u32,
25}
26
27impl Default for RateLimitConfig {
28    fn default() -> Self {
29        Self {
30            requests_per_minute: DEFAULT_REQUESTS_PER_MINUTE,
31        }
32    }
33}
34
35impl RateLimitConfig {
36    /// Create a new rate limit configuration
37    pub fn new(requests_per_minute: u32) -> Self {
38        Self {
39            requests_per_minute,
40        }
41    }
42
43    /// Build a rate limiter from this configuration
44    pub fn build_limiter(&self) -> Arc<CgRateLimiter> {
45        let quota = Quota::per_minute(
46            NonZeroU32::new(self.requests_per_minute).unwrap_or(NonZeroU32::new(1).unwrap()),
47        );
48        Arc::new(RateLimiter::direct(quota))
49    }
50}
51
52/// Wait for a rate limit permit
53pub async fn wait_for_permit(limiter: &CgRateLimiter) {
54    limiter.until_ready().await;
55    debug!("Rate limit permit acquired");
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_default_config() {
64        let config = RateLimitConfig::default();
65        assert_eq!(config.requests_per_minute, DEFAULT_REQUESTS_PER_MINUTE);
66    }
67
68    #[test]
69    fn test_custom_config() {
70        let config = RateLimitConfig::new(10);
71        assert_eq!(config.requests_per_minute, 10);
72    }
73
74    #[test]
75    fn test_build_limiter() {
76        let config = RateLimitConfig::new(5);
77        let limiter = config.build_limiter();
78        assert!(limiter.check().is_ok());
79    }
80}