/
match_clause.go
244 lines (215 loc) · 8.01 KB
/
match_clause.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
package ldmodel
import (
"regexp"
"strings"
"time"
"gopkg.in/launchdarkly/go-sdk-common.v2/lduser"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
)
// ClauseMatchesUser return true if the user matches the conditions in this clause.
//
// This method cannot be used if the clause's Operation is OperatorSegmentMatch, since that involves
// pulling data from outside of the clause. In that case it will simply return false.
//
// This part of the flag evaluation logic is defined in ldmodel and exported, rather than being
// internal to Evaluator, as a compromise to allow for optimizations that require storing precomputed
// data in the model object. Exporting this function is preferable to exporting those internal
// implementation details.
//
// The clause and user are passed by reference for efficiency only; the function will not modify
// them. Passing a nil value will cause a panic.
func ClauseMatchesUser(c *Clause, user *lduser.User) bool {
uValue := user.GetAttribute(c.Attribute)
if uValue.IsNull() {
// if the user attribute is null/missing, it's an automatic non-match - regardless of c.Negate
return false
}
matchFn := operatorFn(c.Op)
// If the user value is an array, see if the intersection is non-empty. If so, this clause matches
if uValue.Type() == ldvalue.ArrayType {
for i := 0; i < uValue.Count(); i++ {
if matchAny(c.Op, matchFn, uValue.GetByIndex(i), c.Values, c.preprocessed) {
return maybeNegate(c.Negate, true)
}
}
return maybeNegate(c.Negate, false)
}
return maybeNegate(c.Negate, matchAny(c.Op, matchFn, uValue, c.Values, c.preprocessed))
}
func maybeNegate(negate, result bool) bool {
if negate {
return !result
}
return result
}
func matchAny(
op Operator,
fn opFn,
value ldvalue.Value,
values []ldvalue.Value,
preprocessed clausePreprocessedData,
) bool {
if op == OperatorIn && preprocessed.valuesMap != nil {
if key := asPrimitiveValueKey(value); key.isValid() { // see preprocessClausee
return preprocessed.valuesMap[key]
}
}
preValues := preprocessed.values
for i, v := range values {
var p clausePreprocessedValue
if preValues != nil {
p = preValues[i] // this slice is always the same length as values
}
if fn(value, v, p) {
return true
}
}
return false
}
type opFn (func(userValue ldvalue.Value, clauseValue ldvalue.Value, preprocessed clausePreprocessedValue) bool)
var allOps = map[Operator]opFn{ //nolint:gochecknoglobals
OperatorIn: operatorInFn,
OperatorEndsWith: operatorEndsWithFn,
OperatorStartsWith: operatorStartsWithFn,
OperatorMatches: operatorMatchesFn,
OperatorContains: operatorContainsFn,
OperatorLessThan: operatorLessThanFn,
OperatorLessThanOrEqual: operatorLessThanOrEqualFn,
OperatorGreaterThan: operatorGreaterThanFn,
OperatorGreaterThanOrEqual: operatorGreaterThanOrEqualFn,
OperatorBefore: operatorBeforeFn,
OperatorAfter: operatorAfterFn,
OperatorSemVerEqual: operatorSemVerEqualFn,
OperatorSemVerLessThan: operatorSemVerLessThanFn,
OperatorSemVerGreaterThan: operatorSemVerGreaterThanFn,
}
func operatorFn(operator Operator) opFn {
if op, ok := allOps[operator]; ok {
return op
}
return operatorNoneFn
}
func operatorInFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return uValue.Equal(cValue)
}
func stringOperator(uValue ldvalue.Value, cValue ldvalue.Value, fn func(string, string) bool) bool {
if uValue.Type() == ldvalue.StringType && cValue.Type() == ldvalue.StringType {
return fn(uValue.StringValue(), cValue.StringValue())
}
return false
}
func operatorStartsWithFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return stringOperator(uValue, cValue, strings.HasPrefix)
}
func operatorEndsWithFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return stringOperator(uValue, cValue, strings.HasSuffix)
}
func operatorMatchesFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
if preprocessed.computed {
// we have already tried to compile the clause value as a regex
if uValue.Type() != ldvalue.StringType || !preprocessed.valid {
return false
}
return preprocessed.parsedRegexp.MatchString(uValue.StringValue())
}
// the clause did not get preprocessed, so we'll evaluate from scratch
return stringOperator(uValue, cValue, func(u string, c string) bool {
if matched, err := regexp.MatchString(c, u); err == nil {
return matched
}
return false
})
}
func operatorContainsFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return stringOperator(uValue, cValue, strings.Contains)
}
func numericOperator(uValue ldvalue.Value, cValue ldvalue.Value, fn func(float64, float64) bool) bool {
if uValue.IsNumber() && cValue.IsNumber() {
return fn(uValue.Float64Value(), cValue.Float64Value())
}
return false
}
func operatorLessThanFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return numericOperator(uValue, cValue, func(u float64, c float64) bool { return u < c })
}
func operatorLessThanOrEqualFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return numericOperator(uValue, cValue, func(u float64, c float64) bool { return u <= c })
}
func operatorGreaterThanFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return numericOperator(uValue, cValue, func(u float64, c float64) bool { return u > c })
}
func operatorGreaterThanOrEqualFn(
uValue ldvalue.Value,
cValue ldvalue.Value,
preprocessed clausePreprocessedValue,
) bool {
return numericOperator(uValue, cValue, func(u float64, c float64) bool { return u >= c })
}
func dateOperator(
uValue ldvalue.Value,
cValue ldvalue.Value,
preprocessed clausePreprocessedValue,
fn func(time.Time, time.Time) bool,
) bool {
if preprocessed.computed {
// we have already tried to compile the clause value as a date/time
if preprocessed.valid {
if uTime, ok := parseDateTime(uValue); ok {
return fn(uTime, preprocessed.parsedTime)
}
}
return false
}
// the clause did not get preprocessed, so we'll evaluate from scratch
if uTime, ok := parseDateTime(uValue); ok {
if cTime, ok := parseDateTime(cValue); ok {
return fn(uTime, cTime)
}
}
return false
}
func operatorBeforeFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return dateOperator(uValue, cValue, preprocessed, time.Time.Before)
}
func operatorAfterFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return dateOperator(uValue, cValue, preprocessed, time.Time.After)
}
func semVerOperator(
uValue ldvalue.Value,
cValue ldvalue.Value,
preprocessed clausePreprocessedValue,
expectedComparisonResult int,
) bool {
if preprocessed.computed {
// we have already tried to parse the clause value as a version
if preprocessed.valid {
if uVer, ok := parseSemVer(uValue); ok {
return uVer.ComparePrecedence(preprocessed.parsedSemver) == expectedComparisonResult
}
}
return false
}
// the clause did not get preprocessed, so we'll evaluate from scratch
if u, ok := parseSemVer(uValue); ok {
if c, ok := parseSemVer(cValue); ok {
return u.ComparePrecedence(c) == expectedComparisonResult
}
}
return false
}
func operatorSemVerEqualFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return semVerOperator(uValue, cValue, preprocessed, 0)
}
func operatorSemVerLessThanFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return semVerOperator(uValue, cValue, preprocessed, -1)
}
func operatorSemVerGreaterThanFn(
uValue ldvalue.Value,
cValue ldvalue.Value,
preprocessed clausePreprocessedValue,
) bool {
return semVerOperator(uValue, cValue, preprocessed, 1)
}
func operatorNoneFn(uValue ldvalue.Value, cValue ldvalue.Value, preprocessed clausePreprocessedValue) bool {
return false
}