Skip to content

Commit bf8c56b

Browse files
allow subject attributes to be string wrapper numerics; attempt to safely coerce (#35)
* allow subject attributes to be string wrapper numerics; attempt to safely coerce * add comment
1 parent 5f87650 commit bf8c56b

File tree

4 files changed

+76
-5
lines changed

4 files changed

+76
-5
lines changed

eppoclient/rules.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ func evaluateCondition(subjectAttributes dictionary, condition condition) bool {
6161
case "NOT_ONE_OF":
6262
return isNotOneOf(subjectValue, convertToStringArray(condition.Value))
6363
default:
64-
// Attempt to evaluate as numeric condition if both values are numeric.
65-
subjectValueNumeric, isNumericSubject := subjectValue.(float64) // Assuming float64 for general numeric comparison; adjust as needed.
66-
conditionValueNumeric, isNumericCondition := condition.Value.(float64) // Same assumption as above.
67-
if isNumericSubject && isNumericCondition {
64+
// Attempt to coerce both values to float64 and compare them.
65+
subjectValueNumeric, isNumericSubjectErr := ToFloat64(subjectValue)
66+
conditionValueNumeric, isNumericConditionErr := ToFloat64(condition.Value)
67+
if isNumericSubjectErr == nil && isNumericConditionErr == nil {
6868
return evaluateNumericCondition(subjectValueNumeric, conditionValueNumeric, condition)
6969
}
7070

eppoclient/rules_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,24 @@ func Test_findMatchingRule_whenNoRulesMatch(t *testing.T) {
4141
}
4242

4343
func Test_findMatchingRule_Success(t *testing.T) {
44+
// both numeric and string wrapped numeric attributes must match.
45+
46+
// Test with a numeric value
4447
subjectAttributes := make(dictionary)
4548
subjectAttributes["age"] = 99.0
4649

47-
result, _ := findMatchingRule(subjectAttributes, []rule{numericRule})
50+
result, err := findMatchingRule(subjectAttributes, []rule{numericRule})
51+
52+
assert.NoError(t, err)
53+
assert.Equal(t, numericRule, result)
54+
55+
// Test with a string value
56+
subjectAttributes = make(dictionary)
57+
subjectAttributes["age"] = "99.0"
58+
59+
result, err = findMatchingRule(subjectAttributes, []rule{numericRule})
4860

61+
assert.NoError(t, err)
4962
assert.Equal(t, numericRule, result)
5063
}
5164

eppoclient/utils.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package eppoclient
22

3+
import (
4+
"errors"
5+
"fmt"
6+
"strconv"
7+
)
8+
39
type dictionary map[string]interface{}
410

511
type testData struct {
@@ -26,3 +32,21 @@ type testDataShardRange struct {
2632
Start int `json:"start"`
2733
End int `json:"end"`
2834
}
35+
36+
// ToFloat64 attempts to convert an interface{} value to a float64.
37+
// It supports inputs of type float64 or string (which can be parsed as float64).
38+
// Returns a float64 and nil error on success, or 0 and an error on failure.
39+
func ToFloat64(val interface{}) (float64, error) {
40+
switch v := val.(type) {
41+
case float64:
42+
return v, nil
43+
case string:
44+
floatVal, err := strconv.ParseFloat(v, 64)
45+
if err != nil {
46+
return 0, fmt.Errorf("cannot convert string '%s' to float64: %w", v, err)
47+
}
48+
return floatVal, nil
49+
default:
50+
return 0, errors.New("value is neither a float64 nor a convertible string")
51+
}
52+
}

eppoclient/utils_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package eppoclient
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestToFloat64(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
input interface{}
11+
expected float64
12+
expectErr bool
13+
}{
14+
{"Float64Input", 123.456, 123.456, false},
15+
{"StringInputValid", "789.012", 789.012, false},
16+
{"StringInputInvalid", "abc", 0, true},
17+
{"SemVerInputInvalid", "1.2.3", 0, true},
18+
{"BoolInput", true, 0, true},
19+
{"IntInput", 123, 0, true},
20+
}
21+
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
result, err := ToFloat64(tt.input)
25+
if (err != nil) != tt.expectErr {
26+
t.Errorf("ToFloat64(%v) error = %v, expectErr %v", tt.input, err, tt.expectErr)
27+
return
28+
}
29+
if !tt.expectErr && result != tt.expected {
30+
t.Errorf("ToFloat64(%v) = %v, want %v", tt.input, result, tt.expected)
31+
}
32+
})
33+
}
34+
}

0 commit comments

Comments
 (0)