tower-rate-tier
Tier-based rate limiting middleware for Tower.
Every SaaS API needs rate limiting by user plan (free/pro/enterprise). tower-rate-tier eliminates the 200-400 lines of custom middleware you'd otherwise write.
Features
- Named tiers — Define
free,pro,enterprise(or any names) with distinct quotas - Request cost/weight — Expensive endpoints consume more quota (
/export= 20,/search= 5) - Async identifier — Extract
(user_id, tier)from headers, JWT, API keys, or request body - GCRA algorithm — Smooth rate enforcement, no burst-at-boundary issues (used by Stripe, GitHub, Shopify)
- Pluggable storage — In-memory (DashMap) with automatic GC; custom backends via
Storagetrait - Testable clock — Deterministic time control in tests with
FakeClock - Standard headers —
X-RateLimit-Limit,Remaining,Reset(Unix timestamp),Retry-After - Callbacks —
on_limitedfor metrics/logging, custom 429 response builder - Tower-native — Works with Axum, Tonic, Hyper, or any Tower-based framework
Quick Start
Add to your Cargo.toml:
[]
= "0.2"
Define Tiers
use ;
let tier = builder
.tier
.tier
.tier
.default_tier
.build;
Identify Users
With a closure (simple cases):
use ;
let layer = new
.identifier_fn;
Apply to Routes
use ;
use tier_cost;
let app = new
.route // cost: 1 (default)
.route // cost: 5
.route // cost: 20
.route // free (no quota consumed)
.layer;
Rate Limit Response
When a user exceeds their quota, the middleware returns:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710432000
Retry-After: 2450
Content-Type: application/json
{"error":"rate limit exceeded","tier":"free","retry_after":2450}
Custom 429 Response
let layer = new
.identifier_fn
.rate_limited_response;
Metrics / Logging
let layer = new
.identifier_fn
.on_limited;
Optional Features
# Body-based identification (opt-in, buffers request body)
= { = "0.2", = ["buffered-body"] }
Custom Storage Backend
Implement the Storage trait for your own backend:
let custom_storage: = new;
let tier = builder
.tier
.storage // GC disabled automatically for custom backends
.build;
Redis support is planned for v0.3.
Testing
Use FakeClock for deterministic rate limit tests:
use FakeClock;
async
Handling Unidentified Requests
let tier = builder
.on_missing // Use default tier
// .on_missing(OnMissing::Allow) // No rate limiting
// .on_missing(OnMissing::Deny(StatusCode::FORBIDDEN)) // Block
.build;
Storage Error Behavior
let layer = new
.on_storage_error; // Fail open (default)
// .on_storage_error(OnStorageError::Deny); // Fail closed (503)
Comparison
| Feature | tower-governor | tokio-rate-limit | axum_gcra | tower-rate-tier |
|---|---|---|---|---|
| Named tiers | No | No | No | Yes |
| Request cost/weight | No | No | No | Yes |
| Async identifier | No | Partial | No | Yes |
| Body-based identification | No | No | No | Yes |
| Custom storage | No | No | No | Yes |
| Testable clock | No | Yes | No | Yes |
| Tower-compatible | Yes | Axum only | Axum only | Yes |
| Algorithm | GCRA | Token bucket | GCRA | GCRA |
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.