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 field | Input for |
|---|---|
identity | Percentage and user targeting |
groups | Group targeting |
claims | UserClaims |
request | Country, browser, language, device and OS filters |
traits | ContextualTargeting |
entity | ContextProperty |
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.