-
Notifications
You must be signed in to change notification settings - Fork 3
/
eval_accessors.go
194 lines (181 loc) · 7.61 KB
/
eval_accessors.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
package ldmodel
import (
"regexp"
"time"
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
"github.com/launchdarkly/go-semver"
)
// EvaluatorAccessorMethods contains functions that are used by the evaluation engine in the
// parent package to perform certain lookup operations on data model structs.
//
// These are defined in the ldmodel package because they take advantage of the preprocessing
// behavior that is defined for these types, which populates additional data structures to
// speed up such lookups. Those data structures are implementation details of this package,
// so they are not exported. Instead, these methods provide a more abstract way for the
// evaluation engine to perform simple lookups regardless of whether the preprocessed data
// is available. (Normally preprocessed data is always available, because the preprocessing
// step is done every time we unmarshal data from JSON; but the evaluator must be able to
// work even if it receives inputs that were constructed in some other way.)
//
// For efficiency, all of these methods expect structs to be passed by address rather than
// by value. They are guaranteed not to modify any fields.
//
// Defining these as methods of EvaluatorAccessorMethods (accessed via the global variable
// EvaluatorAccessors), rather than simple functions or methods of other types, keeps this
// functionality clearly grouped together and allows data model types like FlagRule to be
// simple structs without methods.
type EvaluatorAccessorMethods struct{}
// EvaluatorAccessors is the global entry point for EvaluatorAccessorMethods.
var EvaluatorAccessors EvaluatorAccessorMethods //nolint:gochecknoglobals
// ClauseFindValue returns true if the specified value is deeply equal to any of the Clause's
// Values, or false otherwise. It also returns false if the value is a JSON array, a JSON
// object, or a JSON null (since equality tests are not valid for these in the LaunchDarkly
// model), or if the clause parameter is nil.
//
// If preprocessing has been done, this is a fast map lookup (as long as the Clause's operator
// is "in", which is the only case where it makes sense to create a map). Otherwise it iterates
// the list.
func (e EvaluatorAccessorMethods) ClauseFindValue(clause *Clause, contextValue ldvalue.Value) bool {
if clause == nil {
return false
}
if clause.preprocessed.valuesMap != nil {
if key := asPrimitiveValueKey(contextValue); key.isValid() {
_, found := clause.preprocessed.valuesMap[key]
return found
}
}
switch contextValue.Type() {
case ldvalue.BoolType, ldvalue.NumberType, ldvalue.StringType:
for _, clauseValue := range clause.Values {
if contextValue.Equal(clauseValue) {
return true
}
}
default:
break
}
return false
}
// ClauseGetValueAsRegexp returns one of the Clause's values as a Regexp, if the value is a string
// that represents a valid regular expression.
//
// It returns nil if the value is not a string or is not valid as a regular expression; if the
// index is out of range; or if the clause parameter is nil.
//
// If preprocessing has been done, this is a fast slice lookup. Otherwise it calls regexp.Compile.
func (e EvaluatorAccessorMethods) ClauseGetValueAsRegexp(clause *Clause, index int) *regexp.Regexp {
if clause == nil {
return nil
}
if clause.preprocessed.values != nil {
if index < 0 || index >= len(clause.preprocessed.values) {
return nil
}
return clause.preprocessed.values[index].parsedRegexp
}
if index >= 0 && index < len(clause.Values) {
return parseRegexp(clause.Values[index])
}
return nil
}
// ClauseGetValueAsSemanticVersion returns one of the Clause's values as a semver.Version, if the
// value is a string in the correct format. Any other type is invalid.
//
// The second return value is true for success or false for failure. It also returns failure if the
// index is out of range, or if the clause parameter is nil.
//
// If preprocessing has been done, this is a fast slice lookup. Otherwise it calls
// TypeConversions.ValueToSemanticVersion.
func (e EvaluatorAccessorMethods) ClauseGetValueAsSemanticVersion(clause *Clause, index int) (semver.Version, bool) {
if clause == nil {
return semver.Version{}, false
}
if clause.preprocessed.values != nil {
if index < 0 || index >= len(clause.preprocessed.values) {
return semver.Version{}, false
}
p := clause.preprocessed.values[index]
return p.parsedSemver, p.valid
}
if index >= 0 && index < len(clause.Values) {
return TypeConversions.ValueToSemanticVersion(clause.Values[index])
}
return semver.Version{}, false
}
// ClauseGetValueAsTimestamp returns one of the Clause's values as a time.Time, if the value is a
// string or number in the correct format. Any other type is invalid.
//
// The second return value is true for success or false for failure.. It also returns failure if the
// index is out of range, or if the clause parameter is nil.
//
// If preprocessing has been done, this is a fast slice lookup. Otherwise it calls
// TypeConversions.ValueToTimestamp.
func (e EvaluatorAccessorMethods) ClauseGetValueAsTimestamp(clause *Clause, index int) (time.Time, bool) {
if clause == nil {
return time.Time{}, false
}
if clause.preprocessed.values != nil {
if index < 0 || index >= len(clause.preprocessed.values) {
return time.Time{}, false
}
t := clause.preprocessed.values[index].parsedTime
return t, !t.IsZero()
}
if index >= 0 && index < len(clause.Values) {
return TypeConversions.ValueToTimestamp(clause.Values[index])
}
return time.Time{}, false
}
// SegmentFindKeyInExcluded returns true if the specified key is in this Segment's
// Excluded list, or false otherwise. It also returns false if the segment parameter is nil.
//
// If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list.
func (e EvaluatorAccessorMethods) SegmentFindKeyInExcluded(segment *Segment, key string) bool {
if segment == nil {
return false
}
return findValueInMapOrStrings(key, segment.Excluded, segment.preprocessed.excludeMap)
}
// SegmentFindKeyInIncluded returns true if the specified key is in this Segment's
// Included list, or false otherwise. It also returns false if the segment parameter is nil.
//
// If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list.
func (e EvaluatorAccessorMethods) SegmentFindKeyInIncluded(segment *Segment, key string) bool {
if segment == nil {
return false
}
return findValueInMapOrStrings(key, segment.Included, segment.preprocessed.includeMap)
}
// SegmentTargetFindKey returns true if the specified key is in this SegmentTarget's
// Values list, or false otherwise. It also returns false if the target parameter is nil.
//
// If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list.
func (e EvaluatorAccessorMethods) SegmentTargetFindKey(target *SegmentTarget, key string) bool {
if target == nil {
return false
}
return findValueInMapOrStrings(key, target.Values, target.preprocessed.valuesMap)
}
// TargetFindKey returns true if the specified key is in this Target's Values list, or false
// otherwise. It also returns false if the target parameter is nil.
//
// If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list.
func (e EvaluatorAccessorMethods) TargetFindKey(target *Target, key string) bool {
if target == nil {
return false
}
return findValueInMapOrStrings(key, target.Values, target.preprocessed.valuesMap)
}
func findValueInMapOrStrings(value string, values []string, valuesMap map[string]struct{}) bool {
if valuesMap != nil {
_, found := valuesMap[value]
return found
}
for _, v := range values {
if value == v {
return true
}
}
return false
}