pub mod algorithm;
pub mod decision;
pub mod error;
pub mod extensions;
pub mod headers;
pub mod key;
pub mod manager;
pub mod policy;
pub mod quota;
pub mod storage;
#[cfg(feature = "axum")]
pub mod middleware;
pub use algorithm::Algorithm;
pub use decision::{Decision, DecisionMetadata, RateLimitInfo};
pub use error::{ConfigError, ConnectionError, RateLimitError, Result, StorageError};
pub use key::{CompositeKey, FnKey, GlobalKey, Key, StaticKey};
pub use manager::{RateLimitManager, RateLimitManagerBuilder, RouteConfig};
pub use quota::{Quota, QuotaBuilder};
pub use storage::{Storage, StorageEntry};
pub use policy::{CompositePolicy, CreditPolicy, DefaultPolicy, PenaltyPolicy, Policy};
pub use extensions::{RateLimitExt, RateLimitResponse};
pub use headers::RateLimitHeaders;
pub use algorithm::{FixedWindow, SlidingWindow, TokenBucket};
#[cfg(feature = "gcra")]
pub use algorithm::GCRA;
#[cfg(feature = "leaky-bucket")]
pub use algorithm::LeakyBucket;
#[cfg(feature = "sliding-log")]
pub use algorithm::SlidingLog;
#[cfg(feature = "concurrent")]
pub use algorithm::ConcurrentLimiter;
#[cfg(feature = "memory")]
pub use storage::{GcConfig, GcInterval, MemoryStorage};
pub mod prelude {
pub use crate::algorithm::Algorithm;
pub use crate::decision::{Decision, RateLimitInfo};
pub use crate::error::{RateLimitError, Result};
pub use crate::quota::Quota;
pub use crate::storage::Storage;
pub use crate::algorithm::{FixedWindow, SlidingWindow, TokenBucket};
#[cfg(feature = "gcra")]
pub use crate::algorithm::GCRA;
#[cfg(feature = "leaky-bucket")]
pub use crate::algorithm::LeakyBucket;
#[cfg(feature = "sliding-log")]
pub use crate::algorithm::SlidingLog;
#[cfg(feature = "concurrent")]
pub use crate::algorithm::ConcurrentLimiter;
#[cfg(feature = "memory")]
pub use crate::storage::{GcConfig, GcInterval, MemoryStorage};
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "memory")]
#[tokio::test]
async fn test_integration_gcra() {
use crate::prelude::*;
let storage = MemoryStorage::new();
let algorithm = GCRA::new();
let quota = Quota::per_second(10).with_burst(5);
for i in 1..=5 {
let decision = algorithm
.check_and_record(&storage, "user:1", "a)
.await
.unwrap();
assert!(decision.is_allowed(), "Request {} should be allowed", i);
}
let decision = algorithm
.check_and_record(&storage, "user:1", "a)
.await
.unwrap();
assert!(decision.is_denied());
assert!(decision.info().retry_after.is_some());
}
#[cfg(feature = "memory")]
#[tokio::test]
async fn test_integration_token_bucket() {
let storage = MemoryStorage::new();
let algorithm = TokenBucket::new();
let quota = Quota::per_minute(60).with_burst(10);
let decision = algorithm
.check_and_record(&storage, "user:1", "a)
.await
.unwrap();
assert!(decision.is_allowed());
assert_eq!(decision.info().remaining, 9);
assert_eq!(decision.info().algorithm, Some("token_bucket"));
}
#[cfg(feature = "memory")]
#[tokio::test]
async fn test_integration_headers() {
let storage = MemoryStorage::new();
let algorithm = FixedWindow::new();
let quota = Quota::per_minute(100);
let decision = algorithm
.check_and_record(&storage, "user:1", "a)
.await
.unwrap();
let headers = decision.info().to_headers();
assert!(headers.iter().any(|(k, _)| *k == "X-RateLimit-Limit"));
assert!(headers.iter().any(|(k, _)| *k == "X-RateLimit-Remaining"));
assert!(headers.iter().any(|(k, _)| *k == "X-RateLimit-Reset"));
}
#[cfg(all(feature = "memory", feature = "concurrent"))]
#[tokio::test]
async fn test_integration_concurrent() {
let limiter = ConcurrentLimiter::new(2);
let _permit1 = limiter.try_acquire("user:1").unwrap();
let _permit2 = limiter.try_acquire("user:1").unwrap();
assert!(limiter.try_acquire("user:1").is_none());
assert_eq!(limiter.remaining("user:1"), 0);
}
}