Skip to content
63 changes: 61 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# PostHog Rust

Please see the main [PostHog docs](https://posthog.com/docs).
The official Rust SDK for [PostHog](https://posthog.com). See the [PostHog docs](https://posthog.com/docs) for more information.

**This crate is under development**
## Features

- **Event capture** - Send events to PostHog for product analytics
- **Feature flags** - Evaluate feature flags with local or remote evaluation
- **A/B testing** - Support for multivariate flags and experiments
- **Group analytics** - Track events and flags for B2B use cases
- **Async and sync clients** - Choose based on your runtime

# Quickstart

Expand Down Expand Up @@ -135,6 +141,59 @@ if let Some(data) = payload {
}
```

### Error Handling

In production code, handle errors properly instead of using `.unwrap()`:

```rust
use posthog_rs::{client, Error, FlagValue};

let client = client("your-api-key");

// Pattern 1: Match on the error type
match client.get_feature_flag("my-flag", "user-123", None, None, None) {
Ok(Some(FlagValue::Boolean(true))) => {
// Flag is enabled
enable_feature();
}
Ok(Some(FlagValue::String(variant))) => {
// Multivariate flag - use the variant
use_variant(&variant);
}
Ok(Some(FlagValue::Boolean(false))) | Ok(None) => {
// Flag is disabled or doesn't exist
use_default_behavior();
}
Err(Error::Connection(e)) => {
// Network error - maybe use cached value or default
eprintln!("Failed to fetch flag: {}", e);
use_default_behavior();
}
Err(Error::InconclusiveMatch(reason)) => {
// Local evaluation couldn't determine flag value
// (missing properties, unknown cohort, etc.)
eprintln!("Inconclusive evaluation: {}", reason);
use_default_behavior();
}
Err(e) => {
// Other errors
eprintln!("Unexpected error: {}", e);
use_default_behavior();
}
}

// Pattern 2: Use unwrap_or for simple defaults
let is_enabled = client
.is_feature_enabled("my-flag", "user-123", None, None, None)
.unwrap_or(false); // Default to disabled on error

// Pattern 3: Propagate errors with ?
fn check_feature(client: &posthog_rs::Client, user_id: &str) -> Result<bool, Error> {
let flag = client.is_feature_enabled("premium-feature", user_id, None, None, None)?;
Ok(flag)
}
```

## Observability

The SDK uses [tracing](https://docs.rs/tracing) for structured logging. To see logs, add a tracing subscriber to your application:
Expand Down
16 changes: 16 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ pub use async_client::client;
#[cfg(feature = "async-client")]
pub use async_client::Client;

/// Configuration options for the PostHog client.
///
/// Use [`ClientOptionsBuilder`] to construct options with custom settings,
/// or create directly from an API key using `ClientOptions::from("your-api-key")`.
///
/// # Example
///
/// ```ignore
/// use posthog_rs::ClientOptionsBuilder;
///
/// let options = ClientOptionsBuilder::default()
/// .api_key("your-api-key".to_string())
/// .host("https://eu.posthog.com")
/// .build()
/// .unwrap();
/// ```
#[derive(Builder, Clone)]
pub struct ClientOptions {
/// Host URL for the PostHog API (defaults to US ingestion endpoint)
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ impl Display for Error {
}
}

/// Errors that can occur when using the PostHog client.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
/// Network or HTTP error when communicating with PostHog API
Connection(String),
/// Error serializing or deserializing JSON data
Serialization(String),
/// Global client was already initialized via `init_global`
AlreadyInitialized,
/// Global client was not initialized before use
NotInitialized,
/// Timestamp could not be parsed or is invalid
InvalidTimestamp(String),
/// Flag evaluation was inconclusive (e.g., missing required properties, unknown operator)
InconclusiveMatch(String),
Expand Down
Loading
Loading