Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra

Rate limiting

A cache-backed rate limiter — the desktop counterpart to Laravel's RateLimiter. Count attempts against a key that expires after a decay window, and throttle once the limit is hit. Needs the Cache (CacheProvider).

use elyra::{command, Ctx, Cache};
use std::time::Duration;

#[command]
async fn login(ctx: Ctx, email: String, password: String) -> Result<Session, String> {
    let limiter = ctx.get::<Cache>().limiter();
    let key = format!("login:{email}");

    if limiter.too_many_attempts(&key, 5) {
        return Err("Too many attempts. Try again later.".into());
    }

    match authenticate(&email, &password) {
        Some(session) => { limiter.clear(&key); Ok(session) }
        None => {
            limiter.hit(&key, Duration::from_secs(60)); // 5 tries / minute
            Err("Invalid credentials.".into())
        }
    }
}

API

Method Does
too_many_attempts(key, max) whether key reached max
hit(key, decay) record an attempt; returns the new count
attempts(key) attempts so far
remaining(key, max) attempts left (never negative)
clear(key) reset the counter
attempt(key, max, decay, callback) run callback if under max (recording a hit), else None

attempt is the concise form:

let limiter = ctx.get::<Cache>().limiter();
match limiter.attempt("send-email", 3, Duration::from_secs(60), || send()) {
    Some(result) => result,
    None => return Err("rate limited".into()),
}

The counter is a cache entry that expires decay after its first hit, so limits reset automatically. In-process (per app run), like the cache itself.

attempt() does the check and the increment under one cache lock (Cache::increment_if_below), so concurrent callers can't both see "under the limit" and push the counter past max.

Related