A Rust SDK for ABsmartly - A/B testing and feature flagging platform.
The ABsmartly Rust SDK is compatible with Rust 2021 edition and later. It provides a synchronous interface for variant assignment and goal tracking.
Add this to your Cargo.toml:
[dependencies]
absmartly-sdk = "0.1"Please follow the installation instructions before trying the following code.
This example assumes an API Key, an Application, and an Environment have been created in the ABsmartly web console.
Using the builder pattern (recommended):
use absmartly_sdk::ABsmartly;
let sdk = ABsmartly::builder()
.endpoint("https://your-company.absmartly.io/v1")
.api_key("YOUR-API-KEY")
.application("website")
.environment("development")
.build()?;Using positional parameters:
use absmartly_sdk::ABsmartly;
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"development",
)?;Advanced configuration:
use absmartly_sdk::ABsmartly;
let sdk = ABsmartly::builder()
.endpoint("https://your-company.absmartly.io/v1")
.api_key("YOUR-API-KEY")
.application("website")
.environment("development")
.timeout(5000)
.retries(3)
.agent("my-agent")
.build()?;SDK Options
| Config | Type | Required? | Default | Description |
|---|---|---|---|---|
| endpoint | String |
✅ | "" |
The URL to your API endpoint. Most commonly "your-company.absmartly.io" |
| api_key | String |
✅ | "" |
Your API key which can be found on the Web Console. |
| environment | String |
✅ | "" |
The environment of the platform where the SDK is installed. Environments are created on the Web Console and should match the available environments in your infrastructure. |
| application | String |
✅ | "" |
The name of the application where the SDK is installed. Applications are created on the Web Console and should match the applications where your experiments will be running. |
| retries | u32 |
❌ | 5 |
Number of retry attempts for failed HTTP requests |
| timeout_ms | u64 |
❌ | 3000 |
Connection timeout in milliseconds |
| agent | Option<String> |
❌ | None |
Custom user agent string |
use absmartly_sdk::ABsmartly;
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"development",
)?;
// Define units for the context - accepts arrays of tuples (no .to_string() needed!)
let units = [("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8")];
// Create context (it will be ready immediately after creation)
let mut context = sdk.create_context(units, None).await?;When doing full-stack experimentation with ABsmartly, we recommend creating a context only once on the server-side. Creating a context involves a round-trip to the ABsmartly event collector. We can avoid repeating the round-trip on the client-side by sending the server-side data embedded in the first document.
use absmartly_sdk::{ABsmartly, ContextData};
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"development",
)?;
// Define units for the context
let units = [("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8")];
// Load context data from ABsmartly API (you'll need to fetch this from your backend)
let context_data: ContextData = serde_json::from_str(&api_response)?;
// Create context with pre-fetched data - no network round-trip needed
let mut context = sdk.create_context_with(units, context_data, None);
assert!(context.is_ready()); // Context is immediately readyFor long-running contexts, the context can be refreshed manually with updated experiment data:
// Fetch fresh context data from your backend
let fresh_data: ContextData = serde_json::from_str(&api_response)?;
// Refresh the context with new data
context.refresh(fresh_data);You can add additional units to a context by calling the set_unit() method. This method may be used, for example, when a user logs in to your application, and you want to use the new unit type in the context.
Note: You cannot override an already set unit type as that would be a change of identity. In this case, you must create a new context instead.
context.set_unit("db_user_id", "1000013")?;
// Or set multiple units at once
context.set_units([
("db_user_id", "1000013"),
("user_type", "premium"),
])?;Attributes are used for audience targeting. The set_attribute() method can be called before the context is ready. It accepts native Rust types directly:
// Accepts native Rust types - no json!() macro needed!
context.set_attribute("user_agent", "Mozilla/5.0")?;
context.set_attribute("customer_age", "new_customer")?;
context.set_attribute("age", 25)?;
context.set_attribute("premium", true)?;
// Or set multiple attributes at once
context.set_attributes([
("user_agent", "Mozilla/5.0"),
("customer_age", "new_customer"),
])?;let variant = context.treatment("exp_test_experiment");
if variant == 0 {
// User is in control group (variant 0)
println!("Control group");
} else {
// User is in treatment group
println!("Treatment group: variant {}", variant);
}Goals are created in the ABsmartly web console.
use serde_json::json;
// Track a simple goal (use () for no properties)
context.track("payment", ())?;
// Track a goal with properties using json!()
context.track("purchase", json!({
"item_count": 1,
"total_amount": 1999.99
}))?;Sometimes it is necessary to ensure all events have been published to the ABsmartly collector before proceeding. You can explicitly call the publish() method.
context.publish();The finalize() method will ensure all events have been published to the ABsmartly collector, like publish(), and will also "seal" the context, preventing any further events from being tracked.
context.finalize();
// Context is now sealed - no more treatments or goals can be trackedAlthough generally not recommended, it is sometimes necessary to peek at a treatment without triggering an exposure. The ABsmartly SDK provides a peek() method for that.
let variant = context.peek("exp_test_experiment");
if variant == 0 {
// User is in control group (variant 0)
} else {
// User is in treatment group
}You can peek at variable values without triggering an exposure.
let button_color = context.peek_variable_value("button.color", "red");During development, for example, it is useful to force a treatment for an experiment. This can be achieved with the set_override() method.
context.set_override("exp_test_experiment", 1); // Force variant 1
// You can also set multiple overrides
context.set_override("exp_another_experiment", 0);Custom assignments allow you to set a specific variant for an experiment programmatically.
context.set_custom_assignment("exp_test_experiment", 1)?;
// Or set multiple custom assignments at once
context.set_custom_assignments([
("exp_test_experiment", 1),
("exp_another_experiment", 0),
])?;Get configuration values from experiments. Variables allow you to configure different values for each variant.
// Get a variable value with a default fallback
// Accepts native Rust types - no json!() macro needed!
let button_color = context.variable_value("button.color", "blue");
println!("Button color: {}", button_color);
// Get other types of variables
let show_banner = context.variable_value("banner.show", false);
let max_items = context.variable_value("cart.max_items", 10);You can implement a custom event logger to handle SDK events. This is useful for debugging, analytics, or integrating with other systems.
use absmartly_sdk::{EventLogger, EventType, Context};
struct CustomEventLogger;
impl EventLogger for CustomEventLogger {
fn handle_event(&self, context: &Context, event: EventType, data: &dyn std::any::Any) {
match event {
EventType::Exposure => {
if let Some(exposure) = data.downcast_ref::<Exposure>() {
println!("Exposed to experiment: {}", exposure.name);
}
}
EventType::Goal => {
if let Some(goal) = data.downcast_ref::<GoalAchievement>() {
println!("Goal tracked: {}", goal.name);
}
}
EventType::Error => {
if let Some(error) = data.downcast_ref::<SDKError>() {
eprintln!("Error: {:?}", error);
}
}
EventType::Ready | EventType::Refresh | EventType::Publish | EventType::Close => {
// Handle other events as needed
}
}
}
}
// Usage with custom event logger requires SDKConfig
let config = SDKConfig::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"development",
);
// Note: event_logger support coming soon
let sdk = ABsmartly::from_config(config)?;Event Types
| Event | When | Data |
|---|---|---|
Error |
Context receives an error | SDKError |
Ready |
Context turns ready | ContextData used to initialize |
Refresh |
refresh() method succeeds |
ContextData used to refresh |
Publish |
publish() method succeeds |
PublishEvent sent to collector |
Exposure |
treatment() succeeds on first exposure |
Exposure enqueued for publishing |
Goal |
track() method succeeds |
GoalAchievement enqueued for publishing |
Close |
finalize() method succeeds the first time |
() |
You can retrieve information about experiments in the current context.
// Get all experiment names
let experiments = context.get_experiments();
for name in experiments {
println!("Experiment: {}", name);
}
// Get custom field value for an experiment
if let Some(value) = context.get_custom_field_value("exp_test", "analyst") {
println!("Analyst: {}", value);
}You can get all variable keys for an experiment.
let keys = context.variable_keys();
for key in keys {
println!("Variable key: {}", key);
}use axum::{
routing::get,
Router,
extract::State,
response::Html,
};
use absmartly_sdk::ABsmartly;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
absmartly: Arc<ABsmartly>,
}
#[tokio::main]
async fn main() {
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"production",
).expect("Failed to initialize ABsmartly SDK");
let state = AppState {
absmartly: Arc::new(sdk),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler(State(state): State<AppState>) -> Html<String> {
let units = [("session_id", "example-session-id")];
let mut context = state.absmartly
.create_context(units, None)
.await
.expect("Failed to create context");
let treatment = context.treatment("exp_test_experiment");
context.finalize();
if treatment == 0 {
Html("<h1>Control Group</h1>".to_string())
} else {
Html("<h1>Treatment Group</h1>".to_string())
}
}use actix_web::{get, web, App, HttpServer, HttpResponse};
use absmartly_sdk::ABsmartly;
use std::sync::Arc;
struct AppState {
absmartly: Arc<ABsmartly>,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> HttpResponse {
let units = [("session_id", "example-session-id")];
let mut context = data.absmartly
.create_context(units, None)
.await
.expect("Failed to create context");
let treatment = context.treatment("exp_test_experiment");
context.finalize();
if treatment == 0 {
HttpResponse::Ok().body("<h1>Control Group</h1>")
} else {
HttpResponse::Ok().body("<h1>Treatment Group</h1>")
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"production",
).expect("Failed to initialize ABsmartly SDK");
let app_state = web::Data::new(AppState {
absmartly: Arc::new(sdk),
});
HttpServer::new(move || {
App::new()
.app_data(app_state.clone())
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}use rocket::{State, get, routes};
use absmartly_sdk::ABsmartly;
use std::sync::Arc;
#[get("/")]
async fn index(sdk: &State<Arc<ABsmartly>>) -> String {
let units = [("session_id", "example-session-id")];
let mut context = sdk.create_context(units, None)
.await
.expect("Failed to create context");
let treatment = context.treatment("exp_test_experiment");
context.finalize();
if treatment == 0 {
String::from("<h1>Control Group</h1>")
} else {
String::from("<h1>Treatment Group</h1>")
}
}
#[rocket::main]
async fn main() {
let sdk = ABsmartly::new(
"https://your-company.absmartly.io/v1",
"YOUR-API-KEY",
"website",
"production",
).expect("Failed to initialize ABsmartly SDK");
rocket::build()
.manage(Arc::new(sdk))
.mount("/", routes![index])
.launch()
.await
.unwrap();
}use absmartly_sdk::{ABsmartly, Context};
use tokio::time::{timeout, Duration};
async fn create_context_with_timeout(
sdk: &ABsmartly
) -> Result<Context, Box<dyn std::error::Error>> {
let units = [("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8")];
let context = timeout(
Duration::from_millis(1500),
sdk.create_context(units, None)
).await??;
Ok(context)
}use absmartly_sdk::ABsmartly;
use tokio::select;
use tokio::sync::oneshot;
async fn create_context_with_cancellation(sdk: &ABsmartly) {
let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
let units = [("session_id", "5ebf06d8cb5d8137290c4abb64155584fbdb64d8")];
select! {
result = sdk.create_context(units, None) => {
match result {
Ok(context) => println!("Context ready"),
Err(e) => eprintln!("Context creation failed: {:?}", e),
}
}
_ = cancel_rx => {
println!("Context creation cancelled");
}
}
// To cancel: drop(cancel_tx) or cancel_tx.send(()).ok();
}The SDK expects context data in the following format (typically fetched from the ABsmartly API):
use absmartly_sdk::ContextData;
let context_data = ContextData {
experiments: vec![
// Experiment configurations from ABsmartly API
],
};The SDK uses Rust's Result type for error handling:
use absmartly_sdk::SDKError;
match context.set_unit("user_id", "12345") {
Ok(_) => println!("Unit set successfully"),
Err(SDKError::UnitAlreadySet(unit_type)) => {
println!("Cannot override unit type: {}", unit_type);
}
Err(e) => println!("Error: {:?}", e),
}The Context is designed for single-threaded use. If you need to use it across threads, wrap it in appropriate synchronization primitives like Arc<Mutex<Context>>.
A/B Smartly is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them.
A/B Smartly's real-time analytics helps engineering and product teams ensure that new features will improve the customer experience without breaking or degrading performance and/or business metrics.
- JavaScript SDK
- Java SDK
- PHP SDK
- Swift SDK
- Vue2 SDK
- Vue3 SDK
- React SDK
- Python3 SDK
- Go SDK
- Ruby SDK
- .NET SDK
- Dart SDK
- Flutter SDK
- Rust SDK
This project is licensed under the MIT License - see the LICENSE file for details.