shared-framework 0.0.17

Reusable building blocks for HTTP services — Hyper routing, SeaORM data layer, validation, OpenAPI docs, jobs, queues, cache.
Documentation
//! General-purpose middlewares for the router.
//!
//! Currently provides [`rate_limiter`], a per-caller request-cap hook. Attach the
//! returned [`Middleware`](crate::controller::Middleware) to a route or pass it as a
//! global middleware so over-limit requests fail with a 429 before the handler runs.
//! ```ignore
//! let mw = rate_limiter(limiter, 100);
//! self.mount_get(&mut router, "/list", desc, handler, vec![mw]);
//! ```

use crate::logging::CorrelationContext;
use crate::response::ErrorResult;
use futures::FutureExt;

/// Builds middleware enforcing `limit` requests per minute per caller.
/// The caller key is the context's user id, or the `x-forwarded-for` header value
/// falling back to `"anonymous"`. Over-limit requests fail with a 429 `ErrorResult`.
pub fn rate_limiter(
    limiter: std::sync::Arc<dyn crate::middleware::rate::RateLimiter>,
    limit: u32,
) -> crate::controller::Middleware {
    std::sync::Arc::new(move |ctx: &mut CorrelationContext| {
        let headers = ctx.headers();
        let cloned = limiter.clone();
        async move {
            let key = ctx.user_id().unwrap_or_else(|| {
                headers
                    .get("x-forwarded-for")
                    .and_then(|v| v.to_str().ok())
                    .unwrap_or("anonymous")
                    .to_string()
            }) + ":"
                + &base64::Engine::encode(&base64::engine::general_purpose::STANDARD, "path");
            if !cloned.is_allowed(&key, limit).await {
                return Err(ErrorResult::new("Too many requests", None, 429));
            }
            Ok(())
        }
        .boxed()
    })
}