...

Source file src/github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel/preprocess.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-semver"
     8  
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    10  )
    11  
    12  type targetPreprocessedData struct {
    13  	valuesMap map[string]struct{}
    14  }
    15  
    16  type segmentPreprocessedData struct {
    17  	includeMap map[string]struct{}
    18  	excludeMap map[string]struct{}
    19  }
    20  
    21  type clausePreprocessedData struct {
    22  	values    []clausePreprocessedValue
    23  	valuesMap map[jsonPrimitiveValueKey]struct{}
    24  }
    25  
    26  type clausePreprocessedValue struct {
    27  	computed     bool
    28  	valid        bool
    29  	parsedRegexp *regexp.Regexp // used for OperatorMatches
    30  	parsedTime   time.Time      // used for OperatorAfter, OperatorBefore
    31  	parsedSemver semver.Version // used for OperatorSemVerEqual, etc.
    32  }
    33  
    34  type jsonPrimitiveValueKey struct {
    35  	valueType    ldvalue.ValueType
    36  	booleanValue bool
    37  	numberValue  float64
    38  	stringValue  string
    39  }
    40  
    41  func (j jsonPrimitiveValueKey) isValid() bool {
    42  	return j.valueType != ldvalue.NullType
    43  }
    44  
    45  // PreprocessFlag precomputes internal data structures based on the flag configuration, to speed up
    46  // evaluations.
    47  //
    48  // This is called once after a flag is deserialized from JSON, or is created with ldbuilders. If you
    49  // construct a flag by some other means, you should call PreprocessFlag exactly once before making it
    50  // available to any other code. The method is not safe for concurrent access across goroutines.
    51  func PreprocessFlag(f *FeatureFlag) {
    52  	for i, t := range f.Targets {
    53  		f.Targets[i].preprocessed.valuesMap = preprocessStringSet(t.Values)
    54  	}
    55  	for i, r := range f.Rules {
    56  		for j, c := range r.Clauses {
    57  			f.Rules[i].Clauses[j].preprocessed = preprocessClause(c)
    58  		}
    59  	}
    60  }
    61  
    62  // PreprocessSegment precomputes internal data structures based on the segment configuration, to speed up
    63  // evaluations.
    64  //
    65  // This is called once after a segment is deserialized from JSON, or is created with ldbuilders. If you
    66  // construct a segment by some other means, you should call PreprocessSegment exactly once before making
    67  // it available to any other code. The method is not safe for concurrent access across goroutines.
    68  func PreprocessSegment(s *Segment) {
    69  	p := segmentPreprocessedData{}
    70  	p.includeMap = preprocessStringSet(s.Included)
    71  	p.excludeMap = preprocessStringSet(s.Excluded)
    72  	for i, t := range s.IncludedContexts {
    73  		s.IncludedContexts[i].preprocessed.valuesMap = preprocessStringSet(t.Values)
    74  	}
    75  	for i, t := range s.ExcludedContexts {
    76  		s.ExcludedContexts[i].preprocessed.valuesMap = preprocessStringSet(t.Values)
    77  	}
    78  	s.preprocessed = p
    79  
    80  	for i, r := range s.Rules {
    81  		for j, c := range r.Clauses {
    82  			s.Rules[i].Clauses[j].preprocessed = preprocessClause(c)
    83  		}
    84  	}
    85  }
    86  
    87  func preprocessClause(c Clause) clausePreprocessedData {
    88  	ret := clausePreprocessedData{}
    89  	switch c.Op {
    90  	case OperatorIn:
    91  		// This is a special case where the clause is testing for an exact match against any of the
    92  		// clause values. As long as the values are primitives, we can use them in a map key (map
    93  		// keys just can't contain slices or maps), and we can convert this test from a linear search
    94  		// to a map lookup.
    95  		if len(c.Values) > 1 { // don't bother if it's empty or has a single value
    96  			valid := true
    97  			m := make(map[jsonPrimitiveValueKey]struct{}, len(c.Values))
    98  			for _, v := range c.Values {
    99  				if key := asPrimitiveValueKey(v); key.isValid() {
   100  					m[key] = struct{}{}
   101  				} else {
   102  					valid = false
   103  					break
   104  				}
   105  			}
   106  			if valid {
   107  				ret.valuesMap = m
   108  			}
   109  		}
   110  	case OperatorMatches:
   111  		ret.values = preprocessValues(c.Values, func(v ldvalue.Value) clausePreprocessedValue {
   112  			r := parseRegexp(v)
   113  			return clausePreprocessedValue{valid: r != nil, parsedRegexp: r}
   114  		})
   115  	case OperatorBefore, OperatorAfter:
   116  		ret.values = preprocessValues(c.Values, func(v ldvalue.Value) clausePreprocessedValue {
   117  			t, ok := parseDateTime(v)
   118  			return clausePreprocessedValue{valid: ok, parsedTime: t}
   119  		})
   120  	case OperatorSemVerEqual, OperatorSemVerGreaterThan, OperatorSemVerLessThan:
   121  		ret.values = preprocessValues(c.Values, func(v ldvalue.Value) clausePreprocessedValue {
   122  			s, ok := parseSemVer(v)
   123  			return clausePreprocessedValue{valid: ok, parsedSemver: s}
   124  		})
   125  	default:
   126  	}
   127  	return ret
   128  }
   129  
   130  func asPrimitiveValueKey(v ldvalue.Value) jsonPrimitiveValueKey {
   131  	switch v.Type() {
   132  	case ldvalue.BoolType:
   133  		return jsonPrimitiveValueKey{valueType: ldvalue.BoolType, booleanValue: v.BoolValue()}
   134  	case ldvalue.NumberType:
   135  		return jsonPrimitiveValueKey{valueType: ldvalue.NumberType, numberValue: v.Float64Value()}
   136  	case ldvalue.StringType:
   137  		return jsonPrimitiveValueKey{valueType: ldvalue.StringType, stringValue: v.StringValue()}
   138  	default:
   139  		return jsonPrimitiveValueKey{}
   140  	}
   141  }
   142  
   143  func preprocessStringSet(valuesIn []string) map[string]struct{} {
   144  	if len(valuesIn) == 0 {
   145  		return nil
   146  	}
   147  	ret := make(map[string]struct{}, len(valuesIn))
   148  	for _, value := range valuesIn {
   149  		ret[value] = struct{}{}
   150  	}
   151  	return ret
   152  }
   153  
   154  func preprocessValues(
   155  	valuesIn []ldvalue.Value,
   156  	fn func(ldvalue.Value) clausePreprocessedValue,
   157  ) []clausePreprocessedValue {
   158  	ret := make([]clausePreprocessedValue, len(valuesIn))
   159  	for i, v := range valuesIn {
   160  		p := fn(v)
   161  		p.computed = true
   162  		ret[i] = p
   163  	}
   164  	return ret
   165  }
   166  

View as plain text