Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,28 @@ rust-version = "1.78.0"
reqwest = { version = "0.11.3", default-features = false, features = [
"rustls-tls",
"blocking",
"json",
"gzip",
] }
serde = { version = "1.0.125", features = ["derive"] }
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = "1.0.64"
semver = "1.0.24"
derive_builder = "0.20.2"
uuid = { version = "1.13.2", features = ["serde", "v7"] }
sha1 = "0.10"
regex = "1.10"
tokio = { version = "1", features = ["rt", "sync", "time", "macros"], optional = true }

[dev-dependencies]
dotenv = "0.15.0"
ctor = "0.1.26"
tokio = { version = "1", features = ["full"] }
httpmock = "0.7"
serde_json = "1.0"
futures = "0.3"

[features]
default = ["async-client"]
e2e-test = []
async-client = []
async-client = ["tokio"]
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,121 @@ posthog-rs = "0.3.7"
```rust
let client = posthog_rs::client(env!("POSTHOG_API_KEY"));

// Capture events
let mut event = posthog_rs::Event::new("test", "1234");
event.insert_prop("key1", "value1").unwrap();
event.insert_prop("key2", vec!["a", "b"]).unwrap();

client.capture(event).unwrap();

// Check feature flags
let is_enabled = client.is_feature_enabled(
"new-feature".to_string(),
"user-123".to_string(),
None,
None,
None,
).unwrap();

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

## Feature Flags

The SDK now supports PostHog feature flags, allowing you to control feature rollout and run A/B tests.

### Basic Usage

```rust
use posthog_rs::{client, ClientOptions, FlagValue};
use std::collections::HashMap;
use serde_json::json;

let client = client(ClientOptions::from("your-api-key"));

// Check if a feature is enabled
let is_enabled = client.is_feature_enabled(
"feature-key".to_string(),
"user-id".to_string(),
None, None, None
).unwrap();

// Get feature flag value (boolean or variant)
match client.get_feature_flag(
"feature-key".to_string(),
"user-id".to_string(),
None, None, None
).unwrap() {
Some(FlagValue::Boolean(enabled)) => println!("Flag is: {}", enabled),
Some(FlagValue::String(variant)) => println!("Variant: {}", variant),
None => println!("Flag is disabled"),
}
```

### With Properties

```rust
// Include person properties for flag evaluation
let mut person_props = HashMap::new();
person_props.insert("plan".to_string(), json!("enterprise"));
person_props.insert("country".to_string(), json!("US"));

let flag = client.get_feature_flag(
"premium-feature".to_string(),
"user-id".to_string(),
None,
Some(person_props),
None
).unwrap();
```

### With Groups (B2B)

```rust
// For B2B apps with group-based flags
let mut groups = HashMap::new();
groups.insert("company".to_string(), "company-123".to_string());

let mut group_props = HashMap::new();
let mut company_props = HashMap::new();
company_props.insert("size".to_string(), json!(500));
group_props.insert("company".to_string(), company_props);

let flag = client.get_feature_flag(
"b2b-feature".to_string(),
"user-id".to_string(),
Some(groups),
None,
Some(group_props)
).unwrap();
```

### Get All Flags

```rust
// Get all feature flags for a user
let response = client.get_feature_flags(
"user-id".to_string(),
None, None, None
).unwrap();

for (key, value) in response.feature_flags {
println!("Flag {}: {:?}", key, value);
}
```

### Feature Flag Payloads

```rust
// Get additional data associated with a feature flag
let payload = client.get_feature_flag_payload(
"onboarding-flow".to_string(),
"user-id".to_string()
).unwrap();

if let Some(data) = payload {
println!("Payload: {}", data);
}
```
93 changes: 93 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# PostHog Rust SDK Examples

This directory contains example applications demonstrating how to use the PostHog Rust SDK, particularly the feature flags functionality.

## Running the Examples

### 1. Feature Flags with Mock Data (No API Key Required)
This example demonstrates feature flag evaluation using local mock data:

```bash
cargo run --example feature_flags_with_mock
```

This example shows:
- Simple percentage rollouts
- Property-based targeting (country, plan, age)
- Multivariate experiments with variants
- Local evaluation without API calls

### 2. Feature Flags Demo Application
A complete e-commerce demo with interactive testing:

```bash
# With a real PostHog API key
POSTHOG_API_KEY=your_api_key cargo run --example feature_flags_demo --all-features

# Without API key (uses local fallbacks)
cargo run --example feature_flags_demo --all-features
```

Features demonstrated:
- New checkout flow (premium/enterprise plans)
- AI recommendations (multivariate test)
- Pricing experiments (based on lifetime value)
- Holiday themes (geographic targeting)
- Interactive testing mode

### 3. Basic Feature Flags Example
Simple examples of all feature flag operations:

```bash
POSTHOG_API_KEY=your_api_key cargo run --example feature_flags
```

Shows:
- Checking if flags are enabled
- Getting flag values and variants
- Using person and group properties
- Getting feature flag payloads

## Testing Without a PostHog Account

The `feature_flags_with_mock` example is perfect for testing the SDK without needing a PostHog account. It demonstrates:

1. **Percentage Rollouts**: Flags enabled for a percentage of users
2. **Property Matching**: Target users based on properties like country, plan, age
3. **Multivariate Testing**: Split users into different variants
4. **Complex Conditions**: Combine multiple conditions with AND logic

## Key Concepts

### Feature Flag Types

1. **Boolean Flags**: Simple on/off toggles
```rust
FlagValue::Boolean(true) // enabled
FlagValue::Boolean(false) // disabled
```

2. **Multivariate Flags**: Multiple variants for A/B/n testing
```rust
FlagValue::String("control")
FlagValue::String("variant-a")
FlagValue::String("variant-b")
```

### Evaluation Methods

1. **Remote Evaluation**: Calls PostHog API for the latest flag values
2. **Local Evaluation**: Uses cached flag definitions for offline evaluation

### Properties

- **Person Properties**: User attributes (country, plan, age, etc.)
- **Group Properties**: Organization/team attributes for B2B apps

## Common Use Cases

1. **Feature Rollouts**: Gradually release features to users
2. **A/B Testing**: Test different variants to measure impact
3. **User Targeting**: Enable features for specific user segments
4. **Kill Switches**: Quickly disable problematic features
5. **Beta Programs**: Give early access to beta users
64 changes: 64 additions & 0 deletions examples/advanced_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/// SDK Configuration Examples
///
/// Shows different ways to configure the PostHog Rust SDK for various use cases.
///
/// Run with: cargo run --example advanced_config --features async-client
use posthog_rs::{ClientOptionsBuilder, EU_INGESTION_ENDPOINT};

#[cfg(feature = "async-client")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== PostHog SDK Configuration Examples ===\n");

// 1. SIMPLEST: Just an API key (uses US endpoint by default)
println!("1. Basic client (US region):");
let _basic = posthog_rs::client("phc_test_api_key").await;
println!(" → Created with default settings\n");

// 2. REGIONAL: EU data residency
println!("2. EU region client:");
let _eu = posthog_rs::client(("phc_test_api_key", EU_INGESTION_ENDPOINT)).await;
println!(" → Data stays in EU (GDPR compliant)\n");

// 3. SELF-HOSTED: Your own PostHog instance
println!("3. Self-hosted instance:");
let _custom = posthog_rs::client(("phc_test_api_key", "https://analytics.mycompany.com")).await;
println!(" → Uses your private PostHog deployment\n");

// 4. PRODUCTION: Common production settings
println!("4. Production configuration:");
let production_config = ClientOptionsBuilder::default()
.api_key("phc_production_key".to_string())
.host("https://eu.posthog.com") // Auto-detects and uses EU ingestion
.gzip(true) // Compress requests
.request_timeout_seconds(30) // 30s timeout
.build()?;

let _prod = posthog_rs::client(production_config).await;
println!(" → Optimized for production workloads\n");

// 5. HIGH-PERFORMANCE: Local flag evaluation
println!("5. High-performance with local evaluation:");
let performance_config = ClientOptionsBuilder::default()
.api_key("phc_project_key".to_string())
.personal_api_key("phx_personal_key") // Required for local eval
.enable_local_evaluation(true) // Cache flags locally
.poll_interval_seconds(30) // Update cache every 30s
.feature_flags_request_timeout_seconds(3)
.build()?;

let _perf = posthog_rs::client(performance_config).await;
println!(" → Evaluates flags locally (100x faster)\n");

println!("✅ Configuration examples complete!");
println!("\nTip: Check out 'feature_flags' example for flag usage");
println!(" and 'local_evaluation' for performance optimization.");

Ok(())
}

#[cfg(not(feature = "async-client"))]
fn main() {
println!("This example requires the async-client feature.");
println!("Run with: cargo run --example advanced_config --features async-client");
}
Loading
Loading