Skip to content

Commit 8f4becf

Browse files
Add marshaling and unmarshaling of Value struct (FF-1382) (#28)
* Add unit test to verify undesired functionality (FF-1382) * marshall and unmarshall Value * handle marshalling all types * json string serialization * test subject attributes * add error handling
1 parent 8dc51f3 commit 8f4becf

File tree

6 files changed

+169
-64
lines changed

6 files changed

+169
-64
lines changed

eppoclient/assignmentlogger.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ type IAssignmentLogger interface {
77
}
88

99
type AssignmentEvent struct {
10-
Experiment string
11-
FeatureFlag string
12-
Allocation string
13-
Variation Value
14-
Subject string
15-
Timestamp string
16-
SubjectAttributes dictionary
10+
Experiment string `json:"experiment"`
11+
FeatureFlag string `json:"featureFlag"`
12+
Allocation string `json:"allocation"`
13+
Variation Value `json:"variation"`
14+
Subject string `json:"subject"`
15+
Timestamp string `json:"timestamp"`
16+
SubjectAttributes dictionary `json:"subjectAttributes,omitempty"`
1717
}
1818

1919
type AssignmentLogger struct {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package eppoclient
2+
3+
import (
4+
"encoding/json"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
// TestAssignmentEventSerialization tests the JSON serialization and deserialization of AssignmentEvent
10+
func TestAssignmentEventSerialization(t *testing.T) {
11+
// Create a test case with each type
12+
testCases := []AssignmentEvent{
13+
// empty subject attributes
14+
{
15+
Experiment: "testExperiment",
16+
FeatureFlag: "testFeatureFlag",
17+
Allocation: "testAllocation",
18+
Variation: String("testVariation"),
19+
Subject: "testSubject",
20+
Timestamp: "testTimestamp",
21+
},
22+
{
23+
Experiment: "testExperiment",
24+
FeatureFlag: "testFeatureFlag",
25+
Allocation: "testAllocation",
26+
Variation: Bool(true),
27+
Subject: "testSubject",
28+
Timestamp: "testTimestamp",
29+
SubjectAttributes: dictionary{"testKey": "testValue"},
30+
},
31+
{
32+
Experiment: "testExperiment",
33+
FeatureFlag: "testFeatureFlag",
34+
Allocation: "testAllocation",
35+
Variation: Numeric(123.45),
36+
Subject: "testSubject",
37+
Timestamp: "testTimestamp",
38+
SubjectAttributes: dictionary{"testKey": "testValue"},
39+
},
40+
{
41+
Experiment: "testExperiment",
42+
FeatureFlag: "testFeatureFlag",
43+
Allocation: "testAllocation",
44+
Variation: String("testVariation"),
45+
Subject: "testSubject",
46+
Timestamp: "testTimestamp",
47+
SubjectAttributes: dictionary{"testKey": "testValue"},
48+
},
49+
{
50+
Experiment: "testExperiment",
51+
FeatureFlag: "testFeatureFlag",
52+
Allocation: "testAllocation",
53+
Variation: String("{\"foo\":\"bar\",\"car\":\"far\"}"),
54+
Subject: "testSubject",
55+
Timestamp: "testTimestamp",
56+
SubjectAttributes: dictionary{"testKey": "testValue"},
57+
},
58+
}
59+
60+
for _, original := range testCases {
61+
// Marshal to JSON
62+
marshaled, err := json.Marshal(original)
63+
if err != nil {
64+
t.Errorf("Failed to marshal: %v", err)
65+
}
66+
67+
// Unmarshal from JSON
68+
var unmarshaled AssignmentEvent
69+
err = json.Unmarshal(marshaled, &unmarshaled)
70+
if err != nil {
71+
t.Errorf("Failed to unmarshal: %v", err)
72+
}
73+
74+
// Compare the original and unmarshaled
75+
if !reflect.DeepEqual(original, unmarshaled) {
76+
t.Errorf("Original and unmarshaled Value are not equal. Original: %+v, Unmarshaled: %+v", original, unmarshaled)
77+
}
78+
}
79+
}

eppoclient/client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,22 @@ func (ec *EppoClient) GetAssignment(subjectKey string, flagKey string, subjectAt
3535

3636
func (ec *EppoClient) GetBoolAssignment(subjectKey string, flagKey string, subjectAttributes dictionary) (bool, error) {
3737
variation, err := ec.getAssignment(subjectKey, flagKey, subjectAttributes, BoolType)
38-
return variation.boolValue, err
38+
return variation.BoolValue, err
3939
}
4040

4141
func (ec *EppoClient) GetNumericAssignment(subjectKey string, flagKey string, subjectAttributes dictionary) (float64, error) {
4242
variation, err := ec.getAssignment(subjectKey, flagKey, subjectAttributes, NumericType)
43-
return variation.numericValue, err
43+
return variation.NumericValue, err
4444
}
4545

4646
func (ec *EppoClient) GetStringAssignment(subjectKey string, flagKey string, subjectAttributes dictionary) (string, error) {
4747
variation, err := ec.getAssignment(subjectKey, flagKey, subjectAttributes, StringType)
48-
return variation.stringValue, err
48+
return variation.StringValue, err
4949
}
5050

5151
func (ec *EppoClient) GetJSONStringAssignment(subjectKey string, flagKey string, subjectAttributes dictionary) (string, error) {
5252
variation, err := ec.getAssignment(subjectKey, flagKey, subjectAttributes, StringType)
53-
return variation.stringValue, err
53+
return variation.StringValue, err
5454
}
5555

5656
func (ec *EppoClient) getAssignment(subjectKey string, flagKey string, subjectAttributes dictionary, valueType ValueType) (Value, error) {

eppoclient/client_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,20 +232,20 @@ func Test_WithSubjectInOverrides(t *testing.T) {
232232
case StringType:
233233
assignment, _ := client.GetStringAssignment("user-1", "experiment-key-1", dictionary{})
234234

235-
if assignment != tt.want.stringValue {
236-
t.Errorf("got %s, want %s", assignment, tt.want.stringValue)
235+
if assignment != tt.want.StringValue {
236+
t.Errorf("got %s, want %s", assignment, tt.want.StringValue)
237237
}
238238
case NumericType:
239239
assignment, _ := client.GetNumericAssignment("user-1", "experiment-key-1", dictionary{})
240240

241-
if assignment != tt.want.numericValue {
242-
t.Errorf("got %T, want %T", assignment, tt.want.numericValue)
241+
if assignment != tt.want.NumericValue {
242+
t.Errorf("got %T, want %T", assignment, tt.want.NumericValue)
243243
}
244244
case BoolType:
245245
assignment, _ := client.GetBoolAssignment("user-1", "experiment-key-1", dictionary{})
246246

247-
if assignment != tt.want.boolValue {
248-
t.Errorf("got %t, want %t", assignment, tt.want.boolValue)
247+
if assignment != tt.want.BoolValue {
248+
t.Errorf("got %t, want %t", assignment, tt.want.BoolValue)
249249
}
250250

251251
}

eppoclient/eppoclient_e2e_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,25 @@ func Test_e2e(t *testing.T) {
106106
case "boolean":
107107
expectedAssignments := []bool{}
108108
for _, assignment := range experiment.ExpectedAssignments {
109-
expectedAssignments = append(expectedAssignments, assignment.boolValue)
109+
expectedAssignments = append(expectedAssignments, assignment.BoolValue)
110110
}
111111
assert.Equal(t, expectedAssignments, booleanAssignments)
112112
case "json":
113113
expectedAssignments := []string{}
114114
for _, assignment := range experiment.ExpectedAssignments {
115-
expectedAssignments = append(expectedAssignments, assignment.stringValue)
115+
expectedAssignments = append(expectedAssignments, assignment.StringValue)
116116
}
117117
assert.Equal(t, expectedAssignments, jsonAssignments)
118118
case "numeric":
119119
expectedAssignments := []float64{}
120120
for _, assignment := range experiment.ExpectedAssignments {
121-
expectedAssignments = append(expectedAssignments, assignment.numericValue)
121+
expectedAssignments = append(expectedAssignments, assignment.NumericValue)
122122
}
123123
assert.Equal(t, expectedAssignments, numericAssignments)
124124
case "string":
125125
expectedAssignments := []string{}
126126
for _, assignment := range experiment.ExpectedAssignments {
127-
expectedAssignments = append(expectedAssignments, assignment.stringValue)
127+
expectedAssignments = append(expectedAssignments, assignment.StringValue)
128128
}
129129
assert.Equal(t, expectedAssignments, stringAssignments)
130130
}

eppoclient/value.go

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package eppoclient
22

33
import (
44
"encoding/json"
5+
"fmt"
56
)
67

78
type ValueType int
@@ -14,79 +15,104 @@ const (
1415
)
1516

1617
type Value struct {
17-
valueType ValueType
18-
boolValue bool
19-
numericValue float64
20-
stringValue string
18+
ValueType ValueType `json:"valueType"`
19+
BoolValue bool `json:"boolValue,omitempty"`
20+
NumericValue float64 `json:"numericValue,omitempty"`
21+
StringValue string `json:"stringValue,omitempty"`
2122
}
2223

2324
func Null() Value {
24-
return Value{valueType: NullType}
25+
return Value{ValueType: NullType}
2526
}
2627

2728
func Bool(value bool) Value {
28-
return Value{valueType: BoolType, boolValue: value}
29+
return Value{ValueType: BoolType, BoolValue: value}
2930
}
3031

3132
func Numeric(value float64) Value {
32-
return Value{valueType: NumericType, numericValue: value}
33+
return Value{ValueType: NumericType, NumericValue: value}
3334
}
3435

3536
func String(value string) Value {
36-
return Value{valueType: StringType, stringValue: value}
37+
return Value{ValueType: StringType, StringValue: value}
3738
}
3839

39-
func (receiver *Value) UnmarshalJSON(data []byte) error {
40-
var valueInterface interface{}
41-
if err := json.Unmarshal(data, &valueInterface); err != nil {
42-
return err
40+
func (v Value) GetBoolValue() bool {
41+
return v.ValueType == BoolType && v.BoolValue
42+
}
43+
44+
func (v Value) GetStringValue() string {
45+
if v.ValueType == StringType {
46+
return v.StringValue
4347
}
44-
*receiver = castInterfaceToValue(valueInterface)
48+
return ""
49+
}
4550

46-
return nil
51+
func (v Value) GetNumericValue() float64 {
52+
if v.ValueType == NumericType {
53+
return v.NumericValue
54+
}
55+
56+
return 0
4757
}
4858

49-
func castInterfaceToValue(valueInterface interface{}) Value {
50-
if valueInterface == nil {
51-
return Null()
59+
func (v Value) MarshalJSON() ([]byte, error) {
60+
switch v.ValueType {
61+
case BoolType:
62+
return json.Marshal(v.BoolValue)
63+
case NumericType:
64+
return json.Marshal(v.NumericValue)
65+
case StringType:
66+
return json.Marshal(v.StringValue)
67+
case NullType:
68+
return json.Marshal(nil)
69+
default:
70+
return nil, fmt.Errorf("unsupported value type")
5271
}
53-
switch v := valueInterface.(type) {
72+
}
73+
74+
func (v *Value) UnmarshalJSON(data []byte) error {
75+
// Unmarshal the data into an interface{} to check its type
76+
var typedValue interface{}
77+
if err := json.Unmarshal(data, &typedValue); err != nil {
78+
return err
79+
}
80+
81+
// Determine the type of typedValue and set the Value struct accordingly
82+
switch value := typedValue.(type) {
5483
case Value:
55-
return v
84+
*v = value
5685
case *Value:
5786
if v == nil {
58-
return Null()
87+
*v = Null()
5988
}
60-
return *v
89+
case string:
90+
*v = String(value)
91+
case *string:
92+
if v == nil {
93+
*v = Null()
94+
}
95+
case float64:
96+
// JSON numbers are float64 by default
97+
*v = Numeric(value)
6198
case bool:
62-
return Bool(v)
99+
*v = Bool(value)
63100
case *bool:
64101
if v == nil {
65-
return Null()
102+
*v = Null()
66103
}
67-
return Bool(*v)
104+
*v = Bool(*value)
68105
case map[string]interface{}:
69-
out, _ := json.Marshal(&v)
70-
return String(string(out))
71-
case string:
72-
return String(v)
73-
case *string:
74-
if v == nil {
75-
return Null()
106+
out, err := json.Marshal(typedValue)
107+
if err != nil {
108+
return err
76109
}
77-
return String(*v)
110+
*v = String(string(out))
111+
case nil:
112+
*v = Null()
78113
default:
79-
return Null()
114+
*v = Null()
80115
}
81-
}
82-
83-
func (v Value) StringValue() string {
84-
if v.valueType == StringType {
85-
return v.stringValue
86-
}
87-
return ""
88-
}
89116

90-
func (v Value) BoolValue() bool {
91-
return v.valueType == BoolType && v.boolValue
117+
return nil
92118
}

0 commit comments

Comments
 (0)