From c9ff5a3b62c9942fd4e20c41038ea199a1d84885 Mon Sep 17 00:00:00 2001 From: ugurtafrali Date: Thu, 16 Apr 2026 11:10:56 +0300 Subject: [PATCH 1/2] fix: handle missing/null targeting keys in fractional evaluator --- core/pkg/evaluator/fractional.go | 7 +++ core/pkg/evaluator/fractional_test.go | 61 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/core/pkg/evaluator/fractional.go b/core/pkg/evaluator/fractional.go index 267151797..11efd915d 100644 --- a/core/pkg/evaluator/fractional.go +++ b/core/pkg/evaluator/fractional.go @@ -48,6 +48,10 @@ func (fe *Fractional) Evaluate(values, data any) any { return nil } + if feDistributions == nil { + return nil + } + hashValue := uint32(murmur3.StringSum32(valueToDistribute)) return distributeValue(hashValue, feDistributions) } @@ -78,6 +82,9 @@ if len(valuesArray) < 1 { valuesArray = valuesArray[1:] } + if dataMap[targetingKeyKey] == nil { + return "", nil, nil + } targetingKey, ok := dataMap[targetingKeyKey].(string) if !ok { return "", nil, fmt.Errorf("flag %q: bucketing value not supplied and no targetingKey in context", flagKey) diff --git a/core/pkg/evaluator/fractional_test.go b/core/pkg/evaluator/fractional_test.go index cd92df66f..5bd9cdf43 100644 --- a/core/pkg/evaluator/fractional_test.go +++ b/core/pkg/evaluator/fractional_test.go @@ -442,6 +442,67 @@ func TestFractionalEvaluation(t *testing.T) { expectedValue: blueHex, expectedReason: model.TargetingMatchReason, }, + "null targetingKey returns default variant": { + flags: []model.Flag{{ + Key: "headerColor", + State: "ENABLED", + DefaultVariant: redVariant, + Variants: colorVariants, + Targeting: []byte(`{ + "fractional": [ + ["blue", 50], + ["green", 50] + ] + }`), + }}, + flagKey: "headerColor", + context: map[string]any{ + "targetingKey": nil, + }, + expectedVariant: redVariant, + expectedValue: redHex, + expectedReason: model.DefaultReason, + }, + "missing targetingKey returns default variant": { + flags: []model.Flag{{ + Key: "headerColor", + State: "ENABLED", + DefaultVariant: redVariant, + Variants: colorVariants, + Targeting: []byte(`{ + "fractional": [ + ["blue", 50], + ["green", 50] + ] + }`), + }}, + flagKey: "headerColor", + context: map[string]any{}, + expectedVariant: redVariant, + expectedValue: redHex, + expectedReason: model.DefaultReason, + }, + "empty targetingKey buckets by flagKey": { + flags: []model.Flag{{ + Key: "headerColor", + State: "ENABLED", + DefaultVariant: redVariant, + Variants: colorVariants, + Targeting: []byte(`{ + "fractional": [ + ["blue", 50], + ["green", 50] + ] + }`), + }}, + flagKey: "headerColor", + context: map[string]any{ + "targetingKey": "", + }, + expectedVariant: greenVariant, + expectedValue: greenHex, + expectedReason: model.TargetingMatchReason, + }, "single-entry always returns the sole variant": { flags: []model.Flag{{ Key: "headerColor", From 2f031384c60abc76bd93b6189a8617f346f6278c Mon Sep 17 00:00:00 2001 From: utafrali Date: Thu, 16 Apr 2026 22:04:59 +0300 Subject: [PATCH 2/2] Address review feedback --- core/pkg/evaluator/fractional.go | 4 ++++ core/pkg/evaluator/fractional_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/pkg/evaluator/fractional.go b/core/pkg/evaluator/fractional.go index 11efd915d..0163e0929 100644 --- a/core/pkg/evaluator/fractional.go +++ b/core/pkg/evaluator/fractional.go @@ -90,6 +90,10 @@ if len(valuesArray) < 1 { return "", nil, fmt.Errorf("flag %q: bucketing value not supplied and no targetingKey in context", flagKey) } + if targetingKey == "" { + return "", nil, nil + } + bucketBy = fmt.Sprintf("%s%s", properties.FlagKey, targetingKey) } diff --git a/core/pkg/evaluator/fractional_test.go b/core/pkg/evaluator/fractional_test.go index 5bd9cdf43..6fcfa7ffb 100644 --- a/core/pkg/evaluator/fractional_test.go +++ b/core/pkg/evaluator/fractional_test.go @@ -482,7 +482,7 @@ func TestFractionalEvaluation(t *testing.T) { expectedValue: redHex, expectedReason: model.DefaultReason, }, - "empty targetingKey buckets by flagKey": { + "empty targetingKey returns default variant": { flags: []model.Flag{{ Key: "headerColor", State: "ENABLED", @@ -499,9 +499,9 @@ func TestFractionalEvaluation(t *testing.T) { context: map[string]any{ "targetingKey": "", }, - expectedVariant: greenVariant, - expectedValue: greenHex, - expectedReason: model.TargetingMatchReason, + expectedVariant: redVariant, + expectedValue: redHex, + expectedReason: model.DefaultReason, }, "single-entry always returns the sole variant": { flags: []model.Flag{{