Skip to content

Commit 6099a64

Browse files
committed
fix: add numeric type support to IN operator
- Add type-aware comparison for IN operator conditions - Support int, float, bool, and string comparisons - Handle numeric arrays ([]int, []float) in segment conditions - Handle JSON-encoded numeric arrays - Handle comma-separated numeric values
1 parent 733ab59 commit 6099a64

File tree

1 file changed

+59
-15
lines changed

1 file changed

+59
-15
lines changed

flagengine/engine_eval/evaluator.go

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,49 +115,93 @@ func contextMatchesCondition(ec *EngineEvaluationContext, segmentCondition *Cond
115115
return false
116116
}
117117

118-
// matchInOperator handles the IN operator for segment conditions, supporting both StringArray and comma-separated strings.
118+
// matchInOperator handles the IN operator for segment conditions, supporting arrays and comma-separated strings.
119119
func matchInOperator(segmentCondition *Condition, contextValue ContextValue) bool {
120120
if contextValue == nil {
121121
return false
122122
}
123123

124-
traitValue := ToString(contextValue)
125-
126124
if segmentCondition.Value == nil {
127125
return false
128126
}
129127

130-
// First try to use []string if available
128+
// Handle []string array
131129
if strArray, ok := segmentCondition.Value.([]string); ok {
130+
traitValue := ToString(contextValue)
132131
return slices.Contains(strArray, traitValue)
133132
}
134133

135-
// Convert []interface{} to []string (happens during JSON unmarshaling)
134+
// Handle []interface{} array (from JSON unmarshaling) - supports mixed types
136135
if ifaceArray, ok := segmentCondition.Value.([]interface{}); ok {
137-
for _, v := range ifaceArray {
138-
if str, ok := v.(string); ok && str == traitValue {
139-
return true
140-
}
141-
}
142-
return false
136+
return matchInArrayInterface(ifaceArray, contextValue)
143137
}
144138

145-
// Fall back to string - try JSON parsing first, then comma-separated
139+
// Handle string value (JSON-encoded or comma-separated)
146140
if strValue, ok := segmentCondition.Value.(string); ok {
147141
// Try to parse as JSON array first
148-
var jsonArray []string
142+
var jsonArray []interface{}
149143
if err := json.Unmarshal([]byte(strValue), &jsonArray); err == nil {
150-
return slices.Contains(jsonArray, traitValue)
144+
return matchInArrayInterface(jsonArray, contextValue)
151145
}
152146

153147
// Fall back to comma-separated string
154148
values := strings.Split(strValue, ",")
155-
return slices.Contains(values, traitValue)
149+
// Try numeric comparison for each value
150+
for _, v := range values {
151+
if compareValues(contextValue, strings.TrimSpace(v)) {
152+
return true
153+
}
154+
}
155+
return false
156156
}
157157

158158
return false
159159
}
160160

161+
// matchInArrayInterface checks if contextValue matches any element in the array.
162+
func matchInArrayInterface(array []interface{}, contextValue ContextValue) bool {
163+
for _, v := range array {
164+
if compareValues(contextValue, v) {
165+
return true
166+
}
167+
}
168+
return false
169+
}
170+
171+
// compareValues compares two values with type-aware comparison.
172+
// Supports int, float and string comparisons.
173+
func compareValues(v1, v2 interface{}) bool {
174+
// Direct equality check first
175+
if v1 == v2 {
176+
return true
177+
}
178+
179+
// Try numeric comparison - convert both to float64
180+
f1, ok1 := toFloat64(v1)
181+
f2, ok2 := toFloat64(v2)
182+
if ok1 && ok2 {
183+
return f1 == f2
184+
}
185+
186+
// Fall back to string comparison
187+
return ToString(v1) == ToString(v2)
188+
}
189+
190+
// toFloat64 attempts to convert a value to float64.
191+
func toFloat64(v interface{}) (float64, bool) {
192+
switch val := v.(type) {
193+
case float64:
194+
return val, true
195+
case int64:
196+
return float64(val), true
197+
case string:
198+
if f, err := strconv.ParseFloat(val, 64); err == nil {
199+
return f, true
200+
}
201+
}
202+
return 0, false
203+
}
204+
161205
func getContextValue(ec *EngineEvaluationContext, property string) ContextValue {
162206
if strings.HasPrefix(property, "$.") {
163207
value := getContextValueGetter(property)(ec)

0 commit comments

Comments
 (0)