rust 51 lines · 7 steps

Feature-flag middleware in Axum

A shared, mutable flag set gates routes behind an async Axum middleware factory.

Explained by highlit
1use std::{collections::HashSet, sync::Arc};
2 
3use axum::{
4 body::Body,
5 extract::{Request, State},
6 http::StatusCode,
7 middleware::Next,
8 response::{IntoResponse, Response},
9};
10use tokio::sync::RwLock;
11 
12#[derive(Clone, Default)]
13pub struct FeatureFlags {
14 enabled: Arc<RwLock<HashSet<String>>>,
15}
16 
17impl FeatureFlags {
18 pub async fn is_enabled(&self, flag: &str) -> bool {
19 self.enabled.read().await.contains(flag)
20 }
21 
22 pub async fn set(&self, flag: &str, on: bool) {
23 let mut guard = self.enabled.write().await;
24 if on {
25 guard.insert(flag.to_owned());
26 } else {
27 guard.remove(flag);
28 }
29 }
30}
31 
32pub fn require_flag(
33 flag: &'static str,
34) -> impl Clone + Fn(State<FeatureFlags>, Request, Next) -> BoxedFuture {
35 move |State(flags): State<FeatureFlags>, req: Request, next: Next| {
36 Box::pin(async move {
37 if flags.is_enabled(flag).await {
38 next.run(req).await
39 } else {
40 (
41 StatusCode::NOT_FOUND,
42 format!("feature `{flag}` is not available"),
43 )
44 .into_response()
45 }
46 })
47 }
48}
49 
50type BoxedFuture =
51 std::pin::Pin<Box<dyn std::future::Future<Output = Response<Body>> + Send>>;
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1An Arc<RwLock<...>> lets many handlers read shared state concurrently while still allowing exclusive updates.
  2. 2Returning a closure from a function lets you parameterize middleware — here, per-flag gating — while keeping one implementation.
  3. 3Axum middleware can either forward the request via next.run or short-circuit with its own response.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Feature-flag middleware in Axum — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code