forked from growthbook/growthbook-golang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
302 lines (275 loc) · 7.89 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package growthbook
import (
"encoding/json"
"net/url"
"regexp"
"time"
)
// ExperimentOverride provides the possibility to temporarily override
// some experiment settings.
type ExperimentOverride struct {
Condition Condition
Weights []float64
Active *bool
Status *ExperimentStatus
Force *int
Coverage *float64
Groups []string
Namespace *Namespace
URL *regexp.Regexp
}
type ExperimentOverrides map[string]*ExperimentOverride
// Context contains the options for creating a new GrowthBook
// instance.
type Context struct {
Enabled bool
Attributes Attributes
URL *url.URL
Features FeatureMap
ForcedVariations ForcedVariationsMap
QAMode bool
DevMode bool
TrackingCallback ExperimentCallback
OnFeatureUsage FeatureUsageCallback
UserAttributes Attributes
Groups map[string]bool
APIHost string
ClientKey string
DecryptionKey string
Overrides ExperimentOverrides
CacheTTL time.Duration
}
// ExperimentCallback is a callback function that is executed every
// time a user is included in an Experiment. It is also the type used
// for subscription functions, which are called whenever
// Experiment.Run is called and the experiment result changes,
// independent of whether a user is inncluded in the experiment or
// not.
type ExperimentCallback func(experiment *Experiment, result *Result)
// FeatureUsageCallback is a callback function that is executed every
// time a feature is evaluated.
type FeatureUsageCallback func(key string, result *FeatureResult)
// NewContext creates a context with default settings: enabled, but
// all other fields empty.
func NewContext() *Context {
return &Context{
Enabled: true,
CacheTTL: 60 * time.Second,
}
}
// WithEnabled sets the enabled flag for a context.
func (ctx *Context) WithEnabled(enabled bool) *Context {
ctx.Enabled = enabled
return ctx
}
// WithAttributes sets the attributes for a context.
func (ctx *Context) WithAttributes(attributes Attributes) *Context {
savedAttributes := Attributes{}
for k, v := range attributes {
savedAttributes[k] = fixSliceTypes(v)
}
ctx.Attributes = savedAttributes
return ctx
}
// WithUserAttributes sets the user attributes for a context.
func (ctx *Context) WithUserAttributes(attributes Attributes) *Context {
savedAttributes := Attributes{}
for k, v := range attributes {
savedAttributes[k] = fixSliceTypes(v)
}
ctx.UserAttributes = savedAttributes
return ctx
}
// WithURL sets the URL for a context.
func (ctx *Context) WithURL(url *url.URL) *Context {
ctx.URL = url
return ctx
}
// WithFeatures sets the features for a context (as a value of type
// FeatureMap, which is a map from feature names to *Feature values).
func (ctx *Context) WithFeatures(features FeatureMap) *Context {
ctx.Features = features
return ctx
}
// WithForcedVariations sets the forced variations for a context (as a
// value of type ForcedVariationsMap, which is a map from experiment
// keys to variation indexes).
func (ctx *Context) WithForcedVariations(forcedVariations ForcedVariationsMap) *Context {
ctx.ForcedVariations = forcedVariations
return ctx
}
func (ctx *Context) ForceVariation(key string, variation int) {
if ctx.ForcedVariations == nil {
ctx.ForcedVariations = ForcedVariationsMap{}
}
ctx.ForcedVariations[key] = variation
}
func (ctx *Context) UnforceVariation(key string) {
delete(ctx.ForcedVariations, key)
}
// WithQAMode can be used to enable or disable the QA mode for a
// context.
func (ctx *Context) WithQAMode(qaMode bool) *Context {
ctx.QAMode = qaMode
return ctx
}
// WithDevMode can be used to enable or disable the development mode
// for a context.
func (ctx *Context) WithDevMode(devMode bool) *Context {
ctx.DevMode = devMode
return ctx
}
// WithTrackingCallback is used to set a tracking callback for a
// context.
func (ctx *Context) WithTrackingCallback(callback ExperimentCallback) *Context {
ctx.TrackingCallback = callback
return ctx
}
// WithFeatureUsageCallback is used to set a feature usage callback
// for a context.
func (ctx *Context) WithFeatureUsageCallback(callback FeatureUsageCallback) *Context {
ctx.OnFeatureUsage = callback
return ctx
}
// WithGroups sets the groups map of a context.
func (ctx *Context) WithGroups(groups map[string]bool) *Context {
ctx.Groups = groups
return ctx
}
// WithAPIHost sets the API host of a context.
func (ctx *Context) WithAPIHost(host string) *Context {
ctx.APIHost = host
return ctx
}
// WithClientKey sets the API client key of a context.
func (ctx *Context) WithClientKey(key string) *Context {
ctx.ClientKey = key
return ctx
}
// WithDecryptionKey sets the decryption key of a context.
func (ctx *Context) WithDecryptionKey(key string) *Context {
ctx.DecryptionKey = key
return ctx
}
// WithOverrides sets the experiment overrides of a context.
func (ctx *Context) WithOverrides(overrides ExperimentOverrides) *Context {
ctx.Overrides = overrides
return ctx
}
// WithCacheTTL sets the TTL for the feature cache.
func (ctx *Context) WithCacheTTL(ttl time.Duration) *Context {
ctx.CacheTTL = ttl
return ctx
}
// ParseContext creates a Context value from raw JSON input.
func ParseContext(data []byte) *Context {
dict := make(map[string]interface{})
err := json.Unmarshal(data, &dict)
if err != nil {
logError("Failed parsing JSON input", "Context")
return NewContext()
}
return BuildContext(dict)
}
// BuildContext creates a Context value from a JSON object represented
// as a Go map.
func BuildContext(dict map[string]interface{}) *Context {
context := NewContext()
for k, v := range dict {
switch k {
case "enabled":
enabled, ok := v.(bool)
if ok {
context = context.WithEnabled(enabled)
} else {
logWarn("Invalid 'enabled' field in JSON context data")
}
case "attributes":
attrs, ok := v.(map[string]interface{})
if ok {
context = context.WithAttributes(attrs)
} else {
logWarn("Invalid 'attributes' field in JSON context data")
}
case "url":
urlString, ok := v.(string)
if ok {
url, err := url.Parse(urlString)
if err != nil {
logError("Invalid URL in JSON context data", urlString)
} else {
context = context.WithURL(url)
}
} else {
logWarn("Invalid 'url' field in JSON context data")
}
case "features":
features, ok := v.(map[string]interface{})
if ok {
context.Features = BuildFeatureMap(features)
} else {
logWarn("Invalid 'features' field in JSON context data")
}
case "forcedVariations":
forcedVariations, ok := v.(map[string]interface{})
if ok {
vars := make(map[string]int)
allVOK := true
for k, vr := range forcedVariations {
v, vok := vr.(float64)
if !vok {
allVOK = false
break
}
vars[k] = int(v)
}
if allVOK {
context = context.WithForcedVariations(vars)
} else {
ok = false
}
}
if !ok {
logWarn("Invalid 'forcedVariations' field in JSON context data")
}
case "qaMode":
qaMode, ok := v.(bool)
if ok {
context = context.WithQAMode(qaMode)
} else {
logWarn("Invalid 'qaMode' field in JSON context data")
}
case "groups":
groups, ok := v.(map[string]bool)
if ok {
context = context.WithGroups(groups)
} else {
logWarn("Invalid 'groups' field in JSON context data")
}
case "apiHost":
apiHost, ok := v.(string)
if ok {
context = context.WithAPIHost(apiHost)
} else {
logWarn("Invalid 'apiHost' field in JSON context data")
}
case "clientKey":
clientKey, ok := v.(string)
if ok {
context = context.WithClientKey(clientKey)
} else {
logWarn("Invalid 'clientKey' field in JSON context data")
}
case "decryptionKey":
decryptionKey, ok := v.(string)
if ok {
context = context.WithDecryptionKey(decryptionKey)
} else {
logWarn("Invalid 'decryptionKey' field in JSON context data")
}
default:
logWarn("Unknown key in JSON data", "Context", k)
}
}
return context
}