• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

optimizely / go-sdk / 16894449811

11 Aug 2025 11:13PM UTC coverage: 91.645%. First build
16894449811

Pull #415

github

web-flow
Merge branch 'master' into mpirnovar-holdouts-projconfig-fssdk-11551
Pull Request #415: [FSSDK-11551] Adding project config support for holdouts to go-sdk

39 of 42 new or added lines in 1 file covered. (92.86%)

5397 of 5889 relevant lines covered (91.65%)

9345.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.58
/pkg/config/datafileprojectconfig/config.go
1
/****************************************************************************
2
 * Copyright 2019-2025, Optimizely, Inc. and contributors                   *
3
 *                                                                          *
4
 * Licensed under the Apache License, Version 2.0 (the "License");          *
5
 * you may not use this file except in compliance with the License.         *
6
 * You may obtain a copy of the License at                                  *
7
 *                                                                          *
8
 *    https://www.apache.org/licenses/LICENSE-2.0                           *
9
 *                                                                          *
10
 * Unless required by applicable law or agreed to in writing, software      *
11
 * distributed under the License is distributed on an "AS IS" BASIS,        *
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13
 * See the License for the specific language governing permissions and      *
14
 * limitations under the License.                                           *
15
 ***************************************************************************/
16

17
// Package datafileprojectconfig //
18
package datafileprojectconfig
19

20
import (
21
        "errors"
22
        "fmt"
23

24
        "github.com/optimizely/go-sdk/v2/pkg/config/datafileprojectconfig/mappers"
25
        "github.com/optimizely/go-sdk/v2/pkg/entities"
26
        "github.com/optimizely/go-sdk/v2/pkg/logging"
27
)
28

29
var datafileVersions = map[string]struct{}{
30
        "4": {},
31
}
32

33
// DatafileProjectConfig is a project config backed by a datafile
34
type DatafileProjectConfig struct {
35
        datafile             string
36
        hostForODP           string
37
        publicKeyForODP      string
38
        accountID            string
39
        projectID            string
40
        revision             string
41
        experimentKeyToIDMap map[string]string
42
        audienceMap          map[string]entities.Audience
43
        attributeMap         map[string]entities.Attribute
44
        attributeKeyMap      map[string]entities.Attribute
45
        eventMap             map[string]entities.Event
46
        attributeKeyToIDMap  map[string]string
47
        attributeIDToKeyMap  map[string]string
48
        experimentMap        map[string]entities.Experiment
49
        featureMap           map[string]entities.Feature
50
        groupMap             map[string]entities.Group
51
        rollouts             []entities.Rollout
52
        integrations         []entities.Integration
53
        segments             []string
54
        rolloutMap           map[string]entities.Rollout
55
        anonymizeIP          bool
56
        botFiltering         bool
57
        sendFlagDecisions    bool
58
        sdkKey               string
59
        environmentKey       string
60
        region               string
61

62
        flagVariationsMap map[string][]entities.Variation
63
}
64

65
// GetDatafile returns a string representation of the environment's datafile
66
func (c DatafileProjectConfig) GetDatafile() string {
4✔
67
        return c.datafile
4✔
68
}
4✔
69

70
// GetHostForODP returns hostForODP
71
func (c DatafileProjectConfig) GetHostForODP() string {
2✔
72
        return c.hostForODP
2✔
73
}
2✔
74

75
// GetPublicKeyForODP returns publicKeyForODP
76
func (c DatafileProjectConfig) GetPublicKeyForODP() string {
2✔
77
        return c.publicKeyForODP
2✔
78
}
2✔
79

80
// GetProjectID returns projectID
81
func (c DatafileProjectConfig) GetProjectID() string {
1✔
82
        return c.projectID
1✔
83
}
1✔
84

85
// GetRevision returns revision
86
func (c DatafileProjectConfig) GetRevision() string {
1✔
87
        return c.revision
1✔
88
}
1✔
89

90
// GetAccountID returns accountID
91
func (c DatafileProjectConfig) GetAccountID() string {
1✔
92
        return c.accountID
1✔
93
}
1✔
94

95
// GetAnonymizeIP returns anonymizeIP
96
func (c DatafileProjectConfig) GetAnonymizeIP() bool {
1✔
97
        return c.anonymizeIP
1✔
98
}
1✔
99

100
// GetAttributes returns attributes
101
func (c DatafileProjectConfig) GetAttributes() (attributeList []entities.Attribute) {
1✔
102
        for _, attribute := range c.attributeMap {
3✔
103
                attributeList = append(attributeList, attribute)
2✔
104
        }
2✔
105
        return attributeList
1✔
106
}
107

108
// GetAttributeID returns attributeID
109
func (c DatafileProjectConfig) GetAttributeID(key string) string {
1✔
110
        return c.attributeKeyToIDMap[key]
1✔
111
}
1✔
112

113
// GetAttributeByKey returns the attribute with the given key
114
func (c DatafileProjectConfig) GetAttributeByKey(key string) (entities.Attribute, error) {
4✔
115
        if attribute, ok := c.attributeKeyMap[key]; ok {
6✔
116
                return attribute, nil
2✔
117
        }
2✔
118

119
        return entities.Attribute{}, fmt.Errorf(`attribute with key "%s" not found`, key)
2✔
120
}
121

122
// GetAttributeKeyByID returns the attribute key for the given ID
123
func (c DatafileProjectConfig) GetAttributeKeyByID(id string) (string, error) {
2✔
124
        if key, ok := c.attributeIDToKeyMap[id]; ok {
3✔
125
                return key, nil
1✔
126
        }
1✔
127

128
        return "", fmt.Errorf(`attribute with ID "%s" not found`, id)
1✔
129
}
130

131
// GetBotFiltering returns botFiltering
132
func (c DatafileProjectConfig) GetBotFiltering() bool {
1✔
133
        return c.botFiltering
1✔
134
}
1✔
135

136
// GetSdkKey returns sdkKey for specific environment.
137
func (c DatafileProjectConfig) GetSdkKey() string {
1✔
138
        return c.sdkKey
1✔
139
}
1✔
140

141
// GetEnvironmentKey returns current environment of the datafile.
142
func (c DatafileProjectConfig) GetEnvironmentKey() string {
1✔
143
        return c.environmentKey
1✔
144
}
1✔
145

146
// GetEvents returns all events
147
func (c DatafileProjectConfig) GetEvents() (eventList []entities.Event) {
1✔
148
        for _, event := range c.eventMap {
2✔
149
                eventList = append(eventList, event)
1✔
150
        }
1✔
151
        return eventList
1✔
152
}
153

154
// GetEventByKey returns the event with the given key
155
func (c DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
2✔
156
        if event, ok := c.eventMap[eventKey]; ok {
3✔
157
                return event, nil
1✔
158
        }
1✔
159

160
        return entities.Event{}, fmt.Errorf(`event with key "%s" not found`, eventKey)
1✔
161
}
162

163
// GetFeatureByKey returns the feature with the given key
164
func (c DatafileProjectConfig) GetFeatureByKey(featureKey string) (entities.Feature, error) {
2✔
165
        if feature, ok := c.featureMap[featureKey]; ok {
3✔
166
                return feature, nil
1✔
167
        }
1✔
168

169
        return entities.Feature{}, fmt.Errorf(`feature with key "%s" not found`, featureKey)
1✔
170
}
171

172
// GetVariableByKey returns the featureVariable with the given key
173
func (c DatafileProjectConfig) GetVariableByKey(featureKey, variableKey string) (entities.Variable, error) {
3✔
174

3✔
175
        var variable entities.Variable
3✔
176
        var err = fmt.Errorf(`variable with key "%s" not found`, featureKey)
3✔
177
        if feature, ok := c.featureMap[featureKey]; ok {
5✔
178

2✔
179
                if v, ok := feature.VariableMap[variableKey]; ok {
3✔
180
                        variable = v
1✔
181
                        err = nil
1✔
182
                }
1✔
183
        }
184
        return variable, err
3✔
185
}
186

187
// GetFeatureList returns an array of all the features
188
func (c DatafileProjectConfig) GetFeatureList() (featureList []entities.Feature) {
1✔
189
        for _, feature := range c.featureMap {
2✔
190
                featureList = append(featureList, feature)
1✔
191
        }
1✔
192
        return featureList
1✔
193
}
194

195
// GetExperimentList returns an array of all the experiments
196
func (c DatafileProjectConfig) GetExperimentList() (experimentList []entities.Experiment) {
1✔
197
        for _, experiment := range c.experimentMap {
2✔
198
                experimentList = append(experimentList, experiment)
1✔
199
        }
1✔
200
        return experimentList
1✔
201
}
202

203
// GetIntegrationList returns an array of all the integrations
204
func (c DatafileProjectConfig) GetIntegrationList() (integrationList []entities.Integration) {
1✔
205
        return c.integrations
1✔
206
}
1✔
207

208
// GetSegmentList returns an array of all the segments
209
func (c DatafileProjectConfig) GetSegmentList() (segmentList []string) {
1✔
210
        return c.segments
1✔
211
}
1✔
212

213
// GetRolloutList returns an array of all the rollouts
214
func (c DatafileProjectConfig) GetRolloutList() (rolloutList []entities.Rollout) {
1✔
215
        return c.rollouts
1✔
216
}
1✔
217

218
// GetAudienceList returns an array of all the audiences
219
func (c DatafileProjectConfig) GetAudienceList() (audienceList []entities.Audience) {
1✔
220
        for _, audience := range c.audienceMap {
3✔
221
                audienceList = append(audienceList, audience)
2✔
222
        }
2✔
223
        return audienceList
1✔
224
}
225

226
// GetAudienceByID returns the audience with the given ID
227
func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audience, error) {
2✔
228
        if audience, ok := c.audienceMap[audienceID]; ok {
3✔
229
                return audience, nil
1✔
230
        }
1✔
231

232
        return entities.Audience{}, fmt.Errorf(`audience with ID "%s" not found`, audienceID)
1✔
233
}
234

235
// GetAudienceMap returns the audience map
236
func (c DatafileProjectConfig) GetAudienceMap() map[string]entities.Audience {
1✔
237
        return c.audienceMap
1✔
238
}
1✔
239

240
// GetExperimentByKey returns the experiment with the given key
241
func (c DatafileProjectConfig) GetExperimentByKey(experimentKey string) (entities.Experiment, error) {
6✔
242
        if experimentID, ok := c.experimentKeyToIDMap[experimentKey]; ok {
11✔
243
                if experiment, ok := c.experimentMap[experimentID]; ok {
9✔
244
                        return experiment, nil
4✔
245
                }
4✔
246
        }
247

248
        return entities.Experiment{}, fmt.Errorf(`experiment with key "%s" not found`, experimentKey)
2✔
249
}
250

251
// GetExperimentByID returns the experiment with the given ID
252
func (c DatafileProjectConfig) GetExperimentByID(experimentID string) (entities.Experiment, error) {
3✔
253
        if experiment, ok := c.experimentMap[experimentID]; ok {
5✔
254
                return experiment, nil
2✔
255
        }
2✔
256

257
        return entities.Experiment{}, fmt.Errorf(`experiment with ID "%s" not found`, experimentID)
1✔
258
}
259

260
// GetGroupByID returns the group with the given ID
261
func (c DatafileProjectConfig) GetGroupByID(groupID string) (entities.Group, error) {
2✔
262
        if group, ok := c.groupMap[groupID]; ok {
3✔
263
                return group, nil
1✔
264
        }
1✔
265

266
        return entities.Group{}, fmt.Errorf(`group with ID "%s" not found`, groupID)
1✔
267
}
268

269
// SendFlagDecisions determines whether impressions events are sent for ALL decision types
270
func (c DatafileProjectConfig) SendFlagDecisions() bool {
1✔
271
        return c.sendFlagDecisions
1✔
272
}
1✔
273

274
// GetFlagVariationsMap returns map containing all variations for each flag
275
func (c DatafileProjectConfig) GetFlagVariationsMap() map[string][]entities.Variation {
1✔
276
        return c.flagVariationsMap
1✔
277
}
1✔
278

279
// GetRegion returns the region for the datafile
NEW
280
func (c DatafileProjectConfig) GetRegion() string {
×
NEW
281
        return c.region
×
NEW
282
}
×
283

284
// NewDatafileProjectConfig initializes a new datafile from a json byte array using the default JSON datafile parser
285
func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogProducer) (*DatafileProjectConfig, error) {
15✔
286
        datafile, err := Parse(jsonDatafile)
15✔
287
        if err != nil {
16✔
288
                logger.Error("Error parsing datafile", err)
1✔
289
                return nil, err
1✔
290
        }
1✔
291

292
        if _, ok := datafileVersions[datafile.Version]; !ok {
15✔
293
                err = errors.New("unsupported datafile version")
1✔
294
                logger.Error(fmt.Sprintf("Version %s of datafile not supported", datafile.Version), err)
1✔
295
                return nil, err
1✔
296
        }
1✔
297

298
        var hostForODP, publicKeyForODP string
13✔
299
        for _, integration := range datafile.Integrations {
29✔
300
                if integration.Key == nil {
17✔
301
                        err = errors.New("unsupported key in integrations")
1✔
302
                        logger.Error("Error parsing datafile", err)
1✔
303
                        return nil, err
1✔
304
                }
1✔
305
                if *integration.Key == "odp" {
24✔
306
                        hostForODP = integration.Host
9✔
307
                        publicKeyForODP = integration.PublicKey
9✔
308
                        break
9✔
309
                }
310
        }
311

312
        attributeMap, attributeKeyToIDMap := mappers.MapAttributes(datafile.Attributes)
12✔
313
        allExperiments := mappers.MergeExperiments(datafile.Experiments, datafile.Groups)
12✔
314
        groupMap, experimentGroupMap := mappers.MapGroups(datafile.Groups)
12✔
315
        experimentIDMap, experimentKeyMap := mappers.MapExperiments(allExperiments, experimentGroupMap)
12✔
316

12✔
317
        rollouts, rolloutMap := mappers.MapRollouts(datafile.Rollouts)
12✔
318
        integrations := []entities.Integration{}
12✔
319
        for _, integration := range datafile.Integrations {
28✔
320
                integrations = append(integrations, entities.Integration{Key: *integration.Key, Host: integration.Host, PublicKey: integration.PublicKey})
16✔
321
        }
16✔
322
        eventMap := mappers.MapEvents(datafile.Events)
12✔
323
        featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentIDMap)
12✔
324
        audienceMap, audienceSegmentList := mappers.MapAudiences(append(datafile.TypedAudiences, datafile.Audiences...))
12✔
325
        flagVariationsMap := mappers.MapFlagVariations(featureMap)
12✔
326

12✔
327
        attributeKeyMap := make(map[string]entities.Attribute)
12✔
328
        attributeIDToKeyMap := make(map[string]string)
12✔
329

12✔
330
        for id, attribute := range attributeMap {
19✔
331
                attributeIDToKeyMap[id] = attribute.Key
7✔
332
                attributeKeyMap[attribute.Key] = attribute
7✔
333
        }
7✔
334

335
        region := datafile.Region
12✔
336
        if region == "" {
24✔
337
                region = "US"
12✔
338
        }
12✔
339

340
        config := &DatafileProjectConfig{
12✔
341
                hostForODP:           hostForODP,
12✔
342
                publicKeyForODP:      publicKeyForODP,
12✔
343
                datafile:             string(jsonDatafile),
12✔
344
                accountID:            datafile.AccountID,
12✔
345
                anonymizeIP:          datafile.AnonymizeIP,
12✔
346
                attributeKeyToIDMap:  attributeKeyToIDMap,
12✔
347
                audienceMap:          audienceMap,
12✔
348
                attributeMap:         attributeMap,
12✔
349
                botFiltering:         datafile.BotFiltering,
12✔
350
                sdkKey:               datafile.SDKKey,
12✔
351
                environmentKey:       datafile.EnvironmentKey,
12✔
352
                experimentKeyToIDMap: experimentKeyMap,
12✔
353
                experimentMap:        experimentIDMap,
12✔
354
                groupMap:             groupMap,
12✔
355
                eventMap:             eventMap,
12✔
356
                featureMap:           featureMap,
12✔
357
                projectID:            datafile.ProjectID,
12✔
358
                revision:             datafile.Revision,
12✔
359
                rollouts:             rollouts,
12✔
360
                integrations:         integrations,
12✔
361
                segments:             audienceSegmentList,
12✔
362
                rolloutMap:           rolloutMap,
12✔
363
                sendFlagDecisions:    datafile.SendFlagDecisions,
12✔
364
                flagVariationsMap:    flagVariationsMap,
12✔
365
                attributeKeyMap:      attributeKeyMap,
12✔
366
                attributeIDToKeyMap:  attributeIDToKeyMap,
12✔
367
                region:               region,
12✔
368
        }
12✔
369

12✔
370
        logger.Info("Datafile is valid.")
12✔
371
        return config, nil
12✔
372
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc