Skip to content

feat: add get_experiment_groups() to expose group names and return values#54

Open
cam-matsui wants to merge 2 commits into
statsig-io:mainfrom
cam-matsui:cam/get-experiment-groups
Open

feat: add get_experiment_groups() to expose group names and return values#54
cam-matsui wants to merge 2 commits into
statsig-io:mainfrom
cam-matsui:cam/get-experiment-groups

Conversation

@cam-matsui

Copy link
Copy Markdown

Why?

When building generic experimentation into a "platform", one must be cautious in deciding if and when to log exposures to avoid dilution. For example:

We have a rules engine and we want to generically provide the ability to experiment between 2 rules that evaluate whether a user gets an email. We might set up an experiment like:

Control Test
rule_id=A rule_id=B

One option is just to call get_experiment(user) and evaluate the rule with the returned ID. However, if rule A and rule B both return the same result (e.g., false) for some subset of users, we will dilute our experiment by exposing those users into the experiment even though none of those users would receive the email either way.

In most cases, we should instead evaluate both rules for all users in the experiment, and only log exposure if the results (and therefore the user experience) differ for rule A and B. In these kinds of scenarios, we need access to configurations for all experiment groups.

Because we have all of this data available in memory anyway, we are extending the interface to avoid reaching into SDK internals or making redundant HTTP requests to the console API.

This ports statsig-io/python-sdk#36 to server-core so the same capability is available as customers migrate from the legacy Python SDK to the core SDK.

Summary

Adds a new public get_experiment_groups function that returns the group name and return value for each group in a given experiment, without requiring a user evaluation. Implemented once in the Rust core and exposed across every binding.

New API

// Rust core
let groups = statsig.get_experiment_groups("my_experiment");

Each entry has group_name and return_value. Equivalent methods are exposed in every binding (camelCase / language-idiomatic naming):

Language Call
Rust statsig.get_experiment_groups("my_experiment")
Node statsig.getExperimentGroups("my_experiment")
Python statsig.get_experiment_groups("my_experiment")
Java statsig.getExperimentGroups("my_experiment")
PHP $statsig->getExperimentGroups("my_experiment")
Go statsig.GetExperimentGroups("my_experiment")
.NET statsig.GetExperimentGroups("my_experiment")
Elixir Statsig.get_experiment_groups("my_experiment")

Behavior

  • Returns an empty list if the experiment does not exist or the name refers to a dynamic config (not an experiment).
  • Returns an empty list for inactive experiments (isActive != true).
  • Excludes rules that are not experiment groups (isExperimentGroup != true), e.g. holdout / sizing rules.

Changes

  • Rust core (statsig-rust): new ExperimentGroup type and Statsig::get_experiment_groups, using the existing spec-store read-lock pattern. No user evaluation, no exposures.
  • Bindings: Node (NAPI), Python (pyo3), Java (JNI), PHP (FFI), Go (purego), .NET (FFI), Elixir (rustler). A new statsig_get_experiment_groups C export drives the FFI-based bindings; generated headers/bindings (statsig_ffi.h, libstatsig_ffi.h, StatsigFFI.g.cs) and the pyo3 stub (.pyi) and Node type defs (.d.ts) are regenerated.
  • Tests: per-binding coverage following each language's existing conventions (loading statsig-rust/tests/data/eval_proj_dcs.json and asserting against test_experiment_no_targeting), covering the happy path plus the empty-list cases (unknown experiment, dynamic config, inactive experiment).

…lues

Adds a new public get_experiment_groups method that returns the group name
and return value for each group in a given experiment, without requiring a
user evaluation. Implemented in the Rust core and exposed across all language
bindings (Node, Python, Java, PHP, Go, .NET, Elixir) plus the C/C++ FFI.

Behavior mirrors statsig-io/python-sdk#36:
- Returns [] if the experiment does not exist or the name refers to a dynamic
  config (entity != "experiment").
- Returns [] for inactive experiments (isActive != true).
- Excludes rules that are not experiment groups (isExperimentGroup != true),
  e.g. holdout/sizing rules.
Addresses review feedback:

- C++: expose getExperimentGroups in the hand-written wrapper (statsig.h/.cpp)
  with an ExperimentGroup type and from_json/to_json, plus a hermetic
  serialization test. Previously only the generated FFI header was updated.
- Elixir: add a dedicated test that serves eval_proj_dcs.json over a local
  mock server and exercises the public Statsig.get_experiment_groups/1 wrapper,
  including the happy path (Control/Test/Test2). Removes the redundant
  no-specs NIF test.
- Docs: clarify across Rust, Elixir, Go, PHP, .NET, and Java that the method
  returns an empty list for inactive experiments (and unknown names / dynamic
  configs) by design.
@cam-matsui cam-matsui marked this pull request as ready for review June 26, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant