...

Source file src/github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel/eval_accessors.go

Documentation: github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel

     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  

View as plain text