Decisions
Every rate limiting call (inc(), is_allowed()) returns a RateLimitDecision. This enum has three variants. Understanding them is essential to using Trypema correctly.
The RateLimitDecision enum
pub enum RateLimitDecision {
Allowed,
Rejected {
window_size_seconds: u64,
retry_after_ms: u128,
remaining_after_waiting: u64,
},
Suppressed {
suppression_factor: f64,
is_allowed: bool,
},
}
Allowed
The request is within the rate limit. The increment has been recorded in the limiter's state.
Returned by:
- Absolute strategy: when
total_count < window_capacity - Suppressed strategy: when
accepted_usage < window_capacity(i.e., below the soft limit)
What to do: Proceed with the request.
use trypema::RateLimitDecision;
let decision = RateLimitDecision::Allowed;
match decision {
RateLimitDecision::Allowed => {
// Safe to proceed with the request.
}
_ => {}
}
Rejected
The request exceeds the rate limit and should not proceed. The increment was not recorded -- the limiter does not count rejected requests against the key.
Returned by: The absolute strategy only. The suppressed strategy never returns Rejected.
Fields
| Field | Type | Description |
|---|---|---|
window_size_seconds | u64 | The sliding window size used for this decision (in seconds). |
retry_after_ms | u128 | Best-effort estimate of milliseconds until capacity becomes available. Computed from the oldest active bucket's remaining TTL. |
remaining_after_waiting | u64 | Best-effort estimate of how many requests will still be counted in the window after retry_after_ms elapses. Computed as total_count - oldest_bucket_count. |
How the hints are computed
retry_after_ms: The oldest active bucket in the sliding window has a known timestamp. The time remaining until that bucket expires out of the window iswindow_size_ms - elapsed_ms. This is the estimate of when the first capacity will free up.remaining_after_waiting: Once the oldest bucket expires, the remaining requests in the window will betotal_count - oldest_bucket_count. If all activity is coalesced into the oldest bucket, this will be0. If activity is spread across many buckets, this gives a rough sense of how much capacity will remain.
Retry-After header), not as strict guarantees.use trypema::RateLimitDecision;
let decision = RateLimitDecision::Rejected {
window_size_seconds: 60,
retry_after_ms: 2500,
remaining_after_waiting: 45,
};
match decision {
RateLimitDecision::Rejected {
retry_after_ms,
remaining_after_waiting,
..
} => {
// Send HTTP 429 with:
// Retry-After: 3 (round up 2500ms to seconds)
// Optionally log:
// "After waiting, ~45 requests will still be in the window"
println!("Retry in ~{}ms", retry_after_ms);
println!("~{} requests will remain in window", remaining_after_waiting);
}
_ => {}
}
Suppressed
Returned by the suppressed strategy only. Indicates that probabilistic suppression is active because the key's observed rate is at or above its target capacity.
The absolute strategy never returns Suppressed. The suppressed strategy never returns Rejected.
Fields
| Field | Type | Description |
|---|---|---|
suppression_factor | f64 | The current suppression rate, from 0.0 (no suppression) to 1.0 (full suppression / all requests denied). |
is_allowed | bool | Whether this specific call was admitted. Always use this field as the admission decision. |
How is_allowed works
- When
suppression_factoris0.0,is_allowedis alwaystrue. - When
suppression_factoris1.0,is_allowedis alwaysfalse(full suppression, over the hard limit). - When
0.0 < suppression_factor < 1.0,is_allowedis determined probabilistically: each call is admitted with probability1.0 - suppression_factor.
Counter tracking
The suppressed strategy always increments the total (observed) counter, regardless of the decision. If is_allowed is false, it also increments the declined counter. This means:
- Total = all calls seen (offered load)
- Declined = calls denied by suppression
- Accepted = total - declined (actual throughput)
use trypema::RateLimitDecision;
let decision = RateLimitDecision::Suppressed {
suppression_factor: 0.3,
is_allowed: true,
};
match decision {
RateLimitDecision::Suppressed { is_allowed, suppression_factor } => {
if is_allowed {
// Proceed with the request.
// suppression_factor tells you how close the key is to its limit.
println!("Allowed (suppression at {:.0}%)", suppression_factor * 100.0);
} else {
// Do NOT proceed. This request was denied.
println!("Denied (suppression at {:.0}%)", suppression_factor * 100.0);
}
}
_ => {}
}
Which strategy returns which variants?
| Variant | Absolute strategy | Suppressed strategy |
|---|---|---|
Allowed | Yes (under capacity) | Yes (under capacity) |
Rejected | Yes (over capacity) | Never |
Suppressed | Never | Yes (at/over capacity) |
Complete example: handling all three variants
use trypema::RateLimitDecision;
fn handle_decision(decision: RateLimitDecision) {
match decision {
RateLimitDecision::Allowed => {
// Request is within limits. Proceed.
}
RateLimitDecision::Rejected { retry_after_ms, remaining_after_waiting, .. } => {
// Absolute strategy: request denied.
// Use retry_after_ms as a backoff hint.
eprintln!(
"Rate limited. Retry in ~{}ms (~{} requests will remain).",
retry_after_ms, remaining_after_waiting
);
}
RateLimitDecision::Suppressed { is_allowed, suppression_factor } => {
// Suppressed strategy: check is_allowed.
if is_allowed {
// Proceed, but note the suppression level for metrics.
println!("Allowed (suppression: {:.1}%)", suppression_factor * 100.0);
} else {
// Do NOT proceed.
eprintln!("Suppressed (factor: {:.1}%)", suppression_factor * 100.0);
}
}
}
}
Next steps
- Absolute Strategy -- when and how
Rejectedis returned - Suppressed Strategy -- when and how
Suppressedis returned - How Suppression Works -- deep dive into the suppression algorithm

