1 package ldmodel 2 3 import ( 4 "regexp" 5 "time" 6 7 "github.com/launchdarkly/go-sdk-common/v3/ldvalue" 8 "github.com/launchdarkly/go-semver" 9 ) 10 11 // EvaluatorAccessorMethods contains functions that are used by the evaluation engine in the 12 // parent package to perform certain lookup operations on data model structs. 13 // 14 // These are defined in the ldmodel package because they take advantage of the preprocessing 15 // behavior that is defined for these types, which populates additional data structures to 16 // speed up such lookups. Those data structures are implementation details of this package, 17 // so they are not exported. Instead, these methods provide a more abstract way for the 18 // evaluation engine to perform simple lookups regardless of whether the preprocessed data 19 // is available. (Normally preprocessed data is always available, because the preprocessing 20 // step is done every time we unmarshal data from JSON; but the evaluator must be able to 21 // work even if it receives inputs that were constructed in some other way.) 22 // 23 // For efficiency, all of these methods expect structs to be passed by address rather than 24 // by value. They are guaranteed not to modify any fields. 25 // 26 // Defining these as methods of EvaluatorAccessorMethods (accessed via the global variable 27 // EvaluatorAccessors), rather than simple functions or methods of other types, keeps this 28 // functionality clearly grouped together and allows data model types like FlagRule to be 29 // simple structs without methods. 30 type EvaluatorAccessorMethods struct{} 31 32 // EvaluatorAccessors is the global entry point for EvaluatorAccessorMethods. 33 var EvaluatorAccessors EvaluatorAccessorMethods //nolint:gochecknoglobals 34 35 // ClauseFindValue returns true if the specified value is deeply equal to any of the Clause's 36 // Values, or false otherwise. It also returns false if the value is a JSON array, a JSON 37 // object, or a JSON null (since equality tests are not valid for these in the LaunchDarkly 38 // model), or if the clause parameter is nil. 39 // 40 // If preprocessing has been done, this is a fast map lookup (as long as the Clause's operator 41 // is "in", which is the only case where it makes sense to create a map). Otherwise it iterates 42 // the list. 43 func (e EvaluatorAccessorMethods) ClauseFindValue(clause *Clause, contextValue ldvalue.Value) bool { 44 if clause == nil { 45 return false 46 } 47 if clause.preprocessed.valuesMap != nil { 48 if key := asPrimitiveValueKey(contextValue); key.isValid() { 49 _, found := clause.preprocessed.valuesMap[key] 50 return found 51 } 52 } 53 switch contextValue.Type() { 54 case ldvalue.BoolType, ldvalue.NumberType, ldvalue.StringType: 55 for _, clauseValue := range clause.Values { 56 if contextValue.Equal(clauseValue) { 57 return true 58 } 59 } 60 default: 61 break 62 } 63 return false 64 } 65 66 // ClauseGetValueAsRegexp returns one of the Clause's values as a Regexp, if the value is a string 67 // that represents a valid regular expression. 68 // 69 // It returns nil if the value is not a string or is not valid as a regular expression; if the 70 // index is out of range; or if the clause parameter is nil. 71 // 72 // If preprocessing has been done, this is a fast slice lookup. Otherwise it calls regexp.Compile. 73 func (e EvaluatorAccessorMethods) ClauseGetValueAsRegexp(clause *Clause, index int) *regexp.Regexp { 74 if clause == nil { 75 return nil 76 } 77 if clause.preprocessed.values != nil { 78 if index < 0 || index >= len(clause.preprocessed.values) { 79 return nil 80 } 81 return clause.preprocessed.values[index].parsedRegexp 82 } 83 if index >= 0 && index < len(clause.Values) { 84 return parseRegexp(clause.Values[index]) 85 } 86 return nil 87 } 88 89 // ClauseGetValueAsSemanticVersion returns one of the Clause's values as a semver.Version, if the 90 // value is a string in the correct format. Any other type is invalid. 91 // 92 // The second return value is true for success or false for failure. It also returns failure if the 93 // index is out of range, or if the clause parameter is nil. 94 // 95 // If preprocessing has been done, this is a fast slice lookup. Otherwise it calls 96 // TypeConversions.ValueToSemanticVersion. 97 func (e EvaluatorAccessorMethods) ClauseGetValueAsSemanticVersion(clause *Clause, index int) (semver.Version, bool) { 98 if clause == nil { 99 return semver.Version{}, false 100 } 101 if clause.preprocessed.values != nil { 102 if index < 0 || index >= len(clause.preprocessed.values) { 103 return semver.Version{}, false 104 } 105 p := clause.preprocessed.values[index] 106 return p.parsedSemver, p.valid 107 } 108 if index >= 0 && index < len(clause.Values) { 109 return TypeConversions.ValueToSemanticVersion(clause.Values[index]) 110 } 111 return semver.Version{}, false 112 } 113 114 // ClauseGetValueAsTimestamp returns one of the Clause's values as a time.Time, if the value is a 115 // string or number in the correct format. Any other type is invalid. 116 // 117 // The second return value is true for success or false for failure.. It also returns failure if the 118 // index is out of range, or if the clause parameter is nil. 119 // 120 // If preprocessing has been done, this is a fast slice lookup. Otherwise it calls 121 // TypeConversions.ValueToTimestamp. 122 func (e EvaluatorAccessorMethods) ClauseGetValueAsTimestamp(clause *Clause, index int) (time.Time, bool) { 123 if clause == nil { 124 return time.Time{}, false 125 } 126 if clause.preprocessed.values != nil { 127 if index < 0 || index >= len(clause.preprocessed.values) { 128 return time.Time{}, false 129 } 130 t := clause.preprocessed.values[index].parsedTime 131 return t, !t.IsZero() 132 } 133 if index >= 0 && index < len(clause.Values) { 134 return TypeConversions.ValueToTimestamp(clause.Values[index]) 135 } 136 return time.Time{}, false 137 } 138 139 // SegmentFindKeyInExcluded returns true if the specified key is in this Segment's 140 // Excluded list, or false otherwise. It also returns false if the segment parameter is nil. 141 // 142 // If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list. 143 func (e EvaluatorAccessorMethods) SegmentFindKeyInExcluded(segment *Segment, key string) bool { 144 if segment == nil { 145 return false 146 } 147 return findValueInMapOrStrings(key, segment.Excluded, segment.preprocessed.excludeMap) 148 } 149 150 // SegmentFindKeyInIncluded returns true if the specified key is in this Segment's 151 // Included list, or false otherwise. It also returns false if the segment parameter is nil. 152 // 153 // If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list. 154 func (e EvaluatorAccessorMethods) SegmentFindKeyInIncluded(segment *Segment, key string) bool { 155 if segment == nil { 156 return false 157 } 158 return findValueInMapOrStrings(key, segment.Included, segment.preprocessed.includeMap) 159 } 160 161 // SegmentTargetFindKey returns true if the specified key is in this SegmentTarget's 162 // Values list, or false otherwise. It also returns false if the target parameter is nil. 163 // 164 // If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list. 165 func (e EvaluatorAccessorMethods) SegmentTargetFindKey(target *SegmentTarget, key string) bool { 166 if target == nil { 167 return false 168 } 169 return findValueInMapOrStrings(key, target.Values, target.preprocessed.valuesMap) 170 } 171 172 // TargetFindKey returns true if the specified key is in this Target's Values list, or false 173 // otherwise. It also returns false if the target parameter is nil. 174 // 175 // If preprocessing has been done, this is a fast map lookup. Otherwise it iterates the list. 176 func (e EvaluatorAccessorMethods) TargetFindKey(target *Target, key string) bool { 177 if target == nil { 178 return false 179 } 180 return findValueInMapOrStrings(key, target.Values, target.preprocessed.valuesMap) 181 } 182 183 func findValueInMapOrStrings(value string, values []string, valuesMap map[string]struct{}) bool { 184 if valuesMap != nil { 185 _, found := valuesMap[value] 186 return found 187 } 188 for _, v := range values { 189 if value == v { 190 return true 191 } 192 } 193 return false 194 } 195