use tiny_counter::{EventId, EventStore, TimeUnit};
#[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");
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),
}
}
println!("\n2. Burst protection (10/min, 50/hour):");
let store = EventStore::new();
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);
}
}
println!("\n3. Check limits without recording:");
let store = EventStore::new();
for _ in 0..3 {
store.record(ApiEvent::Upload);
}
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");
}
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
);
}
println!("\n4. Transactional reservations (prevent race conditions):");
let store = EventStore::new();
match store
.limit()
.at_most(ApiEvent::Request, 10, TimeUnit::Hours)
.reserve(ApiEvent::Request)
{
Ok(reservation) => {
println!(" Reserved slot for API call");
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),
}
println!("\n5. Prerequisite checking (must login first):");
let store = EventStore::new();
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");
}
store.record(ApiEvent::Login);
println!(" Logged in");
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");
}
println!("\n6. Cooldown periods:");
let store = EventStore::new();
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");
}
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!");
}