1
+ /****************************************************************************
2
+ * Copyright 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
+ * http://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 mappers ...
18
+ package mappers
19
+
20
+ import (
21
+ datafileEntities "github.com/optimizely/go-sdk/v2/pkg/config/datafileprojectconfig/entities"
22
+ "github.com/optimizely/go-sdk/v2/pkg/entities"
23
+ )
24
+
25
+ // HoldoutMaps contains the different holdout mappings for efficient lookup
26
+ type HoldoutMaps struct {
27
+ HoldoutIDMap map [string ]entities.Holdout // Map holdout ID to holdout
28
+ GlobalHoldouts []entities.Holdout // Holdouts with no specific flag inclusion
29
+ IncludedHoldouts map [string ][]entities.Holdout // Map flag ID to holdouts that include it
30
+ ExcludedHoldouts map [string ][]entities.Holdout // Map flag ID to holdouts that exclude it
31
+ FlagHoldoutsMap map [string ][]string // Cached map of flag ID to holdout IDs
32
+ }
33
+
34
+ // MapHoldouts maps the raw datafile holdout entities to SDK Holdout entities
35
+ // and creates the necessary mappings for efficient holdout lookup
36
+ func MapHoldouts (holdouts []datafileEntities.Holdout , audienceMap map [string ]entities.Audience ) HoldoutMaps {
37
+ holdoutMaps := HoldoutMaps {
38
+ HoldoutIDMap : make (map [string ]entities.Holdout ),
39
+ GlobalHoldouts : []entities.Holdout {},
40
+ IncludedHoldouts : make (map [string ][]entities.Holdout ),
41
+ ExcludedHoldouts : make (map [string ][]entities.Holdout ),
42
+ FlagHoldoutsMap : make (map [string ][]string ),
43
+ }
44
+
45
+ for _ , datafileHoldout := range holdouts {
46
+ // Create minimal runtime holdout entity - only what's needed for flag dependency
47
+ holdout := entities.Holdout {
48
+ ID : datafileHoldout .ID ,
49
+ Key : datafileHoldout .Key ,
50
+ Status : entities .HoldoutStatus (datafileHoldout .Status ),
51
+ IncludedFlags : datafileHoldout .IncludedFlags ,
52
+ ExcludedFlags : datafileHoldout .ExcludedFlags ,
53
+ }
54
+
55
+ // Add to ID map
56
+ holdoutMaps .HoldoutIDMap [holdout .ID ] = holdout
57
+
58
+ // Categorize holdouts based on flag targeting
59
+ if len (datafileHoldout .IncludedFlags ) == 0 {
60
+ // This is a global holdout (applies to all flags unless excluded)
61
+ holdoutMaps .GlobalHoldouts = append (holdoutMaps .GlobalHoldouts , holdout )
62
+
63
+ // Add to excluded flags map
64
+ for _ , flagID := range datafileHoldout .ExcludedFlags {
65
+ holdoutMaps .ExcludedHoldouts [flagID ] = append (holdoutMaps .ExcludedHoldouts [flagID ], holdout )
66
+ }
67
+ } else {
68
+ // This holdout specifically includes certain flags
69
+ for _ , flagID := range datafileHoldout .IncludedFlags {
70
+ holdoutMaps .IncludedHoldouts [flagID ] = append (holdoutMaps .IncludedHoldouts [flagID ], holdout )
71
+ }
72
+ }
73
+ }
74
+
75
+ return holdoutMaps
76
+ }
77
+
78
+ // GetHoldoutsForFlag returns the holdout IDs that apply to a specific flag
79
+ // This follows the logic from JavaScript SDK: global holdouts (minus excluded) + specifically included
80
+ func GetHoldoutsForFlag (flagID string , holdoutMaps HoldoutMaps ) []string {
81
+ // Check cache first
82
+ if cachedHoldoutIDs , exists := holdoutMaps .FlagHoldoutsMap [flagID ]; exists {
83
+ return cachedHoldoutIDs
84
+ }
85
+
86
+ holdoutIDs := []string {}
87
+
88
+ // Add global holdouts that don't exclude this flag
89
+ for _ , holdout := range holdoutMaps .GlobalHoldouts {
90
+ isExcluded := false
91
+ for _ , excludedFlagID := range holdout .ExcludedFlags {
92
+ if excludedFlagID == flagID {
93
+ isExcluded = true
94
+ break
95
+ }
96
+ }
97
+ if ! isExcluded {
98
+ holdoutIDs = append (holdoutIDs , holdout .ID )
99
+ }
100
+ }
101
+
102
+ // Add holdouts that specifically include this flag
103
+ if includedHoldouts , exists := holdoutMaps .IncludedHoldouts [flagID ]; exists {
104
+ for _ , holdout := range includedHoldouts {
105
+ holdoutIDs = append (holdoutIDs , holdout .ID )
106
+ }
107
+ }
108
+
109
+ // Cache the result
110
+ holdoutMaps .FlagHoldoutsMap [flagID ] = holdoutIDs
111
+
112
+ return holdoutIDs
113
+ }
0 commit comments