Skip to content

Commit 6085165

Browse files
leoromanovskyerka
andauthored
fix(memprovider): use int64 type in IntEvaluation to match API (#464)
The IntEvaluation method was using genericResolve[int] which caused TYPE_MISMATCH errors when users stored int64 values in Variants. The FeatureProvider interface defines IntEvaluation with int64: IntEvaluation(ctx, flag string, defaultValue int64, ...) IntResolutionDetail The type assertion in genericResolve should use int64 to match. Before this fix: - User stores: Variants{"max": int64(100)} - genericResolve[int] tries: value.(int) - Type assertion fails because int64 != int - Returns default value with TYPE_MISMATCH error After this fix: - User stores: Variants{"max": int64(100)} - genericResolve[int64] tries: value.(int64) - Type assertion succeeds - Returns correct value Updated tests to use explicit int64 types in Variants to match the expected usage pattern for the IntEvaluation API. --------- Signed-off-by: Leo Romanovsky <leo.romanovsky@datadoghq.com> Signed-off-by: Roman Dmytrenko <rdmytrenko@gmail.com> Co-authored-by: Roman Dmytrenko <rdmytrenko@gmail.com>
1 parent 0a2fb41 commit 6085165

File tree

2 files changed

+113
-26
lines changed

2 files changed

+113
-26
lines changed

openfeature/memprovider/in_memory_provider.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ func (i InMemoryProvider) IntEvaluation(ctx context.Context, flag string, defaul
9595
}
9696

9797
resolveFlag, detail := memoryFlag.Resolve(defaultValue, flatCtx)
98-
result := genericResolve[int](resolveFlag, int(defaultValue), &detail)
98+
result := genericResolve[int64](resolveFlag, defaultValue, &detail)
9999

100100
return openfeature.IntResolutionDetail{
101-
Value: int64(result),
101+
Value: result,
102102
ProviderResolutionDetail: detail,
103103
}
104104
}
@@ -156,14 +156,41 @@ func (i InMemoryProvider) find(flag string) (*InMemoryFlag, *openfeature.Provide
156156

157157
// helpers
158158

159-
// genericResolve is a helper to extract type verified evaluation and fill openfeature.ProviderResolutionDetail
159+
// genericResolve is a helper to extract type verified evaluation and fill openfeature.ProviderResolutionDetail.
160+
// It coerces smaller numeric types to their canonical forms (int* -> int64, float32 -> float64)
161+
// to provide a more forgiving API for test flag configuration.
162+
//
163+
// Note: Only signed integer types are supported for conversion. Unsigned integer types
164+
// (uint, uint8, uint16, uint32, uint64) will result in type mismatch errors.
160165
func genericResolve[T comparable](value any, defaultValue T, detail *openfeature.ProviderResolutionDetail) T {
161-
v, ok := value.(T)
162-
163-
if ok {
166+
// Try direct type assertion first
167+
if v, ok := value.(T); ok {
164168
return v
165169
}
166170

171+
// Handle type conversions based on target type
172+
switch any(defaultValue).(type) {
173+
case int64:
174+
// Convert various int types to int64
175+
switch v := value.(type) {
176+
case int8:
177+
return any(int64(v)).(T)
178+
case int16:
179+
return any(int64(v)).(T)
180+
case int32:
181+
return any(int64(v)).(T)
182+
case int:
183+
return any(int64(v)).(T)
184+
}
185+
case float64:
186+
// Convert float32 to float64 and int types to float64
187+
switch v := value.(type) {
188+
case float32:
189+
return any(float64(v)).(T)
190+
}
191+
}
192+
193+
// If no conversion worked, return error
167194
detail.Reason = openfeature.ErrorReason
168195
detail.ResolutionError = openfeature.NewTypeMismatchResolutionError("incorrect type association")
169196
return defaultValue

openfeature/memprovider/in_memory_provider_test.go

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package memprovider
22

33
import (
4+
"math"
45
"testing"
56

67
"github.com/open-feature/go-sdk/openfeature"
@@ -68,6 +69,16 @@ func TestInMemoryProvider_Float(t *testing.T) {
6869
},
6970
ContextEvaluator: nil,
7071
},
72+
"float32Flag": {
73+
Key: "float32Flag",
74+
State: Enabled,
75+
DefaultVariant: "fOne",
76+
Variants: map[string]any{
77+
"fOne": float32(3.5),
78+
"fTwo": float32(4.5),
79+
},
80+
ContextEvaluator: nil,
81+
},
7182
})
7283

7384
ctx := t.Context()
@@ -79,31 +90,80 @@ func TestInMemoryProvider_Float(t *testing.T) {
7990
t.Errorf("incorrect evaluation, expected %f, got %f", 1.1, evaluation.Value)
8091
}
8192
})
93+
94+
t.Run("test float32 conversion success", func(t *testing.T) {
95+
evaluation := memoryProvider.FloatEvaluation(ctx, "float32Flag", 1.0, nil)
96+
expected := 3.5
97+
if evaluation.Value != expected {
98+
t.Errorf("incorrect evaluation, expected %f, got %f", expected, evaluation.Value)
99+
}
100+
})
82101
}
83102

84103
func TestInMemoryProvider_Int(t *testing.T) {
85-
memoryProvider := NewInMemoryProvider(map[string]InMemoryFlag{
86-
"intFlag": {
87-
Key: "intFlag",
88-
State: Enabled,
89-
DefaultVariant: "max",
90-
Variants: map[string]any{
91-
"min": -9223372036854775808,
92-
"max": 9223372036854775807,
93-
},
94-
ContextEvaluator: nil,
104+
// Test that both int and int64 variants work correctly.
105+
// The provider coerces int to int64 internally to match the API contract.
106+
tests := []struct {
107+
name string
108+
variant any
109+
defaultValue int64
110+
expected int64
111+
}{
112+
{
113+
name: "int64 max value",
114+
variant: int64(math.MaxInt64),
115+
defaultValue: 1,
116+
expected: math.MaxInt64,
95117
},
96-
})
97-
98-
ctx := t.Context()
99-
100-
t.Run("test integer success", func(t *testing.T) {
101-
evaluation := memoryProvider.IntEvaluation(ctx, "intFlag", 1, nil)
118+
{
119+
name: "int64 min value",
120+
variant: int64(math.MinInt64),
121+
defaultValue: 1,
122+
expected: math.MinInt64,
123+
},
124+
{
125+
name: "plain int coerced to int64",
126+
variant: 42,
127+
defaultValue: 0,
128+
expected: 42,
129+
},
130+
{
131+
name: "int8 coerced to int64",
132+
variant: int8(8),
133+
defaultValue: 0,
134+
expected: 8,
135+
},
136+
{
137+
name: "int16 coerced to int64",
138+
variant: int16(16),
139+
defaultValue: 0,
140+
expected: 16,
141+
},
142+
{
143+
name: "int32 coerced to int64",
144+
variant: int32(32),
145+
defaultValue: 0,
146+
expected: 32,
147+
},
148+
}
102149

103-
if evaluation.Value != 9223372036854775807 {
104-
t.Errorf("incorrect evaluation, expected %d, got %d", 1, evaluation.Value)
105-
}
106-
})
150+
for _, tt := range tests {
151+
t.Run(tt.name, func(t *testing.T) {
152+
memoryProvider := NewInMemoryProvider(map[string]InMemoryFlag{
153+
"intFlag": {
154+
State: Enabled,
155+
DefaultVariant: "value",
156+
Variants: map[string]any{"value": tt.variant},
157+
},
158+
})
159+
160+
evaluation := memoryProvider.IntEvaluation(t.Context(), "intFlag", tt.defaultValue, nil)
161+
162+
if evaluation.Value != tt.expected {
163+
t.Errorf("expected %d, got %d", tt.expected, evaluation.Value)
164+
}
165+
})
166+
}
107167
}
108168

109169
func TestInMemoryProvider_Object(t *testing.T) {

0 commit comments

Comments
 (0)