forked from DevCycleHQ/go-server-sdk
/
model_filters.go
228 lines (187 loc) · 6.08 KB
/
model_filters.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
package bucketing
import (
"encoding/json"
"fmt"
"github.com/BIwashi/go-server-sdk/v2/api"
"github.com/BIwashi/go-server-sdk/v2/util"
)
// A FilterOrOperator represents either an individual filter or a nested sequence of filters with a logical operator.
type FilterOrOperator interface {
Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool
}
// For compiling values inside a filter after parsing, or other optimizations
type InitializedFilter interface {
Initialize() error
}
// Represents a partially parsed filter object from the JSON, before parsing a specific filter type
type filter struct {
Type string `json:"type" validate:"regexp=^(all|user|optIn)$"`
SubType string `json:"subType" validate:"regexp=^(|user_id|email|ip|country|platform|platformVersion|appVersion|deviceModel|customData)$"`
Comparator string `json:"comparator" validate:"regexp=^(=|!=|>|>=|<|<=|exist|!exist|contain|!contain)$"`
Operator string `json:"operator" validate:"regexp=^(and|or)$"`
}
func (f filter) GetType() string {
return f.Type
}
func (f filter) GetSubType() string {
return f.SubType
}
func (f filter) GetComparator() string {
return f.Comparator
}
// MixedFilters is used to parse a list of generic filters from JSON that can be one of
// several types, and transform them into specific implementations (e.g. UserFilter).
type MixedFilters []FilterOrOperator
func (m *MixedFilters) UnmarshalJSON(data []byte) error {
// Parse into a list of RawMessages
var rawItems []json.RawMessage
err := json.Unmarshal(data, &rawItems)
if err != nil {
return err
}
filters := make([]FilterOrOperator, len(rawItems))
for index, rawItem := range rawItems {
// Parse each filter again to get the type
var partial filter
err = json.Unmarshal(rawItem, &partial)
if err != nil {
return err
}
var filter FilterOrOperator
if partial.Operator != "" {
var operator *AudienceOperator
err = json.Unmarshal(rawItem, &operator)
if err != nil {
return fmt.Errorf("Error unmarshalling filter: %w", err)
}
filters[index] = operator
continue
}
switch partial.Type {
case TypeAll:
filter = &AllFilter{}
case TypeOptIn:
filter = &OptInFilter{}
case TypeUser:
switch partial.SubType {
case SubTypeCustomData:
filter = &CustomDataFilter{}
default:
filter = &UserFilter{}
}
case TypeAudienceMatch:
filter = &AudienceMatchFilter{}
default:
util.Warnf(`Warning: Invalid filter type %s. To leverage this new filter definition, please update to the latest version of the DevCycle SDK.`, partial.Type)
continue
}
err = json.Unmarshal(rawItem, &filter)
if err != nil {
return fmt.Errorf("Error unmarshalling filter: %w", err)
}
if filter, ok := filter.(InitializedFilter); ok {
if err := filter.Initialize(); err != nil {
return fmt.Errorf("Error initializing filter: %w", err)
}
}
filters[index] = filter
}
*m = filters
return nil
}
type PassFilter struct{}
func (filter PassFilter) Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool {
return true
}
type NoPassFilter struct{}
func (filter NoPassFilter) Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool {
return false
}
type AllFilter struct {
PassFilter
}
// OptInFilter never passes, because it's only supported by Cloud Bucketing.
type OptInFilter struct {
NoPassFilter
}
type UserFilter struct {
filter
Values []interface{} `json:"values"`
CompiledStringVals []string
CompiledBoolVals []bool
CompiledNumVals []float64
}
func (filter *UserFilter) Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool {
return filterFunctionsBySubtype(filter, user, clientCustomData)
}
func (f UserFilter) Type() string {
return TypeUser
}
func (f *UserFilter) Initialize() error {
return f.compileValues()
}
func (u *UserFilter) compileValues() error {
if len(u.Values) == 0 {
return nil
}
firstValue := u.Values[0]
switch firstValue.(type) {
case bool:
var boolValues []bool
for _, value := range u.Values {
if val, ok := value.(bool); ok {
boolValues = append(boolValues, val)
} else {
return fmt.Errorf("Filter values must be all of the same type. Expected: bool, got: %T %#v\n", value, value)
}
}
u.CompiledBoolVals = boolValues
case string:
var stringValues []string
for _, value := range u.Values {
if val, ok := value.(string); ok {
stringValues = append(stringValues, val)
} else {
return fmt.Errorf("Filter values must be all of the same type. Expected: string, got: %T %#v\n", value, value)
}
}
u.CompiledStringVals = stringValues
case float64:
var numValues []float64
for _, value := range u.Values {
if val, ok := value.(float64); ok {
numValues = append(numValues, val)
} else {
return fmt.Errorf("Filter values must be all of the same type. Expected: number, got: %T %#v\n", value, value)
}
}
u.CompiledNumVals = numValues
default:
return fmt.Errorf("Filter values must be of type bool, string, or float64. Got: %T %#v\n", firstValue, firstValue)
}
return nil
}
type CustomDataFilter struct {
*UserFilter
DataKey string `json:"dataKey"`
DataKeyType string `json:"dataKeyType" validate:"regexp=^(String|Boolean|Number)$"`
}
func (filter *CustomDataFilter) Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool {
return checkCustomData(filter, user.CombinedCustomData(), clientCustomData)
}
func (f CustomDataFilter) Type() string {
return TypeUser
}
func (f CustomDataFilter) SubType() string {
return SubTypeCustomData
}
type AudienceMatchFilter struct {
filter
Audiences []string `json:"_audiences"`
}
func (filter *AudienceMatchFilter) Evaluate(audiences map[string]NoIdAudience, user api.PopulatedUser, clientCustomData map[string]interface{}) bool {
return filterForAudienceMatch(filter, audiences, user, clientCustomData)
}
func (f AudienceMatchFilter) Type() string {
return TypeAudienceMatch
}