tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
// API rate limiting and throttling.
//
// This example shows how to:
// - Implement simple rate limits
// - Enforce burst protection across multiple time scales
// - Check limits without recording
// - Use transactional reservations to prevent race conditions

use tiny_counter::{EventId, EventStore, TimeUnit};

// API events with type safety
#[derive(Debug, Clone, Copy)]
enum ApiEvent {
    Request,
    Login,
    Upload,
    PasswordReset,
}

impl AsRef<str> for ApiEvent {
    fn as_ref(&self) -> &str {
        match self {
            ApiEvent::Request => "api:request",
            ApiEvent::Login => "api:login",
            ApiEvent::Upload => "api:upload",
            ApiEvent::PasswordReset => "api:password_reset",
        }
    }
}

impl EventId for ApiEvent {}

fn main() {
    println!("=== Rate Limiting Examples ===\n");

    // 1. Simple rate limit
    println!("1. Simple rate limit (5 requests per minute):");
    let store = EventStore::new();

    for i in 1..=7 {
        let result = store
            .limit()
            .at_most(ApiEvent::Request, 5, TimeUnit::Minutes)
            .check_and_record(ApiEvent::Request);

        match result {
            Ok(()) => println!("  Request {}: ✓ Allowed", i),
            Err(e) => println!("  Request {}: ✗ Rate limited - {}", i, e),
        }
    }

    // 2. Burst protection with multiple time scales
    println!("\n2. Burst protection (10/min, 50/hour):");
    let store = EventStore::new();

    // Simulate rapid requests
    for i in 1..=12 {
        let result = store
            .limit()
            .at_most(ApiEvent::Request, 10, TimeUnit::Minutes)
            .at_most(ApiEvent::Request, 50, TimeUnit::Hours)
            .check_and_record(ApiEvent::Request);

        if result.is_ok() {
            println!("  Request {}: ✓ Allowed", i);
        } else {
            println!("  Request {}: ✗ Rate limited (burst protection)", i);
        }
    }

    // 3. Check without recording
    println!("\n3. Check limits without recording:");
    let store = EventStore::new();

    // Record some events
    for _ in 0..3 {
        store.record(ApiEvent::Upload);
    }

    // Check if we can make more uploads
    let can_upload = store
        .limit()
        .at_most(ApiEvent::Upload, 5, TimeUnit::Minutes)
        .allowed(ApiEvent::Upload);

    println!("  Uploaded 3 files");
    println!("  Can upload more: {}", can_upload);

    if can_upload {
        store.record(ApiEvent::Upload);
        println!("  Uploaded 1 more file");
    }

    // Check again
    let remaining = store
        .limit()
        .at_most(ApiEvent::Upload, 5, TimeUnit::Minutes)
        .usage(ApiEvent::Upload);

    if let Ok(usage) = remaining {
        println!(
            "  Usage: {}/{} ({} remaining)",
            usage.count, usage.limit, usage.remaining
        );
    }

    // 4. Transactional reservations
    println!("\n4. Transactional reservations (prevent race conditions):");
    let store = EventStore::new();

    // Reserve slot before doing work
    match store
        .limit()
        .at_most(ApiEvent::Request, 10, TimeUnit::Hours)
        .reserve(ApiEvent::Request)
    {
        Ok(reservation) => {
            println!("  Reserved slot for API call");

            // Simulate work
            let success = true;

            if success {
                reservation.commit();
                println!("  API call succeeded - committed");
            } else {
                reservation.cancel();
                println!("  API call failed - cancelled (slot released)");
            }
        }
        Err(e) => println!("  Could not reserve slot: {}", e),
    }

    // 5. Prerequisite checking (at_least)
    println!("\n5. Prerequisite checking (must login first):");
    let store = EventStore::new();

    // Try to access protected endpoint without login
    let result = store
        .limit()
        .at_least(ApiEvent::Login, 1, TimeUnit::Days)
        .check_and_record(ApiEvent::Request);

    println!("  Accessing protected API without login:");
    if result.is_err() {
        println!("    ✗ Denied - login required");
    }

    // Login first
    store.record(ApiEvent::Login);
    println!("  Logged in");

    // Try again
    let result = store
        .limit()
        .at_least(ApiEvent::Login, 1, TimeUnit::Days)
        .check_and_record(ApiEvent::Request);

    println!("  Accessing protected API after login:");
    if result.is_ok() {
        println!("    ✓ Allowed");
    }

    // 6. Cooldown periods
    println!("\n6. Cooldown periods:");
    let store = EventStore::new();

    // First password reset attempt
    let result = store
        .limit()
        .cooldown(ApiEvent::PasswordReset, chrono::Duration::minutes(5))
        .check_and_record(ApiEvent::PasswordReset);

    println!("  First password reset:");
    if result.is_ok() {
        println!("    ✓ Allowed");
    }

    // Immediate second attempt
    let result = store
        .limit()
        .cooldown(ApiEvent::PasswordReset, chrono::Duration::minutes(5))
        .check_and_record(ApiEvent::PasswordReset);

    println!("  Immediate second attempt:");
    if result.is_err() {
        println!("    ✗ Denied - cooldown active (wait 5 minutes)");
    }

    println!("\n✓ Rate limiting complete!");
}