gatekpr-rate-limiter 0.2.3

Reusable rate limiting with multiple backend support
Documentation
//! Rate limit configuration
//!
//! Provides flexible configuration for rate limiting with support for
//! different tiers/plans.

use std::time::Duration;

/// Rate limit configuration
///
/// Defines the limits for a particular rate limiting window.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RateLimitConfig {
    /// Maximum requests allowed in the minute window
    pub requests_per_minute: u32,
    /// Maximum requests allowed in the hour window
    pub requests_per_hour: u32,
}

impl Default for RateLimitConfig {
    fn default() -> Self {
        Self {
            requests_per_minute: 60,
            requests_per_hour: 1000,
        }
    }
}

impl RateLimitConfig {
    /// Create a new rate limit config
    pub fn new(requests_per_minute: u32, requests_per_hour: u32) -> Self {
        Self {
            requests_per_minute,
            requests_per_hour,
        }
    }

    /// Create config for a named plan tier
    ///
    /// This provides sensible defaults for common SaaS tiers:
    /// - `free`: 20/min, 500/hour
    /// - `pro`: 100/min, 5000/hour
    /// - `enterprise`: 1000/min, 50000/hour
    /// - any other: uses `free` limits
    #[inline]
    pub fn for_plan(plan: &str) -> Self {
        match plan {
            "enterprise" => Self {
                requests_per_minute: 1000,
                requests_per_hour: 50000,
            },
            "pro" => Self {
                requests_per_minute: 100,
                requests_per_hour: 5000,
            },
            // "free" is the default for unknown plans
            _ => Self {
                requests_per_minute: 20,
                requests_per_hour: 500,
            },
        }
    }

    /// Create unlimited config (no rate limiting)
    pub fn unlimited() -> Self {
        Self {
            requests_per_minute: u32::MAX,
            requests_per_hour: u32::MAX,
        }
    }

    /// Check if this config represents unlimited access
    pub fn is_unlimited(&self) -> bool {
        self.requests_per_minute == u32::MAX && self.requests_per_hour == u32::MAX
    }

    /// Get the minute window duration
    pub fn minute_window() -> Duration {
        Duration::from_secs(60)
    }

    /// Get the hour window duration
    pub fn hour_window() -> Duration {
        Duration::from_secs(3600)
    }
}

/// Builder for creating custom rate limit configurations
#[derive(Debug, Clone)]
pub struct RateLimitConfigBuilder {
    requests_per_minute: u32,
    requests_per_hour: u32,
}

impl Default for RateLimitConfigBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl RateLimitConfigBuilder {
    /// Create a new builder with default limits
    pub fn new() -> Self {
        Self {
            requests_per_minute: 60,
            requests_per_hour: 1000,
        }
    }

    /// Set the per-minute limit
    pub fn per_minute(mut self, limit: u32) -> Self {
        self.requests_per_minute = limit;
        self
    }

    /// Set the per-hour limit
    pub fn per_hour(mut self, limit: u32) -> Self {
        self.requests_per_hour = limit;
        self
    }

    /// Build the configuration
    pub fn build(self) -> RateLimitConfig {
        RateLimitConfig {
            requests_per_minute: self.requests_per_minute,
            requests_per_hour: self.requests_per_hour,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = RateLimitConfig::default();
        assert_eq!(config.requests_per_minute, 60);
        assert_eq!(config.requests_per_hour, 1000);
    }

    #[test]
    fn test_for_plan() {
        let free = RateLimitConfig::for_plan("free");
        assert_eq!(free.requests_per_minute, 20);
        assert_eq!(free.requests_per_hour, 500);

        let pro = RateLimitConfig::for_plan("pro");
        assert_eq!(pro.requests_per_minute, 100);
        assert_eq!(pro.requests_per_hour, 5000);

        let enterprise = RateLimitConfig::for_plan("enterprise");
        assert_eq!(enterprise.requests_per_minute, 1000);
        assert_eq!(enterprise.requests_per_hour, 50000);

        // Unknown plan defaults to free
        let unknown = RateLimitConfig::for_plan("unknown");
        assert_eq!(unknown, RateLimitConfig::for_plan("free"));
    }

    #[test]
    fn test_unlimited() {
        let config = RateLimitConfig::unlimited();
        assert!(config.is_unlimited());
    }

    #[test]
    fn test_builder() {
        let config = RateLimitConfigBuilder::new()
            .per_minute(200)
            .per_hour(10000)
            .build();

        assert_eq!(config.requests_per_minute, 200);
        assert_eq!(config.requests_per_hour, 10000);
    }
}