Skip to main content

Feature Evaluation

Evaluate feature keys using one shared TogglyClient and an owned context for the current operation. Feature flags select behavior; your application's authentication and authorization remain separate.

Basic Evaluation​

Check if Enabled​

Check if Disabled​

Check if Defined​

This function accepts an already initialized client and context, so it can be called from a handler or a background job:

use toggly::{EvalContext, TogglyClient};

pub async fn describe(
client: &TogglyClient,
context: EvalContext,
) -> toggly::Result<(bool, bool, bool)> {
let enabled = client.is_enabled("new-dashboard", context.clone()).await?;
let disabled = client.is_disabled("new-dashboard", context).await?;
// Defined means a key exists, even when its conditions evaluate false.
let defined = client.is_defined("new-dashboard").await;
Ok((enabled, disabled, defined))
}

An undefined flag returns false with the default configuration; is_disabled therefore returns true. Errors use toggly::Result; do not treat every transport or configuration error as a missing flag.

Evaluation Context​

Identity​

Use a stable application identity for targeting and percentage rollouts. A demonstration identity such as alice is not authentication.

Groups​

.groups(...) takes Vec<String>; .group(...) appends a single group.

Traits​

Traits are arbitrary JSON values for ContextualTargeting. Claims and request fields have separate containers and are not automatically inferred from traits.

Full Example​

use toggly::{EvalContext, HttpRequestMapper};

pub fn authenticated_context(user_id: &str) -> EvalContext {
// Replace these example memberships and claims with verified auth data.
EvalContext::builder()
.identity(user_id)
.groups(vec!["beta-testers".into(), "enterprise".into()])
.claim("role", "admin")
.trait_value("plan", "enterprise")
.request(HttpRequestMapper::from_http_headers([
("user-agent", "Mozilla/5.0 Chrome/128.0.0.0 Safari/537.36"),
("accept-language", "en-US,en;q=0.9"),
("cf-ipcountry", "US"),
]))
.build()
}
Context fieldInput for
identityPercentage and user targeting
groupsGroup targeting
claimsUserClaims
requestCountry, browser, language, device and OS filters
traitsContextualTargeting
entityContextProperty

Mapping HTTP headers​

HttpRequestMapper::from_http_headers reads User-Agent, Accept-Language and country headers. Country precedence is CF-IPCountry, X-Vercel-IP-Country, then CloudFront-Viewer-Country. Trust country headers only when your proxy controls them. merge_into(headers, Some(&base)) clones the base context and replaces its request fields with mapped values; it retains the base identity, groups, claims, traits and entity.

Native Actix/Axum/Rocket Feature helpers map identity headers only. They do not automatically apply the complete context above to their own checks. Use the adapter's direct client access and pass the enriched context explicitly, as shown in each guide. Preparing context does not fetch definitions or invalidate an existing cached decision; see Caching.

Feature Gates​

use toggly::{EvalContext, Requirement, TogglyClient};

pub async fn gates(
client: &TogglyClient,
context: EvalContext,
) -> toggly::Result<(bool, bool, bool)> {
let keys = ["new-dashboard", "api-v2"];
let all = client.evaluate_gate(
&keys, Requirement::All, context.clone(), false,
).await?;
let any = client.evaluate_gate(
&keys, Requirement::Any, context.clone(), false,
).await?;
// Negation applies to each flag before combining the results.
let both_disabled = client.evaluate_gate(
&keys, Requirement::All, context, true,
).await?;
Ok((all, any, both_disabled))
}

An empty feature-key list returns false, including for All and negated gates.

Built-in Filter Evaluators​

AlwaysOn / AlwaysOff​

Use these for a deterministic first flag: they evaluate to true and false respectively.

Percentage​

{"name":"Percentage","parameters":{"Value":50}}

A partial rollout uses the feature key and identity to compute a stable bucket. Provide a stable identity for partial rollouts.

Targeting​

{"name":"Targeting","parameters":{"Users":["alice"],"Groups":["beta-testers"]}}

The served Users and Groups fields contain literal values. The SDK also understands flattened Audience.Users: and Audience.Groups: entries; do not add an unused Audiences field.

TimeWindow​

{"name":"TimeWindow","parameters":{"Start":"2026-01-01T00:00:00Z","End":"2030-01-01T00:00:00Z"}}

Choose timestamps for your actual rollout window; these are example UTC boundaries.

ContextualTargeting​

{
"name": "ContextualTargeting",
"parameters": {
"Conditions": [
{"trait": "plan", "operator": "Equals", "value": "enterprise"}
]
}
}

This built-in evaluator compares context traits; it is not a custom evaluator registration example.

HTTP segment filters + UserClaims​

Country, BrowserFamily, BrowserLanguage, DeviceType and OS evaluate structured request fields. UserClaims uses principal claims. Segment rules require their served Percentage parameter as well as their matching fields. See the SDK filter matrix for filter families and entity context for the user/entity distinction.

Custom Filter Evaluators​

The Rust SDK does not expose a public trait for implementing and registering an external custom evaluator. Use the built-in filters and their documented context fields; do not implement an invented FilterEvaluator or access private registry modules.

Listing Features​

client.feature_keys().await lists loaded keys. Repeated is_enabled calls are individual evaluations, not an atomic evaluated snapshot. Rust has no variant-assignment or evaluated-snapshot import/export method.

Entity context​

An Order is the object being evaluated; it is separate from the user. Register the mapper and optional catalog schema once at startup. The mapper below accepts only your application's Order type. Pass the mapped instance with the user context before evaluating ExpressCheckout.

Add serde_json = "1" beside the quick-start dependencies. This is a complete src/main.rs example:

use std::collections::HashMap;
use toggly::{
map_entity, register_context, EntityContextPropertySchema,
EntityContextSchemaRegistration, EvalContext, TogglyClient,
TogglyEntityContext,
};

struct Order {
id: String,
vip: bool,
total: Option<f64>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
register_context("Order", |value| {
let order = value.downcast_ref::<Order>().expect("Order mapper input");
let mut attributes = HashMap::from([
("Id".into(), serde_json::json!(order.id)),
("Vip".into(), serde_json::json!(order.vip)),
]);
if let Some(total) = order.total {
attributes.insert("Total".into(), serde_json::json!(total));
}
TogglyEntityContext {
kind: "Order".into(), key: order.id.clone(), attributes,
}
}, Some(EntityContextSchemaRegistration {
kind: "Order".into(), key_property: "Id".into(),
display_name: Some("Order".into()),
properties: vec![
EntityContextPropertySchema {
name: "Id".into(), type_name: "string".into(),
},
EntityContextPropertySchema {
name: "Vip".into(), type_name: "boolean".into(),
},
EntityContextPropertySchema {
name: "Total".into(), type_name: "number".into(),
},
],
}));

let order = Order { id: "order-1".into(), vip: true, total: Some(120.0) };
let context = EvalContext::builder()
.identity("alice")
.entity(map_entity("Order", &order).expect("Order mapper registered"))
.build();
let client = TogglyClient::builder()
.app_key(std::env::var("TOGGLY_APP_KEY")?)
.environment("Production")
.build().await?;
let result = client.is_enabled("ExpressCheckout", context).await;
client.close().await;
println!("Express checkout: {}", result?);
Ok(())
}

Configure ExpressCheckout with Order context and a ContextProperty condition matching Vip to true. Registration supplies schema metadata; it does not create that flag or its condition. Total is optional in your domain data, so the mapper omits it when absent. Disable catalog upload through TogglyConfigBuilder::disable_entity_context_registration(true) when managing schemas separately; local mapping still works.

Manual Refresh​

client.refresh().await? explicitly refreshes definitions and clears decision entries after a successful refresh.

Clear Cache​

client.clear_cache().await clears both decisions and in-memory definitions/JWKS. Call client.refresh().await? before expecting existing definitions to be available again. It does not remove a disk snapshot: the client has no disk-snapshot persistence API.