Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .changeset/pink-items-stick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"PostHog": patch
Comment thread
dustinbyrne marked this conversation as resolved.
"PostHog.AspNetCore": patch
---

Allow local feature flag definitions with a null `early_exit` value.
5 changes: 3 additions & 2 deletions src/PostHog/Api/LocalEvaluationApiResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,11 @@ internal record FeatureFlagFilters
/// When <c>true</c>, local condition evaluation stops and returns a definitive disabled result
/// as soon as a condition group's property filters match (or the group has no property filters)
/// but the rollout percentage excludes the user, instead of falling through to later condition
/// groups. Mirrors the server-side Rust evaluation engine. Defaults to <c>false</c> when absent.
/// groups. Mirrors the server-side Rust evaluation engine. Defaults to <c>false</c> when absent;
/// explicit <c>null</c> values are also treated as <c>false</c> during evaluation.
/// </summary>
[JsonPropertyName("early_exit")]
public bool EarlyExit { get; init; }
public bool? EarlyExit { get; init; } = false;

/// <summary>
/// Compares this instance to another <see cref="FeatureFlagFilters"/> for equality.
Expand Down
17 changes: 7 additions & 10 deletions tests/UnitTests/Features/LocalEvaluatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2744,13 +2744,12 @@ public void EarlyExitReturnsExpectedResult(bool earlyExit, string firstGroupEmai
Assert.Equal(expected, result.Value);
}

[Fact]
public void EarlyExitDeserializesFromJsonAndEarlyExits()
[Theory]
[InlineData("true", false)]
[InlineData("null", true)]
public void EarlyExitDeserializesFromJson(string earlyExit, bool expected)
{
// Verifies that "early_exit": true round-trips through JSON correctly. A typo in the
// [JsonPropertyName] attribute would silently deserialize to false and disable the
// feature in production while all object-initializer tests still pass.
var json = """
var json = $$"""
{
"flags": [
{
Expand All @@ -2760,7 +2759,7 @@ public void EarlyExitDeserializesFromJsonAndEarlyExits()
"key": "early-exit",
"active": true,
"filters": {
"early_exit": true,
"early_exit": {{earlyExit}},
"groups": [
{
"properties": [
Expand All @@ -2785,14 +2784,12 @@ public void EarlyExitDeserializesFromJsonAndEarlyExits()
var flags = JsonSerializer.Deserialize<LocalEvaluationApiResult>(json, JsonSerializerHelper.Options)!;
var localEvaluator = new LocalEvaluator(flags);

// early_exit=true + rollout 0 on first group (OUT_OF_ROLLOUT_BOUND) must short-circuit
// and return false, never reaching the second group with rollout 100.
var result = localEvaluator.EvaluateFeatureFlag(
key: "early-exit",
distinctId: "1234",
personProperties: new Dictionary<string, object?> { ["email"] = "tyrion@example.com" });

Assert.False(result.Value);
Assert.Equal(expected, result.Value);
}

[Fact]
Expand Down
Loading