Concepts

Decisions

The three variants of RateLimitDecision — Allowed, Rejected, and Suppressed — and what each field means.

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

FieldTypeDescription
window_size_secondsu64The sliding window size used for this decision (in seconds).
retry_after_msu128Best-effort estimate of milliseconds until capacity becomes available. Computed from the oldest active bucket's remaining TTL.
remaining_after_waitingu64Best-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 is window_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 be total_count - oldest_bucket_count. If all activity is coalesced into the oldest bucket, this will be 0. If activity is spread across many buckets, this gives a rough sense of how much capacity will remain.
Both hints are approximate. Bucket coalescing merges nearby increments, reducing granularity. Concurrent requests can change bucket ages and totals. Use these values for backoff guidance (e.g., HTTP 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

FieldTypeDescription
suppression_factorf64The current suppression rate, from 0.0 (no suppression) to 1.0 (full suppression / all requests denied).
is_allowedboolWhether this specific call was admitted. Always use this field as the admission decision.

How is_allowed works

  • When suppression_factor is 0.0, is_allowed is always true.
  • When suppression_factor is 1.0, is_allowed is always false (full suppression, over the hard limit).
  • When 0.0 < suppression_factor < 1.0, is_allowed is determined probabilistically: each call is admitted with probability 1.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?

VariantAbsolute strategySuppressed strategy
AllowedYes (under capacity)Yes (under capacity)
RejectedYes (over capacity)Never
SuppressedNeverYes (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