...

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

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

     1  package evaluation
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldattr"
     8  	"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    10  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
    11  )
    12  
    13  func (es *evaluationScope) clauseMatchesContext(clause *ldmodel.Clause, stack evaluationStack) (bool, error) {
    14  	// Note that clause is passed by reference only for efficiency; we do not modify it
    15  	// In the case of a segment match operator, we check if the user is in any of the segments,
    16  	// and possibly negate
    17  	if clause.Op == ldmodel.OperatorSegmentMatch {
    18  		for _, value := range clause.Values {
    19  			if value.Type() == ldvalue.StringType {
    20  				if segment := es.owner.dataProvider.GetSegment(value.StringValue()); segment != nil {
    21  					match, err := es.segmentContainsContext(segment, stack)
    22  					if err != nil {
    23  						return false, err
    24  					}
    25  					if match {
    26  						return !clause.Negate, nil // match - true unless negated
    27  					}
    28  				}
    29  			}
    30  		}
    31  		return clause.Negate, nil // non-match - false unless negated
    32  	}
    33  
    34  	return clauseMatchesContextNoSegments(clause, &es.context)
    35  }
    36  
    37  func clauseMatchesContextNoSegments(c *ldmodel.Clause, context *ldcontext.Context) (bool, error) {
    38  	if !c.Attribute.IsDefined() {
    39  		return false, emptyAttrRefError{}
    40  	}
    41  	if c.Attribute.Err() != nil {
    42  		return false, badAttrRefError(c.Attribute.String())
    43  	}
    44  	if c.Attribute.String() == ldattr.KindAttr {
    45  		return maybeNegate(c.Negate, clauseMatchByKind(c, context)), nil
    46  	}
    47  	actualContext := context.IndividualContextByKind(c.ContextKind)
    48  	if !actualContext.IsDefined() {
    49  		return false, nil
    50  	}
    51  	uValue := actualContext.GetValueForRef(c.Attribute)
    52  	if uValue.IsNull() {
    53  		// if the user attribute is null/missing, it's an automatic non-match - regardless of c.Negate
    54  		return false, nil
    55  	}
    56  
    57  	// If the user value is an array, see if the intersection is non-empty. If so, this clause matches
    58  	if uValue.Type() == ldvalue.ArrayType {
    59  		for i := 0; i < uValue.Count(); i++ {
    60  			if matchAny(c, uValue.GetByIndex(i)) {
    61  				return maybeNegate(c.Negate, true), nil
    62  			}
    63  		}
    64  		return maybeNegate(c.Negate, false), nil
    65  	}
    66  
    67  	return maybeNegate(c.Negate, matchAny(c, uValue)), nil
    68  }
    69  
    70  func maybeNegate(negate, result bool) bool {
    71  	if negate {
    72  		return !result
    73  	}
    74  	return result
    75  }
    76  
    77  func matchAny(
    78  	c *ldmodel.Clause,
    79  	value ldvalue.Value,
    80  ) bool {
    81  	if c.Op == ldmodel.OperatorIn {
    82  		return ldmodel.EvaluatorAccessors.ClauseFindValue(c, value)
    83  	}
    84  	for i, v := range c.Values {
    85  		if doOp(c, value, v, i) {
    86  			return true
    87  		}
    88  	}
    89  	return false
    90  }
    91  
    92  func doOp(c *ldmodel.Clause, ctxValue, clValue ldvalue.Value, index int) bool {
    93  	switch c.Op {
    94  	case ldmodel.OperatorEndsWith:
    95  		return stringOperator(ctxValue, clValue, strings.HasSuffix)
    96  	case ldmodel.OperatorStartsWith:
    97  		return stringOperator(ctxValue, clValue, strings.HasPrefix)
    98  	case ldmodel.OperatorMatches:
    99  		return operatorMatchesFn(c, ctxValue, index)
   100  	case ldmodel.OperatorContains:
   101  		return stringOperator(ctxValue, clValue, strings.Contains)
   102  	case ldmodel.OperatorLessThan:
   103  		return numericOperator(ctxValue, clValue, func(a float64, b float64) bool { return a < b })
   104  	case ldmodel.OperatorLessThanOrEqual:
   105  		return numericOperator(ctxValue, clValue, func(a float64, b float64) bool { return a <= b })
   106  	case ldmodel.OperatorGreaterThan:
   107  		return numericOperator(ctxValue, clValue, func(a float64, b float64) bool { return a > b })
   108  	case ldmodel.OperatorGreaterThanOrEqual:
   109  		return numericOperator(ctxValue, clValue, func(a float64, b float64) bool { return a >= b })
   110  	case ldmodel.OperatorBefore:
   111  		return dateOperator(c, ctxValue, index, time.Time.Before)
   112  	case ldmodel.OperatorAfter:
   113  		return dateOperator(c, ctxValue, index, time.Time.After)
   114  	case ldmodel.OperatorSemVerEqual:
   115  		return semVerOperator(c, ctxValue, index, 0)
   116  	case ldmodel.OperatorSemVerLessThan:
   117  		return semVerOperator(c, ctxValue, index, -1)
   118  	case ldmodel.OperatorSemVerGreaterThan:
   119  		return semVerOperator(c, ctxValue, index, 1)
   120  	}
   121  	return false
   122  }
   123  
   124  func clauseMatchByKind(c *ldmodel.Clause, context *ldcontext.Context) bool {
   125  	// If Attribute is "kind", then we treat Operator and Values as a match expression against a list
   126  	// of all individual kinds in the context. That is, for a multi-kind context with kinds of "org"
   127  	// and "user", it is a match if either of those strings is a match with Operator and Values.
   128  	if context.Multiple() {
   129  		for i := 0; i < context.IndividualContextCount(); i++ {
   130  			if individualContext := context.IndividualContextByIndex(i); individualContext.IsDefined() {
   131  				ctxValue := ldvalue.String(string(individualContext.Kind()))
   132  				if matchAny(c, ctxValue) {
   133  					return true
   134  				}
   135  			}
   136  		}
   137  		return false
   138  	}
   139  	ctxValue := ldvalue.String(string(context.Kind()))
   140  	return matchAny(c, ctxValue)
   141  }
   142  
   143  func stringOperator(
   144  	ctxValue, clValue ldvalue.Value,
   145  	stringTestFn func(string, string) bool,
   146  ) bool {
   147  	if ctxValue.IsString() && clValue.IsString() {
   148  		return stringTestFn(ctxValue.StringValue(), clValue.StringValue())
   149  	}
   150  	return false
   151  }
   152  
   153  func operatorMatchesFn(c *ldmodel.Clause, ctxValue ldvalue.Value, clValueIndex int) bool {
   154  	if ctxValue.IsString() {
   155  		r := ldmodel.EvaluatorAccessors.ClauseGetValueAsRegexp(c, clValueIndex)
   156  		if r != nil {
   157  			return r.MatchString(ctxValue.StringValue())
   158  		}
   159  	}
   160  	return false
   161  }
   162  
   163  func numericOperator(ctxValue, clValue ldvalue.Value, fn func(float64, float64) bool) bool {
   164  	if ctxValue.IsNumber() && clValue.IsNumber() {
   165  		return fn(ctxValue.Float64Value(), clValue.Float64Value())
   166  	}
   167  	return false
   168  }
   169  
   170  func dateOperator(
   171  	c *ldmodel.Clause,
   172  	ctxValue ldvalue.Value,
   173  	clValueIndex int,
   174  	fn func(time.Time, time.Time) bool,
   175  ) bool {
   176  	if clValueTime, ok := ldmodel.EvaluatorAccessors.ClauseGetValueAsTimestamp(c, clValueIndex); ok {
   177  		if ctxValueTime, ok := ldmodel.TypeConversions.ValueToTimestamp(ctxValue); ok {
   178  			return fn(ctxValueTime, clValueTime)
   179  		}
   180  	}
   181  	return false
   182  }
   183  
   184  func semVerOperator(
   185  	c *ldmodel.Clause,
   186  	ctxValue ldvalue.Value,
   187  	clValueIndex int,
   188  	expectedComparisonResult int,
   189  ) bool {
   190  	if clValueVer, ok := ldmodel.EvaluatorAccessors.ClauseGetValueAsSemanticVersion(c, clValueIndex); ok {
   191  		if ctxValueVer, ok := ldmodel.TypeConversions.ValueToSemanticVersion(ctxValue); ok {
   192  			return ctxValueVer.ComparePrecedence(clValueVer) == expectedComparisonResult
   193  		}
   194  	}
   195  	return false
   196  }
   197  

View as plain text