toggly 0.1.0

High-performance Rust SDK for Toggly feature flags
Documentation

Toggly Rust SDK

High-performance Rust SDK for Toggly feature flags and experimentation platform.

Crates.io Documentation License: MIT CI

Features

  • Async-first: Built on Tokio for efficient async operations
  • Type-safe: Leverages Rust's type system for compile-time safety
  • Thread-safe: Safe concurrent access with Arc and DashMap
  • Zero-cost abstractions: Minimal overhead with idiomatic Rust patterns
  • Framework integrations: Native support for Actix, Axum, and Rocket
  • Procedural macros: Declarative feature flag guards
  • In-memory caching: Built-in caching with configurable TTL
  • WebAssembly support: Works in browser environments

Crates

Crate Description Crates.io
toggly Core SDK with minimal dependencies crates.io
toggly-macros Procedural macros for feature flags crates.io
toggly-actix Actix-web integration crates.io
toggly-axum Axum integration crates.io
toggly-rocket Rocket integration crates.io

Installation

Add the core crate to your Cargo.toml:

[dependencies]
toggly = "0.1"

For framework integrations:

# Actix-web
toggly-actix = "0.1"

# Axum
toggly-axum = "0.1"

# Rocket
toggly-rocket = "0.1"

# Macros
toggly-macros = "0.1"

Quick Start

Basic Usage

use toggly::{TogglyClient, EvalContext};

#[tokio::main]
async fn main() -> Result<(), toggly::Error> {
    // Create client with builder pattern
    let client = TogglyClient::builder()
        .app_key("your-app-key")
        .environment("production")
        .build()
        .await?;

    // Check if a feature is enabled
    let enabled = client.is_enabled("my-feature", EvalContext::default()).await?;

    if enabled {
        println!("Feature is enabled!");
    }

    // Cleanup
    client.close().await;

    Ok(())
}

With User Context

use toggly::{TogglyClient, EvalContext};
use std::collections::HashMap;

async fn check_feature(client: &TogglyClient, user_id: &str) -> bool {
    let mut traits = HashMap::new();
    traits.insert("plan".to_string(), serde_json::json!("premium"));
    traits.insert("country".to_string(), serde_json::json!("US"));

    let context = EvalContext::builder()
        .identity(user_id)
        .groups(vec!["beta-testers".to_string()])
        .traits(traits)
        .build();

    client.is_enabled("premium-feature", context).await.unwrap_or(false)
}

Feature Gates

Evaluate multiple features with AND/OR logic:

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

async fn check_access(client: &TogglyClient) -> bool {
    // All features must be enabled
    client
        .evaluate_gate(
            &["feature-a", "feature-b"],
            Requirement::All,
            EvalContext::default(),
            false,
        )
        .await
        .unwrap_or(false)
}

Framework Integrations

Actix-web

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

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let client = TogglyClient::builder()
        .app_key("your-app-key")
        .environment("production")
        .build()
        .await
        .expect("Failed to create client");

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(client.clone()))
            .route("/", web::get().to(index))
            .route("/beta", web::get().to(beta_handler))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

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

// Route requires feature to be enabled
async fn beta_handler(_: FeatureEnabled<"beta-features">) -> HttpResponse {
    HttpResponse::Ok().body("Beta Content")
}

Axum

use axum::{routing::get, Router, Extension};
use std::sync::Arc;
use toggly::TogglyClient;
use toggly_axum::{Feature, TogglyLayer};

#[tokio::main]
async fn main() {
    let client = TogglyClient::builder()
        .app_key("your-app-key")
        .environment("production")
        .build()
        .await
        .expect("Failed to create client");

    let app = Router::new()
        .route("/", get(index))
        .route("/beta", get(beta_handler).layer(TogglyLayer::require("beta-features")))
        .layer(Extension(Arc::new(client)));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn index(feature: Feature) -> &'static str {
    if feature.is_enabled("new-homepage").await {
        "New Homepage"
    } else {
        "Classic Homepage"
    }
}

async fn beta_handler() -> &'static str {
    "Beta Content"
}

Rocket

use rocket::{get, launch, routes, State};
use toggly::TogglyClient;
use toggly_rocket::{Feature, FeatureEnabled, TogglyFairing};

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

#[get("/beta")]
async fn beta(_: FeatureEnabled<"beta-features">) -> &'static str {
    "Beta Content"
}

#[launch]
async fn rocket() -> _ {
    rocket::build()
        .attach(TogglyFairing::new("your-app-key", "production"))
        .mount("/", routes![index, beta])
}

Procedural Macros

Use the toggly-macros crate for declarative feature guards:

use toggly_macros::{feature_flag, FeatureFlags};

#[feature_flag(feature = "premium-feature")]
async fn premium_function() -> String {
    "Premium content".to_string()
}

// Define feature flags as an enum
#[derive(FeatureFlags)]
pub enum Features {
    #[feature(key = "dark-mode")]
    DarkMode,

    #[feature(key = "new-dashboard", default = true)]
    NewDashboard,

    #[feature(key = "beta-features")]
    BetaFeatures,
}

// Usage
async fn example(client: &TogglyClient) {
    let enabled = Features::DarkMode.is_enabled(client, Default::default()).await.unwrap();
}

Configuration Options

use std::time::Duration;
use toggly::TogglyConfig;

let config = TogglyConfig::builder()
    .app_key("your-app-key")
    .environment("production")
    .base_url("https://app.toggly.io/")
    .definitions_url("https://definitions.toggly.io/")
    .refresh_interval(Duration::from_secs(300))
    .http_timeout(Duration::from_secs(10))
    .cache_ttl(Duration::from_secs(60))
    .cache_max_entries(10_000)
    .enable_undefined_in_dev(true)
    .disable_background_refresh(false)
    .build();

Custom Filter Evaluators

Register custom filter evaluators for advanced targeting:

use toggly::eval::{Evaluator, Registry};
use toggly::{EvalContext, definitions::FeatureFilter};
use std::sync::Arc;

struct MyCustomEvaluator;

impl Evaluator for MyCustomEvaluator {
    fn evaluate(
        &self,
        feature_key: &str,
        filter: &FeatureFilter,
        context: &EvalContext,
    ) -> toggly::Result<bool> {
        // Custom evaluation logic
        Ok(true)
    }
}

// Register with the engine
let registry = Registry::with_defaults();
registry.register("MyCustomFilter", Arc::new(MyCustomEvaluator));

Error Handling

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

async fn safe_feature_check(client: &TogglyClient) -> bool {
    match client.is_enabled("my-feature", EvalContext::default()).await {
        Ok(enabled) => enabled,
        Err(Error::FeatureNotFound(_)) => {
            tracing::warn!("Feature not found, using default");
            false
        }
        Err(e) => {
            tracing::error!("Feature check failed: {}", e);
            false
        }
    }
}

Requirements

  • Rust 1.70+ (MSRV)
  • Tokio runtime

Documentation

License

MIT License - see LICENSE for details.

Support