kiteconnect_async_wasm/connect/
rate_limiter.rs

1//! # Rate Limiter Module
2//!
3//! This module implements sophisticated per-endpoint rate limiting based on official
4//! KiteConnect API limits to prevent exceeding rate limits and API key suspension.
5//!
6//! ## Rate Limiting Strategy
7//!
8//! KiteConnect API has different rate limits for different endpoint categories:
9//! - **Quote endpoints**: 1 request/second (real-time market data)
10//! - **Historical endpoints**: 3 requests/second (historical data)  
11//! - **Order endpoints**: 10 requests/second (order placement/modification)
12//! - **Standard endpoints**: 10 requests/second (all other operations)
13//!
14//! ## Implementation Details
15//!
16//! The rate limiter uses a token bucket algorithm with per-category tracking:
17//! 1. Each category has its own rate limit and timing
18//! 2. Requests are tracked with precise timing to ensure compliance
19//! 3. Automatic delays are inserted when limits would be exceeded
20//! 4. Thread-safe implementation supports concurrent operations
21//!
22//! ## Usage
23//!
24//! The rate limiter is automatically integrated into all API calls through
25//! the `KiteConnect` client. No manual intervention is required.
26//!
27//! ## Example
28//!
29//! ```rust,no_run
30//! use kiteconnect_async_wasm::connect::KiteConnect;
31//!
32//! # #[tokio::main]
33//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
34//! let client = KiteConnect::new("api_key", "access_token");
35//!
36//! // These calls are automatically rate-limited
37//! let _ = client.quote_typed(vec!["NSE:RELIANCE"]).await?; // 1 req/sec limit
38//! let _ = client.orders().await?; // 10 req/sec limit
39//! # Ok(())
40//! # }
41//! ```
42//!
43//! ## Performance Considerations
44//!
45//! - Minimal overhead: Rate limiting adds microseconds per request
46//! - Memory efficient: Only stores timing data for active categories
47//! - Concurrent safe: Supports multiple simultaneous requests
48//! - Auto-cleanup: Unused categories are automatically cleaned up
49
50use std::collections::HashMap;
51use std::sync::Arc;
52use std::time::{Duration, Instant};
53use tokio::sync::Mutex;
54
55use super::endpoints::{KiteEndpoint, RateLimitCategory};
56
57/// Per-category rate limiter state
58///
59/// Tracks timing and request counts for a specific rate limit category.
60/// Each category (Quote, Historical, Orders, Standard) has its own limiter
61/// instance with category-specific limits and timing requirements.
62///
63/// # Fields
64///
65/// - `last_request`: Timestamp of the most recent request in this category
66/// - `min_delay`: Minimum time that must pass between requests
67/// - `request_count`: Number of requests made in the current time window
68/// - `requests_per_second`: Maximum allowed requests per second for this category
69///
70/// # Thread Safety
71///
72/// This struct is designed to be used within a `Mutex` for thread-safe access
73/// across multiple concurrent requests.
74#[derive(Debug)]
75struct CategoryLimiter {
76    /// Last request time for this category
77    last_request: Option<Instant>,
78    /// Minimum delay between requests
79    min_delay: Duration,
80    /// Number of requests in current window
81    request_count: u32,
82    /// Requests per second limit
83    requests_per_second: u32,
84}
85
86impl CategoryLimiter {
87    /// Create a new category limiter with the specified rate limit category
88    ///
89    /// # Arguments
90    ///
91    /// * `category` - The rate limit category which determines the limits
92    ///
93    /// # Returns
94    ///
95    /// A new `CategoryLimiter` configured for the specified category
96    ///
97    /// # Example
98    ///
99    /// ```rust,ignore
100    /// # use kiteconnect_async_wasm::connect::endpoints::RateLimitCategory;
101    /// # use kiteconnect_async_wasm::connect::rate_limiter::CategoryLimiter;
102    /// let limiter = CategoryLimiter::new(RateLimitCategory::Quote);
103    /// // Quote category: 1 request/second
104    /// ```
105    fn new(category: RateLimitCategory) -> Self {
106        Self {
107            last_request: None,
108            min_delay: category.min_delay(),
109            request_count: 0,
110            requests_per_second: category.requests_per_second(),
111        }
112    }
113
114    /// Check if a request can be made immediately without delay
115    ///
116    /// # Returns
117    ///
118    /// `true` if enough time has passed since the last request, `false` otherwise
119    ///
120    /// # Example
121    ///
122    /// ```rust,ignore
123    /// # use kiteconnect_async_wasm::connect::endpoints::RateLimitCategory;
124    /// # use kiteconnect_async_wasm::connect::rate_limiter::CategoryLimiter;
125    /// let mut limiter = CategoryLimiter::new(RateLimitCategory::Quote);
126    ///
127    /// if limiter.can_request_now() {
128    ///     // Make request immediately
129    /// } else {
130    ///     // Need to wait before making request
131    /// }
132    /// ```
133    fn can_request_now(&self) -> bool {
134        if let Some(last) = self.last_request {
135            last.elapsed() >= self.min_delay
136        } else {
137            true
138        }
139    }
140
141    /// Calculate the delay needed before the next request can be made
142    ///
143    /// # Returns
144    ///
145    /// `Duration` representing how long to wait before the next request.
146    /// Returns `Duration::ZERO` if no delay is needed.
147    ///
148    /// # Example
149    ///
150    /// ```rust,ignore
151    /// # use kiteconnect_async_wasm::connect::endpoints::RateLimitCategory;
152    /// # use kiteconnect_async_wasm::connect::rate_limiter::CategoryLimiter;
153    /// let mut limiter = CategoryLimiter::new(RateLimitCategory::Quote);
154    ///
155    /// let delay = limiter.delay_until_next_request();
156    /// if !delay.is_zero() {
157    ///     tokio::time::sleep(delay).await;
158    /// }
159    /// ```
160    fn delay_until_next_request(&self) -> Duration {
161        if let Some(last) = self.last_request {
162            let elapsed = last.elapsed();
163            if elapsed < self.min_delay {
164                self.min_delay - elapsed
165            } else {
166                Duration::ZERO
167            }
168        } else {
169            Duration::ZERO
170        }
171    }
172
173    /// Record a request
174    fn record_request(&mut self) {
175        self.last_request = Some(Instant::now());
176        self.request_count += 1;
177    }
178
179    /// Reset request count (called every second)
180    ///
181    /// This method is used internally for cleaning up request counters
182    /// and may be used in future rate limiting algorithms.
183    #[allow(dead_code)]
184    fn reset_count(&mut self) {
185        self.request_count = 0;
186    }
187}
188
189/// Rate limiter for KiteConnect API endpoints
190///
191/// This struct provides intelligent rate limiting for all KiteConnect API calls
192/// to ensure compliance with official API limits and prevent API key suspension.
193///
194/// ## Features
195///
196/// - **Per-category limits**: Different limits for quotes, historical data, orders, etc.
197/// - **Automatic delays**: Inserts precise delays when limits would be exceeded
198/// - **Thread-safe**: Supports concurrent requests from multiple threads
199/// - **Configurable**: Can be enabled/disabled as needed
200/// - **Zero-overhead**: Minimal performance impact when limits aren't reached
201///
202/// ## Rate Limit Categories
203///
204/// | Category   | Limit (req/sec) | Endpoints                    |
205/// |------------|-----------------|------------------------------|
206/// | Quote      | 1               | Real-time quotes, LTP, OHLC  |
207/// | Historical | 3               | Historical candle data       |
208/// | Orders     | 10              | Order placement/modification |
209/// | Standard   | 10              | All other endpoints          |
210///
211/// ## Usage
212///
213/// The rate limiter is automatically integrated into `KiteConnect` and requires
214/// no manual configuration. It's enabled by default and operates transparently.
215///
216/// ## Example
217///
218/// ```rust,no_run
219/// use kiteconnect_async_wasm::connect::rate_limiter::RateLimiter;
220/// use kiteconnect_async_wasm::connect::endpoints::KiteEndpoint;
221///
222/// # #[tokio::main]
223/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
224/// let rate_limiter = RateLimiter::new(true); // enabled
225///
226/// // Check if request can proceed immediately
227/// let can_proceed = rate_limiter.can_request_immediately(&KiteEndpoint::Quote).await;
228/// if can_proceed {
229///     println!("Request can be made immediately");
230/// }
231///
232/// // Wait for rate limit compliance and make request
233/// rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
234/// println!("Request made with rate limiting");
235/// # Ok(())
236/// # }
237/// ```
238///
239/// ## Performance
240///
241/// - **Fast path**: When limits aren't reached, overhead is ~1-2 microseconds
242/// - **Memory**: Uses minimal memory (only active categories tracked)
243/// - **Scalability**: Handles hundreds of concurrent requests efficiently
244#[derive(Debug, Clone)]
245pub struct RateLimiter {
246    /// Rate limiters per category
247    limiters: Arc<Mutex<HashMap<RateLimitCategory, CategoryLimiter>>>,
248    /// Whether rate limiting is enabled
249    enabled: bool,
250}
251
252impl RateLimiter {
253    /// Create a new rate limiter
254    pub fn new(enabled: bool) -> Self {
255        let mut limiters = HashMap::new();
256
257        // Initialize limiters for all categories
258        limiters.insert(
259            RateLimitCategory::Quote,
260            CategoryLimiter::new(RateLimitCategory::Quote),
261        );
262        limiters.insert(
263            RateLimitCategory::Historical,
264            CategoryLimiter::new(RateLimitCategory::Historical),
265        );
266        limiters.insert(
267            RateLimitCategory::Orders,
268            CategoryLimiter::new(RateLimitCategory::Orders),
269        );
270        limiters.insert(
271            RateLimitCategory::Standard,
272            CategoryLimiter::new(RateLimitCategory::Standard),
273        );
274
275        Self {
276            limiters: Arc::new(Mutex::new(limiters)),
277            enabled,
278        }
279    }
280
281    /// Wait for rate limit compliance before making a request
282    ///
283    /// This method will return immediately if no delay is needed,
284    /// or will sleep for the required duration to comply with rate limits.
285    ///
286    /// # Arguments
287    ///
288    /// * `endpoint` - The endpoint being accessed
289    ///
290    /// # Example
291    ///
292    /// ```rust,no_run
293    /// use kiteconnect_async_wasm::connect::{RateLimiter, KiteEndpoint};
294    ///
295    /// # #[tokio::main]
296    /// # async fn main() {
297    /// let rate_limiter = RateLimiter::new(true);
298    ///
299    /// // This will wait if needed to comply with rate limits
300    /// rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
301    ///
302    /// // Now it's safe to make the API request
303    /// println!("Making quote request...");
304    /// # }
305    /// ```
306    pub async fn wait_for_request(&self, endpoint: &KiteEndpoint) {
307        if !self.enabled {
308            return;
309        }
310
311        let category = endpoint.rate_limit_category();
312        let delay = {
313            let limiters = self.limiters.lock().await;
314            if let Some(limiter) = limiters.get(&category) {
315                limiter.delay_until_next_request()
316            } else {
317                Duration::ZERO
318            }
319        };
320
321        if delay > Duration::ZERO {
322            #[cfg(feature = "debug")]
323            log::debug!(
324                "Rate limiting: waiting {:?} for {:?} category",
325                delay,
326                category
327            );
328
329            tokio::time::sleep(delay).await;
330        }
331
332        // Record the request
333        let mut limiters = self.limiters.lock().await;
334        if let Some(limiter) = limiters.get_mut(&category) {
335            limiter.record_request();
336        }
337    }
338
339    /// Check if a request can be made without waiting
340    ///
341    /// Returns true if the request can be made immediately,
342    /// false if rate limiting would cause a delay.
343    pub async fn can_request_immediately(&self, endpoint: &KiteEndpoint) -> bool {
344        if !self.enabled {
345            return true;
346        }
347
348        let category = endpoint.rate_limit_category();
349        let limiters = self.limiters.lock().await;
350
351        if let Some(limiter) = limiters.get(&category) {
352            limiter.can_request_now()
353        } else {
354            true
355        }
356    }
357
358    /// Get the delay required before making a request
359    ///
360    /// Returns Duration::ZERO if no delay is needed.
361    pub async fn get_delay_for_request(&self, endpoint: &KiteEndpoint) -> Duration {
362        if !self.enabled {
363            return Duration::ZERO;
364        }
365
366        let category = endpoint.rate_limit_category();
367        let limiters = self.limiters.lock().await;
368
369        if let Some(limiter) = limiters.get(&category) {
370            limiter.delay_until_next_request()
371        } else {
372            Duration::ZERO
373        }
374    }
375
376    /// Get rate limiter statistics
377    ///
378    /// Returns information about current rate limiter state for monitoring.
379    pub async fn get_stats(&self) -> RateLimiterStats {
380        let limiters = self.limiters.lock().await;
381        let mut categories = HashMap::new();
382
383        for (category, limiter) in limiters.iter() {
384            categories.insert(
385                category.clone(),
386                CategoryStats {
387                    request_count: limiter.request_count,
388                    requests_per_second: limiter.requests_per_second,
389                    last_request: limiter.last_request,
390                    next_available: limiter.last_request.map(|last| last + limiter.min_delay),
391                },
392            );
393        }
394
395        RateLimiterStats {
396            enabled: self.enabled,
397            categories,
398        }
399    }
400
401    /// Enable or disable rate limiting
402    pub fn set_enabled(&mut self, enabled: bool) {
403        self.enabled = enabled;
404    }
405
406    /// Check if rate limiting is enabled
407    pub fn is_enabled(&self) -> bool {
408        self.enabled
409    }
410}
411
412impl Default for RateLimiter {
413    fn default() -> Self {
414        Self::new(true)
415    }
416}
417
418/// Statistics about rate limiter state
419#[derive(Debug, Clone)]
420pub struct RateLimiterStats {
421    /// Whether rate limiting is enabled
422    pub enabled: bool,
423    /// Per-category statistics
424    pub categories: HashMap<RateLimitCategory, CategoryStats>,
425}
426
427/// Statistics for a specific rate limit category
428#[derive(Debug, Clone)]
429pub struct CategoryStats {
430    /// Current request count in this second
431    pub request_count: u32,
432    /// Maximum requests per second for this category
433    pub requests_per_second: u32,
434    /// When the last request was made
435    pub last_request: Option<Instant>,
436    /// When the next request can be made
437    pub next_available: Option<Instant>,
438}
439
440impl CategoryStats {
441    /// Check if this category is currently at its rate limit
442    pub fn is_at_limit(&self) -> bool {
443        if let Some(next) = self.next_available {
444            next > Instant::now()
445        } else {
446            false
447        }
448    }
449
450    /// Get remaining capacity for this category
451    pub fn remaining_capacity(&self) -> u32 {
452        self.requests_per_second.saturating_sub(self.request_count)
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use tokio::time::Duration;
460
461    #[tokio::test]
462    async fn test_rate_limiter_basic_functionality() {
463        let rate_limiter = RateLimiter::new(true);
464
465        // First request should be immediate
466        assert!(
467            rate_limiter
468                .can_request_immediately(&KiteEndpoint::Quote)
469                .await
470        );
471
472        // Wait for request (should be immediate)
473        let start = Instant::now();
474        rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
475        assert!(start.elapsed() < Duration::from_millis(10));
476
477        // Second request should require waiting (quote is 1 req/sec)
478        assert!(
479            !rate_limiter
480                .can_request_immediately(&KiteEndpoint::Quote)
481                .await
482        );
483
484        let delay = rate_limiter
485            .get_delay_for_request(&KiteEndpoint::Quote)
486            .await;
487        assert!(delay > Duration::from_millis(900)); // Should be close to 1 second
488    }
489
490    #[tokio::test]
491    async fn test_rate_limiter_disabled() {
492        let rate_limiter = RateLimiter::new(false);
493
494        // All requests should be immediate when disabled
495        rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
496        assert!(
497            rate_limiter
498                .can_request_immediately(&KiteEndpoint::Quote)
499                .await
500        );
501
502        let delay = rate_limiter
503            .get_delay_for_request(&KiteEndpoint::Quote)
504            .await;
505        assert_eq!(delay, Duration::ZERO);
506    }
507
508    #[tokio::test]
509    async fn test_different_categories() {
510        let rate_limiter = RateLimiter::new(true);
511
512        // Make a quote request
513        rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
514
515        // Historical request should still be available (different category)
516        assert!(
517            rate_limiter
518                .can_request_immediately(&KiteEndpoint::HistoricalData)
519                .await
520        );
521
522        // Standard requests should be available (different category)
523        assert!(
524            rate_limiter
525                .can_request_immediately(&KiteEndpoint::Holdings)
526                .await
527        );
528    }
529
530    #[tokio::test]
531    async fn test_rate_limiter_stats() {
532        let rate_limiter = RateLimiter::new(true);
533
534        // Make some requests
535        rate_limiter.wait_for_request(&KiteEndpoint::Quote).await;
536        rate_limiter.wait_for_request(&KiteEndpoint::Holdings).await;
537
538        let stats = rate_limiter.get_stats().await;
539        assert!(stats.enabled);
540
541        // Quote category should have 1 request
542        let quote_stats = &stats.categories[&RateLimitCategory::Quote];
543        assert_eq!(quote_stats.request_count, 1);
544        assert_eq!(quote_stats.requests_per_second, 1);
545        assert!(quote_stats.last_request.is_some());
546
547        // Standard category should have 1 request
548        let standard_stats = &stats.categories[&RateLimitCategory::Standard];
549        assert_eq!(standard_stats.request_count, 1);
550        assert_eq!(standard_stats.requests_per_second, 3);
551    }
552
553    #[tokio::test]
554    async fn test_category_stats() {
555        let stats = CategoryStats {
556            request_count: 5,
557            requests_per_second: 10,
558            last_request: Some(Instant::now()),
559            next_available: Some(Instant::now() + Duration::from_millis(100)),
560        };
561
562        assert!(stats.is_at_limit());
563        assert_eq!(stats.remaining_capacity(), 5);
564
565        let stats_not_at_limit = CategoryStats {
566            request_count: 3,
567            requests_per_second: 10,
568            last_request: Some(Instant::now() - Duration::from_secs(1)),
569            next_available: Some(Instant::now() - Duration::from_millis(100)),
570        };
571
572        assert!(!stats_not_at_limit.is_at_limit());
573        assert_eq!(stats_not_at_limit.remaining_capacity(), 7);
574    }
575}