Skip to content

Commit 248d7ca

Browse files
committed
fix(flags): handle null early_exit values
1 parent d126913 commit 248d7ca

3 files changed

Lines changed: 15 additions & 12 deletions

File tree

.changeset/pink-items-stick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"PostHog": patch
3+
---
4+
5+
Allow local feature flag definitions with a null `early_exit` value.

src/PostHog/Api/LocalEvaluationApiResult.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,11 @@ internal record FeatureFlagFilters
167167
/// When <c>true</c>, local condition evaluation stops and returns a definitive disabled result
168168
/// as soon as a condition group's property filters match (or the group has no property filters)
169169
/// but the rollout percentage excludes the user, instead of falling through to later condition
170-
/// groups. Mirrors the server-side Rust evaluation engine. Defaults to <c>false</c> when absent.
170+
/// groups. Mirrors the server-side Rust evaluation engine. Defaults to <c>false</c> when absent;
171+
/// explicit <c>null</c> values are also treated as <c>false</c> during evaluation.
171172
/// </summary>
172173
[JsonPropertyName("early_exit")]
173-
public bool EarlyExit { get; init; }
174+
public bool? EarlyExit { get; init; } = false;
174175

175176
/// <summary>
176177
/// Compares this instance to another <see cref="FeatureFlagFilters"/> for equality.

tests/UnitTests/Features/LocalEvaluatorTests.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,13 +2744,12 @@ public void EarlyExitReturnsExpectedResult(bool earlyExit, string firstGroupEmai
27442744
Assert.Equal(expected, result.Value);
27452745
}
27462746

2747-
[Fact]
2748-
public void EarlyExitDeserializesFromJsonAndEarlyExits()
2747+
[Theory]
2748+
[InlineData("true", false)]
2749+
[InlineData("null", true)]
2750+
public void EarlyExitDeserializesFromJson(string earlyExit, bool expected)
27492751
{
2750-
// Verifies that "early_exit": true round-trips through JSON correctly. A typo in the
2751-
// [JsonPropertyName] attribute would silently deserialize to false and disable the
2752-
// feature in production while all object-initializer tests still pass.
2753-
var json = """
2752+
var json = $$"""
27542753
{
27552754
"flags": [
27562755
{
@@ -2760,7 +2759,7 @@ public void EarlyExitDeserializesFromJsonAndEarlyExits()
27602759
"key": "early-exit",
27612760
"active": true,
27622761
"filters": {
2763-
"early_exit": true,
2762+
"early_exit": {{earlyExit}},
27642763
"groups": [
27652764
{
27662765
"properties": [
@@ -2785,14 +2784,12 @@ public void EarlyExitDeserializesFromJsonAndEarlyExits()
27852784
var flags = JsonSerializer.Deserialize<LocalEvaluationApiResult>(json, JsonSerializerHelper.Options)!;
27862785
var localEvaluator = new LocalEvaluator(flags);
27872786

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

2795-
Assert.False(result.Value);
2792+
Assert.Equal(expected, result.Value);
27962793
}
27972794

27982795
[Fact]

0 commit comments

Comments
 (0)