1
1
package eppoclient
2
2
3
3
import (
4
- "crypto/md5"
5
- "encoding/hex"
6
- "errors"
7
4
"fmt"
8
5
)
9
6
7
+ type SubjectAttributes map [string ]interface {}
8
+
10
9
// Client for eppo.cloud. Instance of this struct will be created on calling InitClient.
11
10
// EppoClient will then immediately start polling experiments data from Eppo.
12
11
type EppoClient struct {
@@ -15,43 +14,73 @@ type EppoClient struct {
15
14
logger IAssignmentLogger
16
15
}
17
16
18
- func newEppoClient (configRequestor iConfigRequestor , assignmentLogger IAssignmentLogger ) * EppoClient {
17
+ func newEppoClient (configRequestor iConfigRequestor , poller * poller , assignmentLogger IAssignmentLogger ) * EppoClient {
19
18
var ec = & EppoClient {}
20
19
21
- var poller = newPoller (10 , configRequestor .FetchAndStoreConfigurations )
22
20
ec .poller = * poller
23
21
ec .configRequestor = configRequestor
24
22
ec .logger = assignmentLogger
25
23
26
24
return ec
27
25
}
28
26
29
- // GetAssignment is maintained for backwards capability. It will return a string value for the assignment.
30
- func (ec * EppoClient ) GetAssignment (subjectKey string , flagKey string , subjectAttributes dictionary ) (string , error ) {
31
- return ec .GetStringAssignment (subjectKey , flagKey , subjectAttributes )
27
+ func (ec * EppoClient ) GetBoolAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , defaultValue bool ) (bool , error ) {
28
+ variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , booleanVariation )
29
+ if err != nil || variation == nil {
30
+ return defaultValue , err
31
+ }
32
+ result , ok := variation .(bool )
33
+ if ! ok {
34
+ return defaultValue , fmt .Errorf ("failed to cast %v to bool" , variation )
35
+ }
36
+ return result , err
32
37
}
33
38
34
- func (ec * EppoClient ) GetBoolAssignment (subjectKey string , flagKey string , subjectAttributes dictionary ) (bool , error ) {
35
- variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , BoolType )
36
- return variation .BoolValue , err
39
+ func (ec * EppoClient ) GetNumericAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , defaultValue float64 ) (float64 , error ) {
40
+ variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , numericVariation )
41
+ if err != nil || variation == nil {
42
+ return defaultValue , err
43
+ }
44
+ result , ok := variation .(float64 )
45
+ if ! ok {
46
+ return defaultValue , fmt .Errorf ("failed to cast %v to float64" , variation )
47
+ }
48
+ return result , err
37
49
}
38
50
39
- func (ec * EppoClient ) GetNumericAssignment (subjectKey string , flagKey string , subjectAttributes dictionary ) (float64 , error ) {
40
- variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , NumericType )
41
- return variation .NumericValue , err
51
+ func (ec * EppoClient ) GetIntegerAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , defaultValue int64 ) (int64 , error ) {
52
+ variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , integerVariation )
53
+ if err != nil || variation == nil {
54
+ return defaultValue , err
55
+ }
56
+ result , ok := variation .(int64 )
57
+ if ! ok {
58
+ return defaultValue , fmt .Errorf ("failed to cast %v to int64" , variation )
59
+ }
60
+ return result , err
42
61
}
43
62
44
- func (ec * EppoClient ) GetStringAssignment (subjectKey string , flagKey string , subjectAttributes dictionary ) (string , error ) {
45
- variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , StringType )
46
- return variation .StringValue , err
63
+ func (ec * EppoClient ) GetStringAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , defaultValue string ) (string , error ) {
64
+ variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , stringVariation )
65
+ if err != nil || variation == nil {
66
+ return defaultValue , err
67
+ }
68
+ result , ok := variation .(string )
69
+ if ! ok {
70
+ return defaultValue , fmt .Errorf ("failed to cast %v to string" , variation )
71
+ }
72
+ return result , err
47
73
}
48
74
49
- func (ec * EppoClient ) GetJSONStringAssignment (subjectKey string , flagKey string , subjectAttributes dictionary ) (string , error ) {
50
- variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , StringType )
51
- return variation .StringValue , err
75
+ func (ec * EppoClient ) GetJSONAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , defaultValue interface {}) (interface {}, error ) {
76
+ variation , err := ec .getAssignment (subjectKey , flagKey , subjectAttributes , jsonVariation )
77
+ if err != nil || variation == nil {
78
+ return defaultValue , err
79
+ }
80
+ return variation , err
52
81
}
53
82
54
- func (ec * EppoClient ) getAssignment (subjectKey string , flagKey string , subjectAttributes dictionary , valueType ValueType ) (Value , error ) {
83
+ func (ec * EppoClient ) getAssignment (subjectKey string , flagKey string , subjectAttributes SubjectAttributes , variationType variationType ) (interface {} , error ) {
55
84
if subjectKey == "" {
56
85
panic ("no subject key provided" )
57
86
}
@@ -60,86 +89,35 @@ func (ec *EppoClient) getAssignment(subjectKey string, flagKey string, subjectAt
60
89
panic ("no flag key provided" )
61
90
}
62
91
63
- config , err := ec .configRequestor .GetConfiguration (flagKey )
92
+ flag , err := ec .configRequestor .GetConfiguration (flagKey )
64
93
if err != nil {
65
- return Null (), err
66
- }
67
-
68
- override := getSubjectVariationOverride (config , subjectKey , valueType )
69
- if override != Null () {
70
- return override , nil
71
- }
72
-
73
- // Check if disabled
74
- if ! config .Enabled {
75
- return Null (), errors .New ("the experiment or flag is not enabled" )
94
+ return nil , err
76
95
}
77
96
78
- // Find matching rule
79
- rule , err := findMatchingRule (subjectAttributes , config .Rules )
97
+ err = flag .verifyType (variationType )
80
98
if err != nil {
81
- return Null (), err
82
- }
83
-
84
- // Check if in sample population
85
- allocation := config .Allocations [rule .AllocationKey ]
86
- if ! isInExperimentSample (subjectKey , flagKey , config .SubjectShards , allocation .PercentExposure ) {
87
- return Null (), errors .New ("subject not part of the sample population" )
99
+ return nil , err
88
100
}
89
101
90
- // Get assigned variation
91
- assignmentKey := "assignment-" + subjectKey + "-" + flagKey
92
- shard := getShard (assignmentKey , config .SubjectShards )
93
- variations := allocation .Variations
94
- var variationShard Variation
95
-
96
- for _ , variation := range variations {
97
- if isShardInRange (shard , variation .ShardRange ) {
98
- variationShard = variation
99
- }
102
+ assignmentValue , assignmentEvent , err := flag .eval (subjectKey , subjectAttributes )
103
+ if err != nil {
104
+ return nil , err
100
105
}
101
106
102
- assignedVariation := variationShard .Value
103
-
104
- func () {
105
- // need to catch panics from Logger and continue
106
- defer func () {
107
- r := recover ()
108
- if r != nil {
109
- fmt .Println ("panic occurred:" , r )
110
- }
107
+ if assignmentEvent != nil {
108
+ func () {
109
+ // need to catch panics from Logger and continue
110
+ defer func () {
111
+ r := recover ()
112
+ if r != nil {
113
+ fmt .Println ("panic occurred:" , r )
114
+ }
115
+ }()
116
+
117
+ // Log assignment
118
+ ec .logger .LogAssignment (* assignmentEvent )
111
119
}()
112
-
113
- // Log assignment
114
- assignmentEvent := AssignmentEvent {
115
- Experiment : flagKey + "-" + rule .AllocationKey ,
116
- FeatureFlag : flagKey ,
117
- Allocation : rule .AllocationKey ,
118
- Variation : assignedVariation ,
119
- Subject : subjectKey ,
120
- Timestamp : TimeNow (),
121
- SubjectAttributes : subjectAttributes ,
122
- }
123
- ec .logger .LogAssignment (assignmentEvent )
124
- }()
125
-
126
- return assignedVariation , nil
127
- }
128
-
129
- func getSubjectVariationOverride (experimentConfig experimentConfiguration , subject string , valueType ValueType ) Value {
130
- hash := md5 .Sum ([]byte (subject ))
131
- hashOutput := hex .EncodeToString (hash [:])
132
-
133
- if val , ok := experimentConfig .Overrides [hashOutput ]; ok {
134
- return val
135
120
}
136
121
137
- return Null ()
138
- }
139
-
140
- func isInExperimentSample (subjectKey string , flagKey string , subjectShards int64 , percentExposure float32 ) bool {
141
- shardKey := "exposure-" + subjectKey + "-" + flagKey
142
- shard := getShard (shardKey , subjectShards )
143
-
144
- return float64 (shard ) <= float64 (percentExposure )* float64 (subjectShards )
122
+ return assignmentValue , nil
145
123
}
0 commit comments