Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/config/datafileprojectconfig/mappers/condition_trees.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -127,6 +127,7 @@ func createLeafCondition(typedV map[string]interface{}, node *entities.TreeNode)
if err := json.Unmarshal(jsonBody, &condition); err != nil {
return err
}
condition.StringRepresentation = string(jsonBody)
node.Item = condition
return nil
}
Expand Down
27 changes: 15 additions & 12 deletions pkg/config/datafileprojectconfig/mappers/condition_trees_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -111,9 +111,10 @@ func TestBuildConditionTreeUsingDatafileAudienceConditions(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand Down Expand Up @@ -145,10 +146,11 @@ func TestBuildConditionTreeSimpleAudienceCondition(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"match":"exact","name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand All @@ -172,10 +174,11 @@ func TestBuildConditionTreeWithLeafNode(t *testing.T) {
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
Name: "s_foo",
Match: "exact",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"match":"exact","name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
Expand Down
45 changes: 0 additions & 45 deletions pkg/decision/evaluator/audience.go

This file was deleted.

35 changes: 29 additions & 6 deletions pkg/decision/evaluator/condition.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -22,6 +22,7 @@ import (

"github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers"
"github.com/optimizely/go-sdk/pkg/entities"
"github.com/optimizely/go-sdk/pkg/logging"
)

const (
Expand All @@ -38,14 +39,23 @@ type ItemEvaluator interface {
}

// CustomAttributeConditionEvaluator evaluates conditions with custom attributes
type CustomAttributeConditionEvaluator struct{}
type CustomAttributeConditionEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewCustomAttributeConditionEvaluator creates a custom attribute condition
func NewCustomAttributeConditionEvaluator(logger logging.OptimizelyLogProducer) *CustomAttributeConditionEvaluator {
return &CustomAttributeConditionEvaluator{logger: logger}
}

// Evaluate returns true if the given user's attributes match the condition
func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition, condTreeParams *entities.TreeParameters) (bool, error) {
// We should only be evaluating custom attributes

if condition.Type != customAttributeType {
return false, fmt.Errorf(`unable to evaluator condition of type "%s"`, condition.Type)

c.logger.Warning(fmt.Sprintf(string(logging.UnknownConditionType), condition.StringRepresentation))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why string is needed? string(logging.UnknownConditionType)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since UnknownConditionType is of custom type, we must convert it to string before formatting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need two Strings. Just pass the same in fmt.Errorf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will confirm and let you know.

return false, fmt.Errorf(`unable to evaluate condition of type "%s"`, condition.Type)
}

var matcher matchers.Matcher
Expand All @@ -57,6 +67,7 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
case exactMatchType:
matcher = matchers.ExactMatcher{
Condition: condition,
Logger: c.logger,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why logger not passed in existsMatchType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No logs were required inside exists.

}
case existsMatchType:
matcher = matchers.ExistsMatcher{
Expand All @@ -65,16 +76,20 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
case ltMatchType:
matcher = matchers.LtMatcher{
Condition: condition,
Logger: c.logger,
}
case gtMatchType:
matcher = matchers.GtMatcher{
Condition: condition,
Logger: c.logger,
}
case substringMatchType:
matcher = matchers.SubstringMatcher{
Condition: condition,
Logger: c.logger,
}
default:
c.logger.Warning(fmt.Sprintf(string(logging.UnknownMatchType), condition.StringRepresentation))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use format specifier.

return false, fmt.Errorf(`invalid Condition matcher "%s"`, condition.Match)
}

Expand All @@ -84,20 +99,28 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
}

// AudienceConditionEvaluator evaluates conditions with audience condition
type AudienceConditionEvaluator struct{}
type AudienceConditionEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewAudienceConditionEvaluator creates a audience condition evaluator
func NewAudienceConditionEvaluator(logger logging.OptimizelyLogProducer) *AudienceConditionEvaluator {
return &AudienceConditionEvaluator{logger: logger}
}

// Evaluate returns true if the given user's attributes match the condition
func (c AudienceConditionEvaluator) Evaluate(audienceID string, condTreeParams *entities.TreeParameters) (bool, error) {

if audience, ok := condTreeParams.AudienceMap[audienceID]; ok {
c.logger.Debug(fmt.Sprintf(string(logging.AudienceEvaluationStarted), audienceID))
condTree := audience.ConditionTree
conditionTreeEvaluator := NewMixedTreeEvaluator()
conditionTreeEvaluator := NewMixedTreeEvaluator(c.logger)
retValue, isValid := conditionTreeEvaluator.Evaluate(condTree, condTreeParams)
if !isValid {
return false, fmt.Errorf(`an error occurred while evaluating nested tree for audience ID "%s"`, audienceID)
}
c.logger.Debug(fmt.Sprintf(string(logging.AudienceEvaluatedTo), audienceID, retValue))
return retValue, nil

}

return false, fmt.Errorf(`unable to evaluate nested tree for audience ID "%s"`, audienceID)
Expand Down
86 changes: 72 additions & 14 deletions pkg/decision/evaluator/condition_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -17,14 +17,26 @@
package evaluator

import (
"fmt"
"testing"

"github.com/optimizely/go-sdk/pkg/entities"
"github.com/stretchr/testify/assert"
"github.com/optimizely/go-sdk/pkg/logging"
"github.com/stretchr/testify/suite"
)

func TestCustomAttributeConditionEvaluator(t *testing.T) {
conditionEvaluator := CustomAttributeConditionEvaluator{}
type ConditionTestSuite struct {
suite.Suite
mockLogger *MockLogger
conditionEvaluator *CustomAttributeConditionEvaluator
}

func (s *ConditionTestSuite) SetupTest() {
s.mockLogger = new(MockLogger)
s.conditionEvaluator = NewCustomAttributeConditionEvaluator(s.mockLogger)
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluator() {
condition := entities.Condition{
Match: "exact",
Value: "foo",
Expand All @@ -40,21 +52,20 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) {
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, true)
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, true)

// Test condition fails
user = entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "not_foo",
},
}
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, false)
result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
}

func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) {
conditionEvaluator := CustomAttributeConditionEvaluator{}
func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithoutMatchType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Expand All @@ -69,15 +80,62 @@ func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) {
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, true)
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, true)

// Test condition fails
user = entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "not_foo",
},
}
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
assert.Equal(t, result, false)
result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithInvalidMatchType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Type: "custom_attribute",
Match: "invalid",
}

// Test condition fails
user := entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "foo",
},
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
s.mockLogger.On("Warning", fmt.Sprintf(string(logging.UnknownMatchType), ""))
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
s.mockLogger.AssertExpectations(s.T())
}

func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithUnknownType() {
condition := entities.Condition{
Value: "foo",
Name: "string_foo",
Type: "",
}

// Test condition fails
user := entities.UserContext{
Attributes: map[string]interface{}{
"string_foo": "foo",
},
}

condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{})
s.mockLogger.On("Warning", fmt.Sprintf(string(logging.UnknownConditionType), ""))
result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams)
s.Equal(result, false)
s.mockLogger.AssertExpectations(s.T())
}

func TestConditionTestSuite(t *testing.T) {
suite.Run(t, new(ConditionTestSuite))
}
12 changes: 7 additions & 5 deletions pkg/decision/evaluator/condition_tree.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -21,6 +21,7 @@ import (
"fmt"

"github.com/optimizely/go-sdk/pkg/entities"
"github.com/optimizely/go-sdk/pkg/logging"
)

const customAttributeType = "custom_attribute"
Expand All @@ -41,11 +42,12 @@ type TreeEvaluator interface {

// MixedTreeEvaluator evaluates a tree of mixed node types (condition node or audience nodes)
type MixedTreeEvaluator struct {
logger logging.OptimizelyLogProducer
}

// NewMixedTreeEvaluator creates a condition tree evaluator with the out-of-the-box condition evaluators
func NewMixedTreeEvaluator() *MixedTreeEvaluator {
return &MixedTreeEvaluator{}
func NewMixedTreeEvaluator(logger logging.OptimizelyLogProducer) *MixedTreeEvaluator {
return &MixedTreeEvaluator{logger: logger}
}

// Evaluate returns whether the userAttributes satisfy the given condition tree and the evaluation of the condition is valid or not (to handle null bubbling)
Expand All @@ -66,10 +68,10 @@ func (c MixedTreeEvaluator) Evaluate(node *entities.TreeNode, condTreeParams *en
var err error
switch v := node.Item.(type) {
case entities.Condition:
evaluator := CustomAttributeConditionEvaluator{}
evaluator := NewCustomAttributeConditionEvaluator(c.logger)
result, err = evaluator.Evaluate(node.Item.(entities.Condition), condTreeParams)
case string:
evaluator := AudienceConditionEvaluator{}
evaluator := NewAudienceConditionEvaluator(c.logger)
result, err = evaluator.Evaluate(node.Item.(string), condTreeParams)
default:
fmt.Printf("I don't know about type %T!\n", v)
Expand Down
Loading