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.