Skip to main content

Actix-web Integration

Use toggly-actix for native feature extractors and middleware. The application owns one shared client; each request carries its own evaluation context.

Installation​

This adapter supports Actix-web 4.4 through 4.15. The current tested host is Actix-web 4.15.0. Declare the compatible host range alongside the adapter:

[dependencies]
toggly = "0.6"
toggly-actix = "0.6"
actix-web = ">=4.4, <4.16"

Setup​

Create web::Data<TogglyClient> once outside HttpServer::new, then clone the Data handle into workers. TogglyClient itself is not Clone. Native extractors look up exactly web::Data<TogglyClient>; web::Data<Arc<TogglyClient>> is a different registration.

Feature Extractor​

Feature exposes is_enabled, is_disabled and context. Its checks return bool, converting an evaluation error to false. Use direct TogglyData access when you need to handle the SDK's Result explicitly.

Multiple Features​

Access Context​

The complete example below checks the authenticated-header demonstration identity from Feature.context(). For a multi-key gate, call TogglyData::evaluate_gate(keys, Requirement, context, negate) through its client dereference.

TogglyData Extractor​

The native TogglyData extractor exposes the shared client's API. This handler maps HTTP fields and adds an illustrative claim before its evaluation:

use actix_web::{HttpRequest, HttpResponse};
use toggly::{EvalContext, HttpRequestMapper};
use toggly_actix::TogglyData;

pub async fn contextual(request: HttpRequest, client: TogglyData) -> HttpResponse {
// For a real application, obtain identity/claims from verified auth state.
let base = EvalContext::builder().identity("alice").claim("role", "admin").build();
let headers = request.headers().iter().filter_map(|(name, value)| {
value.to_str().ok().map(|value| (name.as_str(), value))
});
let context = HttpRequestMapper::merge_into(headers, Some(&base));
match client.is_enabled("filter-user-claims", context).await {
Ok(true) => HttpResponse::Ok().body("Condition matched"),
Ok(false) => HttpResponse::Ok().body("Condition did not match"),
Err(error) => HttpResponse::InternalServerError().body(error.to_string()),
}
}

Register it with .route("/context", web::get().to(contextual)). Mapping this context does not change other native middleware/extractor evaluations in the request; pass the full context explicitly for those checks. Only trust proxy-provided country headers when your proxy controls them.

Middleware​

Middleware Options​

Use TogglyMiddleware::with_feature("api-v2"); .negate() requires a false result and .identity_header("x-user-id") configures the middleware's identity input. new() has no feature key and simply passes requests through. There are no require or deny factory methods on this middleware.

Middleware Responses​

With the client registered, a satisfied condition continues to the handler; an unsatisfied condition returns 404. Configure and verify the shared client registration before serving requests. Feature gates are not a substitute for access control.

Route Guards​

FeatureGuard::new(key) is a definition-existence guard. A defined flag whose condition is false still satisfies it. .negate() and FeatureGuard::disabled(key) invert that existence check; the latter name does not mean asynchronous disabled-state evaluation. Use middleware or Feature for a dynamic feature gate.

Identity from Headers​

The native Feature extractor checks x-user-id, then x-identity. Middleware reads only its explicitly configured identity header. These are demonstration/transport inputs, not authentication: in production, use values supplied by a trusted authenticated boundary. Neither helper automatically maps groups, claims, request segment fields or entities.

Complete Example​

Set TOGGLY_APP_KEY locally and create new-dashboard and api-v2 flags in Production. Put this in src/main.rs; cargo run serves http://localhost:8080. / shows the selected view, /api/v2 returns 404 when its flag is false.

use actix_web::{web, App, HttpResponse, HttpServer};
use toggly::TogglyClient;
use toggly_actix::{Feature, TogglyMiddleware};

async fn index(feature: Feature) -> HttpResponse {
if feature.is_enabled("new-dashboard").await {
HttpResponse::Ok().body("New dashboard")
} else {
HttpResponse::Ok().body("Classic dashboard")
}
}

async fn api_v2() -> HttpResponse {
HttpResponse::Ok().body("API v2")
}

#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TogglyClient::builder()
.app_key(std::env::var("TOGGLY_APP_KEY")?)
.environment("Production")
.build().await?;
let owner = web::Data::new(client);
let workers = owner.clone();
let bound = HttpServer::new(move || {
App::new()
.app_data(workers.clone())
.route("/", web::get().to(index))
.service(web::resource("/api/v2")
.wrap(TogglyMiddleware::with_feature("api-v2")
.identity_header("x-user-id"))
.route(web::get().to(api_v2)))
}).bind(("127.0.0.1", 8080));

// The server drains requests during normal graceful shutdown.
// Close the client on a bind/serve error as well as a normal stop.
let result = match bound {
Ok(server) => server.run().await,
Err(error) => Err(error),
};
owner.close().await;
result?;
Ok(())
}

For complete flag setup recipes, see the Samples catalog. Learn Order mapping, decision retention, and deterministic testing.