Skip to main content

Rocket Integration

toggly-rocket initializes a client during ignition and makes it available to the native Feature request guard. The application decides what response to return when a flag is false.

The adapter retains Rocket 0.5 support and is currently tested with Rocket 0.5.1 on Rust 1.88. Use a Tokio runtime and close the shared client during Rocket shutdown; do not create a client per request.

Installation​

[dependencies]
toggly = "0.6"
toggly-rocket = "0.6"
rocket = "0.5"

Setup with Fairing​

Fairing Options​

Use TogglyFairing::new(app_key, environment) for defaults, or TogglyFairing::from_config(config) for custom configuration. Build refresh/cache/URL settings through TogglyConfig::builder(); these setters are not methods on the fairing.

Manual Setup​

Alternatively, build one client before Rocket startup and register it with .manage(client). Do not also attach the initialization fairing when manually managing that client. In either approach, add explicit shutdown handling as shown in the complete example.

Feature Request Guard​

The native type is Feature<'_>. Extracting it locates the client and constructs an identity-only context. Extraction does not itself require a particular flag to be enabled.

Multiple Features​

Feature::is_enabled and is_disabled return bool. For a multi-key check, use feature.client().evaluate_gate(...) with the required context and Requirement. A false result must be handled by the application.

Access Context​

Access Client​

This native handler supplies explicit claims before evaluation:

use rocket::{get, http::Status};
use toggly::EvalContext;
use toggly_rocket::Feature;

#[get("/context")]
pub async fn contextual(feature: Feature<'_>) -> Result<&'static str, Status> {
// Replace example principal data with authenticated request state.
let context = EvalContext::builder()
.identity("alice").claim("role", "admin").build();
match feature.client().is_enabled("filter-user-claims", context).await {
Ok(true) => Ok("Condition matched"),
Ok(false) => Ok("Condition did not match"),
Err(_) => Err(Status::InternalServerError),
}
}

Mount it with routes![contextual]. For HTTP segments and Order entities, construct the complete evaluation context before calling the client. This does not replace the native guard's own stored context.

FeatureEnabled Guard​

FeatureDisabled Guard​

The exported FeatureEnabled and FeatureDisabled structs do not implement Rocket's request-guard trait. Do not use them as handler parameters. Use the actual Feature<'_> guard and explicitly check enabled/disabled state, as below.

Identity from Headers​

The native Feature guard reads x-user-id first, then x-identity. These headers are inputs, not authentication. It does not populate groups, claims, request segments or entities. Obtain those from trusted application state for explicit evaluations.

Route-Based Feature Flags​

A route protected by an application if check can return404,403 or a fallback view according to its product requirements. The example returns 404 for the gated API and classic content for a false dashboard flag. Both responses come from application code.

Error Handling​

Feature.is_enabled converts evaluation errors to false. Feature.client().is_enabled returns the SDK Result for explicit handling. An initial configuration/definition-fetch error fails the initialization fairing; do not describe that as a disabled flag.

Complete Example​

Set TOGGLY_APP_KEY locally and create new-dashboard and api-v2 in Production. Put this in src/main.rs; cargo run serves Rocket's default http://localhost:8000.

use rocket::{fairing::AdHoc, get, http::Status, routes};
use toggly::{TogglyClient, TogglyConfig};
use toggly_rocket::{Feature, TogglyFairing};

#[get("/")]
async fn index(feature: Feature<'_>) -> &'static str {
if feature.is_enabled("new-dashboard").await {
"New dashboard"
} else {
"Classic dashboard"
}
}

#[get("/api/v2")]
async fn api_v2(feature: Feature<'_>) -> Result<&'static str, Status> {
if feature.is_enabled("api-v2").await {
Ok("API v2")
} else {
Err(Status::NotFound)
}
}

#[rocket::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = TogglyConfig::builder()
.app_key(std::env::var("TOGGLY_APP_KEY")?)
.environment("Production").build();
rocket::build()
.attach(TogglyFairing::from_config(config))
// The SDK fairing initializes only; the application owns shutdown.
.attach(AdHoc::on_shutdown("Close Toggly", |rocket| Box::pin(async move {
if let Some(client) = rocket.state::<TogglyClient>() {
client.close().await;
}
})))
.mount("/", routes![index, api_v2])
.launch().await?;
Ok(())
}

Rocket's normal shutdown lifecycle invokes the hook after its shutdown grace periods. Keep work within that request lifecycle and avoid detached tasks continuing to use a closed client.

Testing​

Use the local definition fixture, pass its config to TogglyFairing::from_config, then create rocket::local::asynchronous::Client::tracked(...). Dispatch actual requests and call client.terminate().await to run shutdown fairings. A fake-looking app key with the default remote URL is not an offline test.

Run the Rust Rocket SDK workshop for a complete application with the native Feature guard, application-supplied request context, Order and filter examples. Its local definitions fixture works without a live Toggly application. See caching for definition and decision freshness boundaries.