From 5af6124073508b31fcaf3cd8aaa4c88617b33686 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Sat, 29 Aug 2026 19:06:52 +0200 Subject: [PATCH] chore: upgrade to Rust SDK v1 --- Cargo.lock | 4 +-- Cargo.toml | 2 +- README.md | 61 ++++++++++++++++------------------ src/main.rs | 96 +++++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 109 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8819849..9263eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "featurevisor" -version = "0.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e954a6c392472c41cb0703b3addba33c4629edad07a4f16d75a9d580c7b16c4" +checksum = "5c6218f50bbd6749164d249f689ab40f3d2b23f70b2d64b3a7af0344bffebb1e" dependencies = [ "chrono", "regex", diff --git a/Cargo.toml b/Cargo.toml index d3d9a3c..00b1cb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ rust-version = "1.74.0" publish = false [dependencies] -featurevisor = "0.1" +featurevisor = "1.0" reqwest = { version = "0.11", default-features = false, features = ["blocking", "rustls-tls"] } diff --git a/README.md b/README.md index c133ec7..84c5878 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,52 @@ # featurevisor-example-rust -A small Rust application showing how to consume the published [Featurevisor Rust SDK](https://github.com/featurevisor/featurevisor-rust) from crates.io. +A small Rust application showing how to use the published [Featurevisor Rust SDK](https://github.com/featurevisor/featurevisor-rust) from crates.io. -The application downloads a Featurevisor datafile, creates an SDK instance, sets a context, and evaluates one feature as a flag, variation, and string variable. +Learn more about Featurevisor [here](https://featurevisor.com). + +It fetches a production datafile, creates a Featurevisor instance with customer context, then evaluates a flag, a variation, feature variables, and global variables. ## Requirements Rust 1.74 or newer. -## Run it +## Run the example ```sh cargo run ``` -The application fetches this production datafile: +The application evaluates `commerce_platform`, `checkout_experience`, `max_items`, `payment_methods`, `serviceEndpoints`, and `supportContact` from this datafile: ```text -https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-mobile.json +https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-sdk-v3.json ``` -The important SDK usage is in `src/main.rs`: - -```rust -use featurevisor::{context, create_featurevisor, DatafileInput, FeaturevisorOptions}; - -let f = create_featurevisor(FeaturevisorOptions { - datafile: Some(DatafileInput::Json(datafile)), - ..Default::default() -}); - -f.set_context( - context! { - "userId" => "mobile-user", - "country" => "nl", - }, - false, -); - -let enabled = f.is_enabled("mobile_experience", None); -let variation = f.get_variation("mobile_experience", None, None); -let welcome_message = f.get_variable_string( - "mobile_experience", - "welcome_message", - None, - None, -); +Expected output: + +```text +Commerce platform enabled: true +Checkout variation: express +Maximum checkout items: 25 +Payment methods: [card, wallet] +Service endpoint: https://api.eu.example.com (timeout: 1200 ms, retries: 4) +Support contact: support-nl@example.com ``` +Change the context in `src/main.rs` to see how Featurevisor selects different rules, variations, and global variable overrides. + The `featurevisor` dependency comes from crates.io. It is not a local path dependency, so this project demonstrates the same setup used by a new consumer. -Learn more in the [Featurevisor Rust SDK documentation](https://featurevisor.com/docs/sdks/rust/). +## Checks + +```sh +cargo fmt --all -- --check +cargo clippy --all-targets -- -D warnings +cargo test +``` ## Featurevisor project -The datafile is generated by the [Featurevisor example Cloudflare project](https://github.com/featurevisor/featurevisor-example-cloudflare). +The datafile is generated by the [Featurevisor Cloudflare example](https://github.com/featurevisor/featurevisor-example-cloudflare). + +Learn more in the [Featurevisor Rust SDK documentation](https://featurevisor.com/docs/sdks/rust/). diff --git a/src/main.rs b/src/main.rs index cb80b99..0e774f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,100 @@ use std::error::Error; +use std::io; +use std::time::Duration; -use featurevisor::{context, create_featurevisor, DatafileInput, FeaturevisorOptions}; +use featurevisor::{ + context, create_featurevisor, DatafileInput, FeaturevisorOptions, LogLevel, VariableValue, +}; const DATAFILE_URL: &str = - "https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-mobile.json"; + "https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-sdk-v3.json"; fn main() -> Result<(), Box> { - let datafile = reqwest::blocking::get(DATAFILE_URL)? + let datafile = reqwest::blocking::Client::builder() + .timeout(Duration::from_secs(10)) + .build()? + .get(DATAFILE_URL) + .header("Accept", "application/json") + .send()? .error_for_status()? .text()?; let f = create_featurevisor(FeaturevisorOptions { datafile: Some(DatafileInput::Json(datafile)), + context: Some(context! { + "userId" => "customer-123", + "country" => "nl", + "locale" => "nl-NL", + "accountPlan" => "pro", + }), + log_level: Some(LogLevel::Error), ..Default::default() }); - f.set_context( - context! { - "userId" => "mobile-user", - "country" => "nl", - }, - false, - ); + let commerce_enabled = f.is_enabled("commerce_platform", None); + let checkout_variation = f.get_variation("checkout_experience", None, None); + let max_items = f.get_variable_integer("checkout_experience", "max_items", None, None); + let payment_methods = + f.get_variable_array("checkout_experience", "payment_methods", None, None); + let endpoints = f + .get_global_variable_object("serviceEndpoints", None, None) + .ok_or_else(|| io::Error::other("serviceEndpoints is unavailable"))?; + let support_contact = f.get_global_variable_string("supportContact", None, None); - let enabled = f.is_enabled("mobile_experience", None); - let variation = f.get_variation("mobile_experience", None, None); - let welcome_message = f.get_variable_string("mobile_experience", "welcome_message", None, None); + let base_url = string_field(&endpoints, "baseUrl")?; + let timeout_ms = integer_field(&endpoints, "timeoutMs")?; + let retries = integer_field(&endpoints, "retries")?; - println!("Datafile revision: {}", f.get_revision()); - println!("Feature 'mobile_experience' enabled: {enabled}"); - println!("Variation: {}", variation.as_deref().unwrap_or("none")); + println!("Commerce platform enabled: {commerce_enabled}"); + println!( + "Checkout variation: {}", + checkout_variation.as_deref().unwrap_or("unavailable") + ); + println!( + "Maximum checkout items: {}", + max_items + .map(|value| value.to_string()) + .unwrap_or_else(|| "unavailable".to_string()) + ); + println!("Payment methods: {}", format_array(payment_methods)); + println!("Service endpoint: {base_url} (timeout: {timeout_ms} ms, retries: {retries})"); println!( - "Variable 'welcome_message': {}", - welcome_message.as_deref().unwrap_or("none") + "Support contact: {}", + support_contact.as_deref().unwrap_or("unavailable") ); f.close(); Ok(()) } + +fn string_field<'a>( + object: &'a std::collections::HashMap, + key: &str, +) -> Result<&'a str, Box> { + match object.get(key) { + Some(VariableValue::String(value)) => Ok(value), + _ => Err(io::Error::other(format!("{key} is unavailable")).into()), + } +} + +fn integer_field( + object: &std::collections::HashMap, + key: &str, +) -> Result> { + match object.get(key) { + Some(VariableValue::Integer(value)) => Ok(*value), + _ => Err(io::Error::other(format!("{key} is unavailable")).into()), + } +} + +fn format_array(values: Option>) -> String { + let items = values + .unwrap_or_default() + .into_iter() + .filter_map(|value| match value { + VariableValue::String(value) => Some(value), + _ => None, + }) + .collect::>(); + format!("[{}]", items.join(", ")) +}