From e0ab087a1499b191981e172a92b0a3c2bed290c1 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Thu, 5 Jun 2025 09:44:37 -0500 Subject: [PATCH 1/7] Create a SplitProducer layer which can be used to cache grammer.Split instances --- engine/evaluator/evaluator.go | 39 +++++----- engine/evaluator/evaluator_test.go | 71 +++++++++++-------- .../inlargesegment_test.go | 5 +- .../{ => dependency_test}/insegment_test.go | 6 +- engine/grammar/split.go | 8 +++ go.mod | 2 +- go.sum | 2 + service/api/specs/splitversionfilter_test.go | 2 +- storage/inmemory/mutexmap/splits.go | 44 ++++++++++++ storage/producer/simple_producer.go | 48 +++++++++++++ 10 files changed, 174 insertions(+), 53 deletions(-) rename engine/grammar/matchers/{ => dependency_test}/inlargesegment_test.go (90%) rename engine/grammar/matchers/{ => dependency_test}/insegment_test.go (91%) create mode 100644 storage/producer/simple_producer.go diff --git a/engine/evaluator/evaluator.go b/engine/evaluator/evaluator.go index 9858e081..3a5e0c8d 100644 --- a/engine/evaluator/evaluator.go +++ b/engine/evaluator/evaluator.go @@ -4,7 +4,6 @@ import ( "fmt" "time" - "github.com/splitio/go-split-commons/v6/dtos" "github.com/splitio/go-split-commons/v6/engine" "github.com/splitio/go-split-commons/v6/engine/evaluator/impressionlabels" "github.com/splitio/go-split-commons/v6/engine/grammar" @@ -38,7 +37,7 @@ type Results struct { // Evaluator struct is the main evaluator type Evaluator struct { - splitStorage storage.SplitStorageConsumer + splitProducer grammar.SplitProducer segmentStorage storage.SegmentStorageConsumer eng *engine.Engine logger logging.LoggerInterface @@ -46,32 +45,27 @@ type Evaluator struct { // NewEvaluator instantiates an Evaluator struct and returns a reference to it func NewEvaluator( - splitStorage storage.SplitStorageConsumer, + splitProducer grammar.SplitProducer, segmentStorage storage.SegmentStorageConsumer, eng *engine.Engine, logger logging.LoggerInterface, + ) *Evaluator { return &Evaluator{ - splitStorage: splitStorage, + splitProducer: splitProducer, segmentStorage: segmentStorage, eng: eng, logger: logger, } } -func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFlag string, splitDto *dtos.SplitDTO, attributes map[string]interface{}) *Result { +func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFlag string, split *grammar.Split, attributes map[string]interface{}) *Result { var config *string - if splitDto == nil { + if split == nil { e.logger.Warning(fmt.Sprintf("Feature flag %s not found, returning control.", featureFlag)) return &Result{Treatment: Control, Label: impressionlabels.SplitNotFound, Config: config} } - ctx := injection.NewContext() - ctx.AddDependency("segmentStorage", e.segmentStorage) - ctx.AddDependency("evaluator", e) - - split := grammar.NewSplit(splitDto, ctx, e.logger) - if split.Killed() { e.logger.Warning(fmt.Sprintf( "Feature flag %s has been killed, returning default treatment: %s", @@ -118,15 +112,23 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl } } +func (e *Evaluator) createInjectionContext() *injection.Context { + ctx := injection.NewContext() + ctx.AddDependency("segmentStorage", e.segmentStorage) + ctx.AddDependency("evaluator", e) + return ctx +} + // EvaluateFeature returns a struct with the resulting treatment and extra information for the impression func (e *Evaluator) EvaluateFeature(key string, bucketingKey *string, featureFlag string, attributes map[string]interface{}) *Result { before := time.Now() - splitDto := e.splitStorage.Split(featureFlag) + ctx := e.createInjectionContext() + split := e.splitProducer.GetSplit(featureFlag, ctx, e.logger) if bucketingKey == nil { bucketingKey = &key } - result := e.evaluateTreatment(key, *bucketingKey, featureFlag, splitDto, attributes) + result := e.evaluateTreatment(key, *bucketingKey, featureFlag, split, attributes) after := time.Now() result.EvaluationTime = after.Sub(before) @@ -140,13 +142,14 @@ func (e *Evaluator) EvaluateFeatures(key string, bucketingKey *string, featureFl EvaluationTime: 0, } before := time.Now() - splits := e.splitStorage.FetchMany(featureFlags) + ctx := e.createInjectionContext() + splits := e.splitProducer.GetSplits(featureFlags, ctx, e.logger) if bucketingKey == nil { bucketingKey = &key } - for _, featureFlag := range featureFlags { - results.Evaluations[featureFlag] = *e.evaluateTreatment(key, *bucketingKey, featureFlag, splits[featureFlag], attributes) + for featureFlag, split := range splits { + results.Evaluations[featureFlag] = *e.evaluateTreatment(key, *bucketingKey, featureFlag, split, attributes) } after := time.Now() @@ -166,7 +169,7 @@ func (e *Evaluator) EvaluateFeatureByFlagSets(key string, bucketingKey *string, // GetFeatureFlagNamesByFlagSets return flags that belong to some flag set func (e *Evaluator) getFeatureFlagNamesByFlagSets(flagSets []string) []string { uniqueFlags := make(map[string]struct{}) - flagsBySets := e.splitStorage.GetNamesByFlagSets(flagSets) + flagsBySets := e.splitProducer.GetNamesByFlagSets(flagSets) for set, flags := range flagsBySets { if len(flags) == 0 { e.logger.Warning(fmt.Sprintf("you passed %s Flag Set that does not contain cached feature flag names, please double check what Flag Sets are in use in the Split user interface.", set)) diff --git a/engine/evaluator/evaluator_test.go b/engine/evaluator/evaluator_test.go index cbe1991e..ea783219 100644 --- a/engine/evaluator/evaluator_test.go +++ b/engine/evaluator/evaluator_test.go @@ -1,18 +1,21 @@ package evaluator import ( + "iter" "testing" "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v6/engine/grammar" "github.com/splitio/go-split-commons/v6/flagsets" "github.com/splitio/go-split-commons/v6/storage/inmemory/mutexmap" "github.com/splitio/go-split-commons/v6/storage/mocks" + "github.com/splitio/go-split-commons/v6/storage/producer" - "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) -type mockStorage struct{} +type mockProducer struct{} var mysplittest = &dtos.SplitDTO{ Algo: 2, @@ -190,7 +193,7 @@ var mysplittest4 = &dtos.SplitDTO{ }, } -func (s *mockStorage) Split( +func (s *mockProducer) split( feature string, ) *dtos.SplitDTO { switch feature { @@ -206,33 +209,45 @@ func (s *mockStorage) Split( } return nil } -func (s *mockStorage) FetchMany( - feature []string, -) map[string]*dtos.SplitDTO { - splits := make(map[string]*dtos.SplitDTO) - splits["mysplittest"] = mysplittest - splits["mysplittest2"] = mysplittest2 - splits["mysplittest3"] = mysplittest3 - splits["mysplittest4"] = mysplittest4 - splits["mysplittest5"] = nil - return splits + +func (s *mockProducer) GetSplit( + feature string, + ctx *injection.Context, + logger logging.LoggerInterface, +) *grammar.Split { + dto := s.split(feature) + if dto == nil { + return nil + } + return grammar.NewSplit(dto, ctx, logger) +} + +func (s *mockProducer) GetSplits( + features []string, + ctx *injection.Context, + logger logging.LoggerInterface, +) iter.Seq2[string, *grammar.Split] { + return func(yield func(string, *grammar.Split) bool) { + for _, feature := range features { + split := s.GetSplit(feature, ctx, logger) + if !yield(feature, split) { + return + } + } + } } -func (s *mockStorage) All() []dtos.SplitDTO { return make([]dtos.SplitDTO, 0) } -func (s *mockStorage) SegmentNames() *set.ThreadUnsafeSet { return nil } -func (s *mockStorage) LargeSegmentNames() *set.ThreadUnsafeSet { return nil } -func (s *mockStorage) SplitNames() []string { return make([]string, 0) } -func (s *mockStorage) TrafficTypeExists(trafficType string) bool { return true } -func (s *mockStorage) ChangeNumber() (int64, error) { return 0, nil } -func (s *mockStorage) GetNamesByFlagSets(sets []string) map[string][]string { + +func (s *mockProducer) GetNamesByFlagSets(sets []string) map[string][]string { return make(map[string][]string) } -func (s *mockStorage) GetAllFlagSetNames() []string { return make([]string, 0) } + +var _ grammar.SplitProducer = (*mockProducer)(nil) func TestSplitWithoutConfigurations(t *testing.T) { logger := logging.NewLogger(nil) evaluator := NewEvaluator( - &mockStorage{}, + &mockProducer{}, nil, nil, logger) @@ -253,7 +268,7 @@ func TestSplitWithtConfigurations(t *testing.T) { logger := logging.NewLogger(nil) evaluator := NewEvaluator( - &mockStorage{}, + &mockProducer{}, nil, nil, logger) @@ -274,7 +289,7 @@ func TestSplitWithtConfigurationsButKilled(t *testing.T) { logger := logging.NewLogger(nil) evaluator := NewEvaluator( - &mockStorage{}, + &mockProducer{}, nil, nil, logger) @@ -295,7 +310,7 @@ func TestSplitWithConfigurationsButKilledWithConfigsOnDefault(t *testing.T) { logger := logging.NewLogger(nil) evaluator := NewEvaluator( - &mockStorage{}, + &mockProducer{}, nil, nil, logger) @@ -316,7 +331,7 @@ func TestMultipleEvaluations(t *testing.T) { logger := logging.NewLogger(nil) evaluator := NewEvaluator( - &mockStorage{}, + &mockProducer{}, nil, nil, logger) @@ -416,7 +431,7 @@ func TestEvaluationByFlagSets(t *testing.T) { } evaluator := NewEvaluator( - mockedStorage, + producer.NewSimpleProducer(mockedStorage), nil, nil, logger) @@ -478,7 +493,7 @@ func TestEvaluationByFlagSetsASetEmpty(t *testing.T) { } evaluator := NewEvaluator( - mockedStorage, + producer.NewSimpleProducer(mockedStorage), nil, nil, logger) diff --git a/engine/grammar/matchers/inlargesegment_test.go b/engine/grammar/matchers/dependency_test/inlargesegment_test.go similarity index 90% rename from engine/grammar/matchers/inlargesegment_test.go rename to engine/grammar/matchers/dependency_test/inlargesegment_test.go index c039f291..7994b76f 100644 --- a/engine/grammar/matchers/inlargesegment_test.go +++ b/engine/grammar/matchers/dependency_test/inlargesegment_test.go @@ -1,10 +1,11 @@ -package matchers +package dependencytests import ( "reflect" "testing" "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v6/engine/grammar/matchers" "github.com/splitio/go-split-commons/v6/storage/inmemory/mutexmap" "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" @@ -29,7 +30,7 @@ func TestInLargeSegmentMatcher(t *testing.T) { ctx := injection.NewContext() ctx.AddDependency("largeSegmentStorage", segmentStorage) - matcher, err := BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, ctx, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) diff --git a/engine/grammar/matchers/insegment_test.go b/engine/grammar/matchers/dependency_test/insegment_test.go similarity index 91% rename from engine/grammar/matchers/insegment_test.go rename to engine/grammar/matchers/dependency_test/insegment_test.go index f96cdb50..b263b261 100644 --- a/engine/grammar/matchers/insegment_test.go +++ b/engine/grammar/matchers/dependency_test/insegment_test.go @@ -1,12 +1,12 @@ -package matchers +package dependencytests import ( "reflect" "testing" "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v6/engine/grammar/matchers" "github.com/splitio/go-split-commons/v6/storage/inmemory/mutexmap" - "github.com/splitio/go-toolkit/v5/datastructures/set" "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" @@ -30,7 +30,7 @@ func TestInSegmentMatcher(t *testing.T) { ctx := injection.NewContext() ctx.AddDependency("segmentStorage", segmentStorage) - matcher, err := BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, ctx, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) diff --git a/engine/grammar/split.go b/engine/grammar/split.go index 69d084cd..faab945f 100644 --- a/engine/grammar/split.go +++ b/engine/grammar/split.go @@ -1,6 +1,8 @@ package grammar import ( + "iter" + "github.com/splitio/go-split-commons/v6/dtos" "github.com/splitio/go-split-commons/v6/engine/evaluator/impressionlabels" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers" @@ -15,6 +17,12 @@ type Split struct { conditions []*Condition } +type SplitProducer interface { + GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *Split + GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *Split] + GetNamesByFlagSets(sets []string) map[string][]string +} + var conditionReplacementUnsupportedMatcher []*Condition = []*Condition{{ conditionType: ConditionTypeWhitelist, label: impressionlabels.UnsupportedMatcherType, diff --git a/go.mod b/go.mod index bb816c4a..be599ed9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/splitio/go-split-commons/v6 -go 1.18 +go 1.23 require ( github.com/bits-and-blooms/bloom/v3 v3.3.1 diff --git a/go.sum b/go.sum index 192010e0..7038e0d0 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,9 @@ github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ= github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90= github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/service/api/specs/splitversionfilter_test.go b/service/api/specs/splitversionfilter_test.go index e9f5cf0a..ab8e51cb 100644 --- a/service/api/specs/splitversionfilter_test.go +++ b/service/api/specs/splitversionfilter_test.go @@ -28,7 +28,7 @@ func TestParseAndValidate(t *testing.T) { } } -func TestsplitVersionFilter(t *testing.T) { +func TestSplitVersionFilter(t *testing.T) { filter := NewSplitVersionFilter() shouldFilter := filter.ShouldFilter(matchers.MatcherTypeBetweenSemver, FLAG_V1_0) if !shouldFilter { diff --git a/storage/inmemory/mutexmap/splits.go b/storage/inmemory/mutexmap/splits.go index 3fa86472..6d20a971 100644 --- a/storage/inmemory/mutexmap/splits.go +++ b/storage/inmemory/mutexmap/splits.go @@ -1,17 +1,22 @@ package mutexmap import ( + "iter" "sync" "github.com/splitio/go-split-commons/v6/dtos" + "github.com/splitio/go-split-commons/v6/engine/grammar" "github.com/splitio/go-split-commons/v6/flagsets" "github.com/splitio/go-split-commons/v6/storage" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" + "github.com/splitio/go-toolkit/v5/logging" ) // MMSplitStorage struct contains is an in-memory implementation of split storage type MMSplitStorage struct { data map[string]dtos.SplitDTO + splitCache map[string]*grammar.Split flagSets flagsets.FeaturesBySet trafficTypes map[string]int64 till int64 @@ -26,6 +31,7 @@ type MMSplitStorage struct { func NewMMSplitStorage(flagSetFilter flagsets.FlagSetFilter) *MMSplitStorage { return &MMSplitStorage{ data: make(map[string]dtos.SplitDTO), + splitCache: make(map[string]*grammar.Split), flagSets: flagsets.NewFeaturesBySet(nil), trafficTypes: make(map[string]int64), till: -1, @@ -96,6 +102,7 @@ func (m *MMSplitStorage) KillLocally(splitName string, defaultTreatment string, split.Killed = true split.ChangeNumber = changeNumber m.data[split.Name] = *split + delete(m.splitCache, split.Name) } } @@ -159,6 +166,7 @@ func (m *MMSplitStorage) Update(toAdd []dtos.SplitDTO, toRemove []dtos.SplitDTO, m.removeFromFlagSets(existing.Name, existing.Sets) } m.data[split.Name] = split + delete(m.splitCache, split.Name) m.increaseTrafficTypeCount(split.TrafficTypeName) m.addToFlagSets(split.Name, split.Sets) } @@ -167,6 +175,7 @@ func (m *MMSplitStorage) Update(toAdd []dtos.SplitDTO, toRemove []dtos.SplitDTO, cached, exists := m.data[split.Name] if exists { delete(m.data, split.Name) + delete(m.splitCache, split.Name) m.decreaseTrafficTypeCount(cached.TrafficTypeName) m.removeFromFlagSets(cached.Name, cached.Sets) } @@ -181,6 +190,7 @@ func (m *MMSplitStorage) Remove(splitName string) { split, exists := m.data[splitName] if exists { delete(m.data, splitName) + delete(m.splitCache, splitName) m.decreaseTrafficTypeCount(split.TrafficTypeName) } } @@ -266,5 +276,39 @@ func (m *MMSplitStorage) GetNamesByFlagSets(sets []string) map[string][]string { return toReturn } +// GetSplit returns a cached grammar.Split if it exists, otherwise creates a new one +func (m *MMSplitStorage) GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *grammar.Split { + m.mutex.RLock() + if cached, ok := m.splitCache[splitName]; ok { + m.mutex.RUnlock() + return cached + } + splitDTO := m._get(splitName) + m.mutex.RUnlock() + + if splitDTO == nil { + return nil + } + + split := grammar.NewSplit(splitDTO, ctx, logger) + + m.mutex.Lock() + defer m.mutex.Unlock() + + m.splitCache[splitName] = split + return split +} + +func (m *MMSplitStorage) GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { + return func(yield func(string, *grammar.Split) bool) { + for _, splitName := range splitNames { + if !yield(splitName, m.GetSplit(splitName, ctx, logger)) { + return + } + } + } +} + var _ storage.SplitStorageConsumer = (*MMSplitStorage)(nil) var _ storage.SplitStorageProducer = (*MMSplitStorage)(nil) +var _ grammar.SplitProducer = (*MMSplitStorage)(nil) diff --git a/storage/producer/simple_producer.go b/storage/producer/simple_producer.go new file mode 100644 index 00000000..a293d395 --- /dev/null +++ b/storage/producer/simple_producer.go @@ -0,0 +1,48 @@ +package producer + +import ( + "iter" + + "github.com/splitio/go-split-commons/v6/engine/grammar" + "github.com/splitio/go-split-commons/v6/storage" + "github.com/splitio/go-toolkit/v5/injection" + "github.com/splitio/go-toolkit/v5/logging" +) + +// Straight forward implementation that does no caching +type SimpleProducer struct { + splitStorage storage.SplitStorageConsumer +} + +func NewSimpleProducer(splitStorage storage.SplitStorageConsumer) *SimpleProducer { + return &SimpleProducer{splitStorage: splitStorage} +} + +func (p *SimpleProducer) GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *grammar.Split { + dto := p.splitStorage.Split(splitName) + if dto == nil { + return nil + } + return grammar.NewSplit(dto, ctx, logger) +} + +func (p *SimpleProducer) GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { + return func(yield func(string, *grammar.Split) bool) { + dtos := p.splitStorage.FetchMany(splitNames) + for splitName, dto := range dtos { + var split *grammar.Split + if dto != nil { + split = grammar.NewSplit(dto, ctx, logger) + } + if !yield(splitName, split) { + return + } + } + } +} + +func (p *SimpleProducer) GetNamesByFlagSets(sets []string) map[string][]string { + return p.splitStorage.GetNamesByFlagSets(sets) +} + +var _ grammar.SplitProducer = (*SimpleProducer)(nil) From 9530a21c8613ccefb23feaecbb4cf6c7780610e9 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:53:42 -0500 Subject: [PATCH 2/7] Move injection.Context from construction of matcher to evaluation of matcher --- engine/engine.go | 4 +- engine/engine_test.go | 16 ++--- engine/evaluator/evaluator.go | 18 ++--- engine/evaluator/evaluator_test.go | 7 +- engine/grammar/condition.go | 12 ++-- engine/grammar/condition_test.go | 8 +-- engine/grammar/matchers/allkeys.go | 6 +- engine/grammar/matchers/allkeys_test.go | 5 +- engine/grammar/matchers/allofset.go | 3 +- engine/grammar/matchers/allofset_test.go | 11 ++- engine/grammar/matchers/anyofset.go | 3 +- engine/grammar/matchers/anyofset_test.go | 14 ++-- engine/grammar/matchers/between.go | 3 +- engine/grammar/matchers/between_test.go | 24 +++---- engine/grammar/matchers/boolean.go | 4 +- engine/grammar/matchers/boolean_test.go | 16 ++--- engine/grammar/matchers/contains.go | 4 +- engine/grammar/matchers/contains_test.go | 10 +-- engine/grammar/matchers/dependency.go | 8 ++- .../dependency_test/dependency_test.go | 16 ++--- .../dependency_test/inlargesegment_test.go | 8 +-- .../dependency_test/insegment_test.go | 8 +-- engine/grammar/matchers/endswith.go | 4 +- engine/grammar/matchers/endswith_test.go | 10 +-- engine/grammar/matchers/equalto.go | 3 +- engine/grammar/matchers/equalto_test.go | 16 ++--- engine/grammar/matchers/equaltoset.go | 3 +- engine/grammar/matchers/equaltoset_test.go | 8 +-- engine/grammar/matchers/gtoet.go | 3 +- engine/grammar/matchers/gtoet_test.go | 16 ++--- engine/grammar/matchers/inlargesegment.go | 5 +- engine/grammar/matchers/insegment.go | 5 +- engine/grammar/matchers/ltoet.go | 3 +- engine/grammar/matchers/ltoet_test.go | 16 ++--- engine/grammar/matchers/matcher_test.go | 25 ++----- engine/grammar/matchers/matchers.go | 9 +-- engine/grammar/matchers/partofset.go | 3 +- engine/grammar/matchers/partofset_test.go | 10 +-- engine/grammar/matchers/regex.go | 4 +- engine/grammar/matchers/regex_test.go | 4 +- engine/grammar/matchers/semver.go | 11 +-- engine/grammar/matchers/semver_test.go | 70 +++++++++---------- engine/grammar/matchers/startswith.go | 4 +- engine/grammar/matchers/startswith_test.go | 10 +-- engine/grammar/matchers/whitelist.go | 3 +- engine/grammar/matchers/whitelist_test.go | 12 ++-- engine/grammar/split.go | 13 ++-- engine/grammar/split_test.go | 6 +- engine/validator/matchers.go | 3 +- storage/inmemory/mutexmap/splits.go | 9 ++- storage/producer/simple_producer.go | 9 ++- 51 files changed, 249 insertions(+), 256 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index acef99ae..bc986b01 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -9,6 +9,7 @@ import ( "github.com/splitio/go-split-commons/v6/engine/hash" "github.com/splitio/go-toolkit/v5/hasher" + "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -24,6 +25,7 @@ func (e *Engine) DoEvaluation( key string, bucketingKey string, attributes map[string]interface{}, + ctx *injection.Context, ) (*string, string) { inRollOut := false for _, condition := range split.Conditions() { @@ -42,7 +44,7 @@ func (e *Engine) DoEvaluation( } } - if condition.Matches(key, &bucketingKey, attributes) { + if condition.Matches(key, &bucketingKey, attributes, ctx) { bucket := e.calculateBucket(split.Algo(), bucketingKey, split.Seed()) treatment := condition.CalculateTreatment(bucket) return treatment, condition.Label() diff --git a/engine/engine_test.go b/engine/engine_test.go index d89e8887..2ba504f6 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -85,11 +85,11 @@ func TestTreatmentOnTrafficAllocation1(t *testing.T) { }, } - split := grammar.NewSplit(&splitDTO, nil, logger) + split := grammar.NewSplit(&splitDTO, logger) eng := Engine{} eng.logger = logger - treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil) + treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) if *treatment == "default" { t.Error("It should not return default treatment.") @@ -134,11 +134,11 @@ func TestTreatmentOnTrafficAllocation99(t *testing.T) { }, } - split := grammar.NewSplit(&splitDTO, nil, logger) + split := grammar.NewSplit(&splitDTO, logger) eng := Engine{} eng.logger = logger - treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil) + treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) if *treatment != "default" { t.Error("It should return default treatment.") @@ -236,13 +236,13 @@ func TestEvaluations(t *testing.T) { t.Error("Data was not added for testing consistency") } - split := grammar.NewSplit(&splitDTO, nil, logger) + split := grammar.NewSplit(&splitDTO, logger) eng := Engine{} eng.logger = logger for _, tr := range treatmentsResults { - treatment, _ := eng.DoEvaluation(split, tr.Key, tr.Key, nil) + treatment, _ := eng.DoEvaluation(split, tr.Key, tr.Key, nil, nil) if *treatment != tr.Result { t.Error("Checking expected treatment " + tr.Result + " for key: " + tr.Key) @@ -266,12 +266,12 @@ func TestNoConditionMatched(t *testing.T) { Conditions: []dtos.ConditionDTO{}, } - split := grammar.NewSplit(&splitDTO, nil, logger) + split := grammar.NewSplit(&splitDTO, logger) eng := Engine{} eng.logger = logger - treatment, err := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil) + treatment, err := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) if treatment != nil { t.Error("It should be nil.") diff --git a/engine/evaluator/evaluator.go b/engine/evaluator/evaluator.go index 3a5e0c8d..13d93d32 100644 --- a/engine/evaluator/evaluator.go +++ b/engine/evaluator/evaluator.go @@ -87,7 +87,10 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl } } - treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes) + ctx := injection.NewContext() + ctx.AddDependency("segmentStorage", e.segmentStorage) + ctx.AddDependency("evaluator", e) + treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes, ctx) if treatment == nil { e.logger.Warning(fmt.Sprintf( "No condition matched, returning default treatment: %s", @@ -112,18 +115,10 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl } } -func (e *Evaluator) createInjectionContext() *injection.Context { - ctx := injection.NewContext() - ctx.AddDependency("segmentStorage", e.segmentStorage) - ctx.AddDependency("evaluator", e) - return ctx -} - // EvaluateFeature returns a struct with the resulting treatment and extra information for the impression func (e *Evaluator) EvaluateFeature(key string, bucketingKey *string, featureFlag string, attributes map[string]interface{}) *Result { before := time.Now() - ctx := e.createInjectionContext() - split := e.splitProducer.GetSplit(featureFlag, ctx, e.logger) + split := e.splitProducer.GetSplit(featureFlag, e.logger) if bucketingKey == nil { bucketingKey = &key @@ -142,8 +137,7 @@ func (e *Evaluator) EvaluateFeatures(key string, bucketingKey *string, featureFl EvaluationTime: 0, } before := time.Now() - ctx := e.createInjectionContext() - splits := e.splitProducer.GetSplits(featureFlags, ctx, e.logger) + splits := e.splitProducer.GetSplits(featureFlags, e.logger) if bucketingKey == nil { bucketingKey = &key diff --git a/engine/evaluator/evaluator_test.go b/engine/evaluator/evaluator_test.go index ea783219..cdcf78e8 100644 --- a/engine/evaluator/evaluator_test.go +++ b/engine/evaluator/evaluator_test.go @@ -11,7 +11,6 @@ import ( "github.com/splitio/go-split-commons/v6/storage/mocks" "github.com/splitio/go-split-commons/v6/storage/producer" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -212,24 +211,22 @@ func (s *mockProducer) split( func (s *mockProducer) GetSplit( feature string, - ctx *injection.Context, logger logging.LoggerInterface, ) *grammar.Split { dto := s.split(feature) if dto == nil { return nil } - return grammar.NewSplit(dto, ctx, logger) + return grammar.NewSplit(dto, logger) } func (s *mockProducer) GetSplits( features []string, - ctx *injection.Context, logger logging.LoggerInterface, ) iter.Seq2[string, *grammar.Split] { return func(yield func(string, *grammar.Split) bool) { for _, feature := range features { - split := s.GetSplit(feature, ctx, logger) + split := s.GetSplit(feature, logger) if !yield(feature, split) { return } diff --git a/engine/grammar/condition.go b/engine/grammar/condition.go index ef51248e..3d119a20 100644 --- a/engine/grammar/condition.go +++ b/engine/grammar/condition.go @@ -18,12 +18,12 @@ type Condition struct { } // NewCondition instantiates a new Condition struct with appropriate wrappers around dtos and returns it. -func NewCondition(cond *dtos.ConditionDTO, ctx *injection.Context, logger logging.LoggerInterface) (*Condition, error) { +func NewCondition(cond *dtos.ConditionDTO, logger logging.LoggerInterface) (*Condition, error) { partitions := make([]Partition, 0) for _, part := range cond.Partitions { partitions = append(partitions, Partition{partitionData: part}) } - matcherObjs, err := processMatchers(cond.MatcherGroup.Matchers, ctx, logger) + matcherObjs, err := processMatchers(cond.MatcherGroup.Matchers, logger) if err != nil { // At this point the only error forwarded is UnsupportedMatcherError return nil, err @@ -38,10 +38,10 @@ func NewCondition(cond *dtos.ConditionDTO, ctx *injection.Context, logger loggin }, nil } -func processMatchers(condMatchers []dtos.MatcherDTO, ctx *injection.Context, logger logging.LoggerInterface) ([]matchers.MatcherInterface, error) { +func processMatchers(condMatchers []dtos.MatcherDTO, logger logging.LoggerInterface) ([]matchers.MatcherInterface, error) { matcherObjs := make([]matchers.MatcherInterface, 0) for _, matcher := range condMatchers { - m, err := matchers.BuildMatcher(&matcher, ctx, logger) + m, err := matchers.BuildMatcher(&matcher, logger) if err == nil { matcherObjs = append(matcherObjs, m) } else { @@ -77,10 +77,10 @@ func (c *Condition) Label() string { } // Matches returns true if the condition matches for a specific key and/or set of attributes -func (c *Condition) Matches(key string, bucketingKey *string, attributes map[string]interface{}) bool { +func (c *Condition) Matches(key string, bucketingKey *string, attributes map[string]interface{}, ctx *injection.Context) bool { partial := make([]bool, len(c.matchers)) for i, matcher := range c.matchers { - partial[i] = matcher.Match(key, attributes, bucketingKey) + partial[i] = matcher.Match(key, attributes, bucketingKey, ctx) if matcher.Negate() { partial[i] = !partial[i] } diff --git a/engine/grammar/condition_test.go b/engine/grammar/condition_test.go index f63ea0fc..aebf916c 100644 --- a/engine/grammar/condition_test.go +++ b/engine/grammar/condition_test.go @@ -48,7 +48,7 @@ func TestConditionWrapperObject(t *testing.T) { }, } - wrapped, err := NewCondition(&condition1, nil, logger) + wrapped, err := NewCondition(&condition1, logger) if err != nil { t.Error("err should be nil") @@ -119,7 +119,7 @@ func TestAnotherWrapper(t *testing.T) { }, } - wrapped, err := NewCondition(&condition1, nil, logger) + wrapped, err := NewCondition(&condition1, logger) if err != nil { t.Error("err should be nil") } @@ -189,7 +189,7 @@ func TestConditionUnsupportedMatcherWrapperObject(t *testing.T) { }, } - _, err := NewCondition(&condition1, nil, logger) + _, err := NewCondition(&condition1, logger) if err == nil { t.Error("err should not be nil") @@ -231,7 +231,7 @@ func TestConditionMatcherWithNilStringWrapperObject(t *testing.T) { }, } - condition, err := NewCondition(&condition1, nil, logger) + condition, err := NewCondition(&condition1, logger) if err != nil { t.Error("err should be nil") diff --git a/engine/grammar/matchers/allkeys.go b/engine/grammar/matchers/allkeys.go index 0ccbd96c..08a8d170 100644 --- a/engine/grammar/matchers/allkeys.go +++ b/engine/grammar/matchers/allkeys.go @@ -1,12 +1,16 @@ package matchers +import ( + "github.com/splitio/go-toolkit/v5/injection" +) + // AllKeysMatcher matches any given key and set of attributes type AllKeysMatcher struct { Matcher } // Match implementation for AllKeysMatcher -func (m AllKeysMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m AllKeysMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { return true } diff --git a/engine/grammar/matchers/allkeys_test.go b/engine/grammar/matchers/allkeys_test.go index d93a14e0..4cc0b4f1 100644 --- a/engine/grammar/matchers/allkeys_test.go +++ b/engine/grammar/matchers/allkeys_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-toolkit/v5/logging" ) @@ -15,7 +14,7 @@ func TestAllKeysMatcher(t *testing.T) { MatcherType: "ALL_KEYS", } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -25,7 +24,7 @@ func TestAllKeysMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.AllKeysMatcher and was %s", matcherType) } - if !matcher.Match("asd", nil, nil) { + if !matcher.Match("asd", nil, nil, nil) { t.Error("Matcher should match ANY string") } } diff --git a/engine/grammar/matchers/allofset.go b/engine/grammar/matchers/allofset.go index c9e1ca06..1b3dd28e 100644 --- a/engine/grammar/matchers/allofset.go +++ b/engine/grammar/matchers/allofset.go @@ -5,6 +5,7 @@ import ( "reflect" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" ) // ContainsAllOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the feature flag @@ -14,7 +15,7 @@ type ContainsAllOfSetMatcher struct { } // Match returns true if the set provided is a superset of the one in the feature flag -func (m *ContainsAllOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *ContainsAllOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("AllOfSetMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/allofset_test.go b/engine/grammar/matchers/allofset_test.go index cf74754c..c11fcfce 100644 --- a/engine/grammar/matchers/allofset_test.go +++ b/engine/grammar/matchers/allofset_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-toolkit/v5/logging" ) @@ -22,7 +21,7 @@ func TestAllOfSetMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,19 +32,19 @@ func TestAllOfSetMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.ContainsAllOfSetMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil, nil) { t.Error("Matcher should match an equal set") } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil, nil) { t.Error("Matcher should match a superset") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil, nil) { t.Error("Matcher should not match an empty set") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil, nil) { t.Error("Matcher should not match a subset") } } diff --git a/engine/grammar/matchers/anyofset.go b/engine/grammar/matchers/anyofset.go index 4e66f42c..778c9b15 100644 --- a/engine/grammar/matchers/anyofset.go +++ b/engine/grammar/matchers/anyofset.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" ) // ContainsAnyOfSetMatcher matches if the set supplied to the getTreatment is a superset of the one in the feature flag @@ -13,7 +14,7 @@ type ContainsAnyOfSetMatcher struct { } // Match returns true if the set provided is a superset of the one in the feature flag -func (m *ContainsAnyOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *ContainsAnyOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("AnyOfSetMatcher %s", err.Error())) diff --git a/engine/grammar/matchers/anyofset_test.go b/engine/grammar/matchers/anyofset_test.go index 1a0f7a65..eed37cb4 100644 --- a/engine/grammar/matchers/anyofset_test.go +++ b/engine/grammar/matchers/anyofset_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-toolkit/v5/logging" ) @@ -22,7 +21,7 @@ func TestAnyOfSetMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,24 +32,23 @@ func TestAnyOfSetMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.ContainsAnyOfSetMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil, nil) { t.Error("Matcher should match an equal set") } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil, nil) { t.Error("Matcher should match a superset") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil, nil) { t.Error("Matcher should NOT match an empty set") } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil, nil) { t.Error("Matcher should match a subset") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{"five", "six"}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{"five", "six"}}, nil, nil) { t.Error("Matcher hsould not match a non-intersecting set") } - } diff --git a/engine/grammar/matchers/between.go b/engine/grammar/matchers/between.go index 729c92f3..fdb38155 100644 --- a/engine/grammar/matchers/between.go +++ b/engine/grammar/matchers/between.go @@ -5,6 +5,7 @@ import ( "reflect" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" + "github.com/splitio/go-toolkit/v5/injection" ) // BetweenMatcher will match if two numbers or two datetimes are equal @@ -16,7 +17,7 @@ type BetweenMatcher struct { } // Match will match if the matchingValue is between lowerComparisonValue and upperComparisonValue -func (m *BetweenMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *BetweenMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingRaw, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("BetweenMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/between_test.go b/engine/grammar/matchers/between_test.go index 6eaf3784..c8764e20 100644 --- a/engine/grammar/matchers/between_test.go +++ b/engine/grammar/matchers/between_test.go @@ -24,7 +24,7 @@ func TestBetweenMatcherInt(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -35,23 +35,23 @@ func TestBetweenMatcherInt(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.BetweenMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil, nil) { t.Error("Lower limit should match") } - if !matcher.Match("asd", map[string]interface{}{"value": 500}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 500}, nil, nil) { t.Error("Upper limit should match") } - if !matcher.Match("asd", map[string]interface{}{"value": 250}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 250}, nil, nil) { t.Error("Matcher should match an equal set") } - if matcher.Match("asd", map[string]interface{}{"value": 99}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 99}, nil, nil) { t.Error("Lower than lower limit should NOT match") } - if matcher.Match("asd", map[string]interface{}{"value": 501}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 501}, nil, nil) { t.Error("Upper than upper limit should NOT match") } } @@ -71,7 +71,7 @@ func TestBetweenMatcherDatetime(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -82,23 +82,23 @@ func TestBetweenMatcherDatetime(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.BetweenMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": 960293532}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 960293532}, nil, nil) { t.Error("Lower limit should match") } - if !matcher.Match("asd", map[string]interface{}{"value": 1275782400}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 1275782400}, nil, nil) { t.Error("Upper limit should match") } - if !matcher.Match("asd", map[string]interface{}{"value": 980293532}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 980293532}, nil, nil) { t.Error("Should match between lower and upper") } - if matcher.Match("asd", map[string]interface{}{"value": 900293532}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 900293532}, nil, nil) { t.Error("Lower than lower limit should NOT match") } - if matcher.Match("asd", map[string]interface{}{"value": 1375782400}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 1375782400}, nil, nil) { t.Error("Upper than upper limit should NOT match") } } diff --git a/engine/grammar/matchers/boolean.go b/engine/grammar/matchers/boolean.go index e5fb17a0..8c26a5af 100644 --- a/engine/grammar/matchers/boolean.go +++ b/engine/grammar/matchers/boolean.go @@ -5,6 +5,8 @@ import ( "reflect" "strconv" "strings" + + "github.com/splitio/go-toolkit/v5/injection" ) // BooleanMatcher returns true if the value supplied can be interpreted as a boolean and is equal to the one stored @@ -14,7 +16,7 @@ type BooleanMatcher struct { } // Match returns true if the value supplied can be interpreted as a boolean and is equal to the one stored -func (m *BooleanMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *BooleanMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("BooleanMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/boolean_test.go b/engine/grammar/matchers/boolean_test.go index 8dc3bb43..3160d8da 100644 --- a/engine/grammar/matchers/boolean_test.go +++ b/engine/grammar/matchers/boolean_test.go @@ -21,7 +21,7 @@ func TestBooleanMatcherTrue(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -32,15 +32,15 @@ func TestBooleanMatcherTrue(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.BooleanMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": true}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": true}, nil, nil) { t.Errorf("true bool should match") } - if !matcher.Match("asd", map[string]interface{}{"value": "true"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "true"}, nil, nil) { t.Errorf("\"true\" tringhould match") } - if !matcher.Match("asd", map[string]interface{}{"value": "tRUe"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "tRUe"}, nil, nil) { t.Errorf("true string with mixed caps should match") } } @@ -57,7 +57,7 @@ func TestBooleanMatcherFalse(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -68,15 +68,15 @@ func TestBooleanMatcherFalse(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.BooleanMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": false}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": false}, nil, nil) { t.Errorf("false bool should match") } - if !matcher.Match("asd", map[string]interface{}{"value": "false"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "false"}, nil, nil) { t.Errorf("\"false\" tringhould match") } - if !matcher.Match("asd", map[string]interface{}{"value": "fALse"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "fALse"}, nil, nil) { t.Errorf("fALse bool should match") } } diff --git a/engine/grammar/matchers/contains.go b/engine/grammar/matchers/contains.go index 03b27547..eebc7520 100644 --- a/engine/grammar/matchers/contains.go +++ b/engine/grammar/matchers/contains.go @@ -3,6 +3,8 @@ package matchers import ( "fmt" "strings" + + "github.com/splitio/go-toolkit/v5/injection" ) // ContainsStringMatcher matches strings contain one of the substrings in the feature flag @@ -12,7 +14,7 @@ type ContainsStringMatcher struct { } // Match returns true if the key contains one of the substrings in the feature flag -func (m *ContainsStringMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *ContainsStringMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("ContainsAllOfSetMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/contains_test.go b/engine/grammar/matchers/contains_test.go index 68dadcca..71e5b783 100644 --- a/engine/grammar/matchers/contains_test.go +++ b/engine/grammar/matchers/contains_test.go @@ -22,7 +22,7 @@ func TestContainsString(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,19 +33,19 @@ func TestContainsString(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.ContainsStringMatcher and was %s", matcherType) } - if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil, nil) { t.Errorf("string without any of the substrings shouldn't match") } - if matcher.Match("asd", map[string]interface{}{"value": ""}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": ""}, nil, nil) { t.Errorf("empty string shouldn't match") } - if !matcher.Match("asd", map[string]interface{}{"value": "ppabc"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "ppabc"}, nil, nil) { t.Errorf("string containing one of the substrings should match") } - if !matcher.Match("asd", map[string]interface{}{"value": "abcdefghimklsad"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "abcdefghimklsad"}, nil, nil) { t.Errorf("string containing all of the substrings should match") } diff --git a/engine/grammar/matchers/dependency.go b/engine/grammar/matchers/dependency.go index 44bce641..aafa0b12 100644 --- a/engine/grammar/matchers/dependency.go +++ b/engine/grammar/matchers/dependency.go @@ -1,5 +1,9 @@ package matchers +import ( + "github.com/splitio/go-toolkit/v5/injection" +) + type dependencyEvaluator interface { EvaluateDependency(key string, bucketingKey *string, feature string, attributes map[string]interface{}) string } @@ -14,8 +18,8 @@ type DependencyMatcher struct { // Match will return true if the evaluation of another split results in one of the treatments defined in the // feature flag -func (m *DependencyMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { - evaluator, ok := m.Context.Dependency("evaluator").(dependencyEvaluator) +func (m *DependencyMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { + evaluator, ok := ctx.Dependency("evaluator").(dependencyEvaluator) if !ok { m.logger.Error("DependencyMatcher: Error retrieving matching key") return false diff --git a/engine/grammar/matchers/dependency_test/dependency_test.go b/engine/grammar/matchers/dependency_test/dependency_test.go index 97e8831c..0e08f121 100644 --- a/engine/grammar/matchers/dependency_test/dependency_test.go +++ b/engine/grammar/matchers/dependency_test/dependency_test.go @@ -128,7 +128,7 @@ func TestDependencyMatcher(t *testing.T) { ), ) - matcher, err := matchers.BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -139,7 +139,7 @@ func TestDependencyMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.DependencyMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": "something"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "something"}, nil, ctx) { t.Errorf("depends on all keys. should match!") } @@ -153,17 +153,17 @@ func TestDependencyMatcher(t *testing.T) { Attribute: &attrName, }, } - matcher, err = matchers.BuildMatcher(dto, ctx, logger) + matcher, err = matchers.BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) } - if !matcher.Match("VAL1", map[string]interface{}{}, nil) { + if !matcher.Match("VAL1", map[string]interface{}{}, nil, ctx) { t.Errorf("depends on whitelist with VAL1. Should match") } - if matcher.Match("VAL2", map[string]interface{}{}, nil) { + if matcher.Match("VAL2", map[string]interface{}{}, nil, ctx) { t.Errorf("depends on whitelist with VAL1. passign VAL2 should fail") } } @@ -239,15 +239,15 @@ func TestDependencyMatcherWithBucketingKey(t *testing.T) { ctx := injection.NewContext() ctx.AddDependency("evaluator", &mockEvaluator{expectedBucketingKey: "bucketingKey_1", t: t}) - matcher, err := matchers.BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) } bucketingKey := "bucketingKey_1" - matcher.Match("asd", map[string]interface{}{"value": "something"}, &bucketingKey) + matcher.Match("asd", map[string]interface{}{"value": "something"}, &bucketingKey, ctx) ctx.AddDependency("evaluator", &mockEvaluator{expectedBucketingKey: "", t: t}) - matcher.Match("asd", map[string]interface{}{"value": "something"}, nil) + matcher.Match("asd", map[string]interface{}{"value": "something"}, nil, ctx) } diff --git a/engine/grammar/matchers/dependency_test/inlargesegment_test.go b/engine/grammar/matchers/dependency_test/inlargesegment_test.go index 7994b76f..451efd28 100644 --- a/engine/grammar/matchers/dependency_test/inlargesegment_test.go +++ b/engine/grammar/matchers/dependency_test/inlargesegment_test.go @@ -30,7 +30,7 @@ func TestInLargeSegmentMatcher(t *testing.T) { ctx := injection.NewContext() ctx.AddDependency("largeSegmentStorage", segmentStorage) - matcher, err := matchers.BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -41,16 +41,16 @@ func TestInLargeSegmentMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.InLargeSegmentMatcher and was %s", matcherType) } - if !matcher.Match("item1", nil, nil) { + if !matcher.Match("item1", nil, nil, ctx) { t.Error("Should match a key present in the large segment") } - if matcher.Match("item7", nil, nil) { + if matcher.Match("item7", nil, nil, ctx) { t.Error("Should not match a key not present in the large segment") } segmentStorage.Update(lsName, []string{}, 123) - if matcher.Match("item1", nil, nil) { + if matcher.Match("item1", nil, nil, ctx) { t.Error("Should return false for a nonexistent large segment") } } diff --git a/engine/grammar/matchers/dependency_test/insegment_test.go b/engine/grammar/matchers/dependency_test/insegment_test.go index b263b261..e589d2e8 100644 --- a/engine/grammar/matchers/dependency_test/insegment_test.go +++ b/engine/grammar/matchers/dependency_test/insegment_test.go @@ -30,7 +30,7 @@ func TestInSegmentMatcher(t *testing.T) { ctx := injection.NewContext() ctx.AddDependency("segmentStorage", segmentStorage) - matcher, err := matchers.BuildMatcher(dto, ctx, logger) + matcher, err := matchers.BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -41,16 +41,16 @@ func TestInSegmentMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.InSegmentMatcher and was %s", matcherType) } - if !matcher.Match("item1", nil, nil) { + if !matcher.Match("item1", nil, nil, ctx) { t.Error("Should match a key present in the segment") } - if matcher.Match("item7", nil, nil) { + if matcher.Match("item7", nil, nil, ctx) { t.Error("Should not match a key not present in the segment") } segmentStorage.Update("segmentito", set.NewSet(), segmentKeys, 123) - if matcher.Match("item1", nil, nil) { + if matcher.Match("item1", nil, nil, ctx) { t.Error("Should return false for a nonexistent segment") } } diff --git a/engine/grammar/matchers/endswith.go b/engine/grammar/matchers/endswith.go index 65e98e3e..cdd3bfa5 100644 --- a/engine/grammar/matchers/endswith.go +++ b/engine/grammar/matchers/endswith.go @@ -3,6 +3,8 @@ package matchers import ( "fmt" "strings" + + "github.com/splitio/go-toolkit/v5/injection" ) // EndsWithMatcher matches strings which end with one of the suffixes in the feature flag @@ -12,7 +14,7 @@ type EndsWithMatcher struct { } // Match returns true if the key provided ends with one of the suffixes in the feature flag. -func (m *EndsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *EndsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("EndsWithMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/endswith_test.go b/engine/grammar/matchers/endswith_test.go index e639f86e..2785a62e 100644 --- a/engine/grammar/matchers/endswith_test.go +++ b/engine/grammar/matchers/endswith_test.go @@ -22,7 +22,7 @@ func TestEndsWith(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,19 +33,19 @@ func TestEndsWith(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.EndsWithMatcher and was %s", matcherType) } - if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil, nil) { t.Errorf("string without any of the suffixes shouldn't match") } - if matcher.Match("asd", map[string]interface{}{"value": ""}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": ""}, nil, nil) { t.Errorf("empty string shouldn't match") } - if !matcher.Match("asd", map[string]interface{}{"value": "ppabc"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "ppabc"}, nil, nil) { t.Errorf("string containing one of the suffixes should match") } - if matcher.Match("asd", map[string]interface{}{"value": "abcdefghimklsad"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "abcdefghimklsad"}, nil, nil) { t.Errorf("string containing some substrings but not as suffixes should not match") } } diff --git a/engine/grammar/matchers/equalto.go b/engine/grammar/matchers/equalto.go index 78699103..ef1e5db3 100644 --- a/engine/grammar/matchers/equalto.go +++ b/engine/grammar/matchers/equalto.go @@ -5,6 +5,7 @@ import ( "reflect" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" + "github.com/splitio/go-toolkit/v5/injection" ) // EqualToMatcher will match if two numbers or two datetimes are equal @@ -15,7 +16,7 @@ type EqualToMatcher struct { } // Match will match if the comparisonValue is equal to the matchingValue -func (m *EqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *EqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingRaw, err := m.matchingKey(key, attributes) if err != nil { diff --git a/engine/grammar/matchers/equalto_test.go b/engine/grammar/matchers/equalto_test.go index c6814d08..d295b10c 100644 --- a/engine/grammar/matchers/equalto_test.go +++ b/engine/grammar/matchers/equalto_test.go @@ -23,7 +23,7 @@ func TestEqualToMatcherInt(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -34,15 +34,15 @@ func TestEqualToMatcherInt(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.EqualToMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil, nil) { t.Error("Equal should match") } - if matcher.Match("asd", map[string]interface{}{"value": 500}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 500}, nil, nil) { t.Error("Greater should not match") } - if matcher.Match("asd", map[string]interface{}{"value": 50}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 50}, nil, nil) { t.Error("Lower should not match") } } @@ -61,7 +61,7 @@ func TestEqualToMatcherDatetime(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -74,17 +74,17 @@ func TestEqualToMatcherDatetime(t *testing.T) { attributes := make(map[string]interface{}) attributes["value"] = int64(960293532) - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should match") } attributes["value"] = int64(1275782400) - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Greater should not match") } attributes["value"] = int64(293532000) - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Lower should not match") } } diff --git a/engine/grammar/matchers/equaltoset.go b/engine/grammar/matchers/equaltoset.go index f1787e75..cc535b4b 100644 --- a/engine/grammar/matchers/equaltoset.go +++ b/engine/grammar/matchers/equaltoset.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" ) // EqualToSetMatcher matches if the set supplied to the getTreatment is equal to the one in the feature flag @@ -13,7 +14,7 @@ type EqualToSetMatcher struct { } // Match returns true if the match provided and the one in the feature flag are equal -func (m *EqualToSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *EqualToSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("EqualToSetMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/equaltoset_test.go b/engine/grammar/matchers/equaltoset_test.go index 4d735dab..c01e3cf3 100644 --- a/engine/grammar/matchers/equaltoset_test.go +++ b/engine/grammar/matchers/equaltoset_test.go @@ -22,7 +22,7 @@ func TestEqualToSetMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,15 +33,15 @@ func TestEqualToSetMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.EqualToSetMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil, nil) { t.Error("Matcher an equal set") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil, nil) { t.Error("Matcher should NOT match a superset") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil, nil) { t.Error("Matcher should not match a subset") } } diff --git a/engine/grammar/matchers/gtoet.go b/engine/grammar/matchers/gtoet.go index 14426a0a..e31b34fe 100644 --- a/engine/grammar/matchers/gtoet.go +++ b/engine/grammar/matchers/gtoet.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" + "github.com/splitio/go-toolkit/v5/injection" ) // GreaterThanOrEqualToMatcher will match if two numbers or two datetimes are equal @@ -14,7 +15,7 @@ type GreaterThanOrEqualToMatcher struct { } // Match will match if the comparisonValue is greater than or equal to the matchingValue -func (m *GreaterThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *GreaterThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingRaw, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("GreaterThanOrEqualToMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/gtoet_test.go b/engine/grammar/matchers/gtoet_test.go index fcbd8c8e..8aece37e 100644 --- a/engine/grammar/matchers/gtoet_test.go +++ b/engine/grammar/matchers/gtoet_test.go @@ -23,7 +23,7 @@ func TestGreaterThanOrEqualToMatcherInt(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -34,15 +34,15 @@ func TestGreaterThanOrEqualToMatcherInt(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.GreaterThanOrEqualToMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil, nil) { t.Error("Equal should match") } - if !matcher.Match("asd", map[string]interface{}{"value": 500}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 500}, nil, nil) { t.Error("Greater should match") } - if matcher.Match("asd", map[string]interface{}{"value": 50}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 50}, nil, nil) { t.Error("Lower should NOT match") } } @@ -61,7 +61,7 @@ func TestGreaterThanOrEqualToMatcherDatetime(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -74,17 +74,17 @@ func TestGreaterThanOrEqualToMatcherDatetime(t *testing.T) { attributes := make(map[string]interface{}) attributes["value"] = int64(960293532) - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should match") } attributes["value"] = int64(1275782400) - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Greater should match") } attributes["value"] = int64(293532000) - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Lower should NOT match") } } diff --git a/engine/grammar/matchers/inlargesegment.go b/engine/grammar/matchers/inlargesegment.go index a38e06c2..615e3dbd 100644 --- a/engine/grammar/matchers/inlargesegment.go +++ b/engine/grammar/matchers/inlargesegment.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-split-commons/v6/storage" + "github.com/splitio/go-toolkit/v5/injection" ) // InLargeSegmentMatcher matches if the key passed is in the large segment which the matcher was constructed with @@ -13,8 +14,8 @@ type InLargeSegmentMatcher struct { } // Match returns true if the key is in the matcher's segment -func (m *InLargeSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { - storage, ok := m.Context.Dependency("largeSegmentStorage").(storage.LargeSegmentStorageConsumer) +func (m *InLargeSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { + storage, ok := ctx.Dependency("largeSegmentStorage").(storage.LargeSegmentStorageConsumer) if !ok { m.logger.Error("InLargeSegmentMatcher: Unable to retrieve large segment storage!") return false diff --git a/engine/grammar/matchers/insegment.go b/engine/grammar/matchers/insegment.go index 44f87251..d162a285 100644 --- a/engine/grammar/matchers/insegment.go +++ b/engine/grammar/matchers/insegment.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-split-commons/v6/storage" + "github.com/splitio/go-toolkit/v5/injection" ) // InSegmentMatcher matches if the key passed is in the segment which the matcher was constructed with @@ -13,8 +14,8 @@ type InSegmentMatcher struct { } // Match returns true if the key is in the matcher's segment -func (m *InSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { - segmentStorage, ok := m.Context.Dependency("segmentStorage").(storage.SegmentStorageConsumer) +func (m *InSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { + segmentStorage, ok := ctx.Dependency("segmentStorage").(storage.SegmentStorageConsumer) if !ok { m.logger.Error("InSegmentMatcher: Unable to retrieve segment storage!") return false diff --git a/engine/grammar/matchers/ltoet.go b/engine/grammar/matchers/ltoet.go index fd198dc2..3abd3928 100644 --- a/engine/grammar/matchers/ltoet.go +++ b/engine/grammar/matchers/ltoet.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" + "github.com/splitio/go-toolkit/v5/injection" ) // LessThanOrEqualToMatcher will match if two numbers or two datetimes are equal @@ -14,7 +15,7 @@ type LessThanOrEqualToMatcher struct { } // Match will match if the comparisonValue is less than or equal to the matchingValue -func (m *LessThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *LessThanOrEqualToMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingRaw, err := m.matchingKey(key, attributes) if err != nil { diff --git a/engine/grammar/matchers/ltoet_test.go b/engine/grammar/matchers/ltoet_test.go index 5ea66c30..0ca85bff 100644 --- a/engine/grammar/matchers/ltoet_test.go +++ b/engine/grammar/matchers/ltoet_test.go @@ -23,7 +23,7 @@ func TestLessThanOrEqualToMatcherInt(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -34,15 +34,15 @@ func TestLessThanOrEqualToMatcherInt(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.LessThanOrEqualToMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 100}, nil, nil) { t.Error("Equal should match") } - if matcher.Match("asd", map[string]interface{}{"value": 500}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 500}, nil, nil) { t.Error("Greater should not match") } - if !matcher.Match("asd", map[string]interface{}{"value": 50}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": 50}, nil, nil) { t.Error("Lower should match") } } @@ -61,7 +61,7 @@ func TestLessThanOrEqualToMatcherDatetime(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -74,17 +74,17 @@ func TestLessThanOrEqualToMatcherDatetime(t *testing.T) { attributes := make(map[string]interface{}) attributes["value"] = int64(960293532) - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should match") } attributes["value"] = int64(1275782400) - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Greater should not match") } attributes["value"] = int64(293532000) - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Lower should match") } } diff --git a/engine/grammar/matchers/matcher_test.go b/engine/grammar/matchers/matcher_test.go index 3c5f3ae2..53c01c6d 100644 --- a/engine/grammar/matchers/matcher_test.go +++ b/engine/grammar/matchers/matcher_test.go @@ -6,7 +6,6 @@ import ( "github.com/splitio/go-split-commons/v6/dtos" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -21,7 +20,7 @@ func TestMatcherConstruction(t *testing.T) { }, } - matcher1, err := BuildMatcher(&dto1, nil, logger) + matcher1, err := BuildMatcher(&dto1, logger) if err != nil { t.Error("Matcher construction shouldn't fail") @@ -51,7 +50,7 @@ func TestMatcherConstruction(t *testing.T) { }, } - matcher2, err := BuildMatcher(&dto2, nil, logger) + matcher2, err := BuildMatcher(&dto2, logger) if err == nil { t.Error("Matcher construction shoul have failed for invalid matcher") @@ -69,9 +68,7 @@ func TestMatcherConstruction(t *testing.T) { TrafficType: "something", }, } - ctx := injection.NewContext() - ctx.AddDependency("key1", "sampleString") - matcher3, err := BuildMatcher(&dto3, ctx, logger) + matcher3, err := BuildMatcher(&dto3, logger) if err != nil { t.Error("There shouldn't have been any errors constructing the matcher") @@ -81,20 +78,6 @@ func TestMatcherConstruction(t *testing.T) { t.Error("Matcher should be negated") } - if matcher3.base().Context != ctx { - t.Error("Context not properly received", matcher3.base().Context) - } - - dep := matcher3.base().Dependency("key1") - asString, ok := dep.(string) - if !ok { - t.Error("Conversion of string stored in context failed") - } - - if asString != "sampleString" { - t.Error("Recovered string doesn't match stored one") - } - attrName := "value" dto4 := &dtos.MatcherDTO{ MatcherType: "IN_SPLIT_TREATMENT", @@ -106,7 +89,7 @@ func TestMatcherConstruction(t *testing.T) { Attribute: &attrName, }, } - matcher4, err := BuildMatcher(dto4, nil, logger) + matcher4, err := BuildMatcher(dto4, logger) if err != nil { t.Error("There shouldn't have been any errors constructing the matcher") } diff --git a/engine/grammar/matchers/matchers.go b/engine/grammar/matchers/matchers.go index d08450cc..0ae89292 100644 --- a/engine/grammar/matchers/matchers.go +++ b/engine/grammar/matchers/matchers.go @@ -68,7 +68,7 @@ const ( // MatcherInterface should be implemented by all matchers type MatcherInterface interface { - Match(key string, attributes map[string]interface{}, bucketingKey *string) bool + Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool Negate() bool base() *Matcher // This method is used to return the embedded matcher when iterating over interfaces matchingKey(key string, attributes map[string]interface{}) (interface{}, error) @@ -76,7 +76,6 @@ type MatcherInterface interface { // Matcher struct with added logic that wraps around a DTO type Matcher struct { - *injection.Context negate bool attributeName *string logger logging.LoggerInterface @@ -114,7 +113,7 @@ func (m *Matcher) base() *Matcher { } // BuildMatcher constructs the appropriate matcher based on the MatcherType attribute of the dto -func BuildMatcher(dto *dtos.MatcherDTO, ctx *injection.Context, logger logging.LoggerInterface) (MatcherInterface, error) { +func BuildMatcher(dto *dtos.MatcherDTO, logger logging.LoggerInterface) (MatcherInterface, error) { var matcher MatcherInterface var attributeName *string @@ -445,10 +444,6 @@ func BuildMatcher(dto *dtos.MatcherDTO, ctx *injection.Context, logger logging.L } } - if ctx != nil { - ctx.Inject(matcher.base()) - } - matcher.base().logger = logger return matcher, nil diff --git a/engine/grammar/matchers/partofset.go b/engine/grammar/matchers/partofset.go index 063e23d3..53c48b1a 100644 --- a/engine/grammar/matchers/partofset.go +++ b/engine/grammar/matchers/partofset.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" ) // PartOfSetMatcher matches if the set supplied to the getTreatment is a subset of the one in the feature flag @@ -13,7 +14,7 @@ type PartOfSetMatcher struct { } // Match returns true if the match provided is a subset of the one in the feature flag -func (m *PartOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *PartOfSetMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("PartOfSetMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/partofset_test.go b/engine/grammar/matchers/partofset_test.go index afa3482c..0adcd3a7 100644 --- a/engine/grammar/matchers/partofset_test.go +++ b/engine/grammar/matchers/partofset_test.go @@ -22,7 +22,7 @@ func TestPartOfSetMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,19 +33,19 @@ func TestPartOfSetMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.PartOfSetMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four"}}, nil, nil) { t.Error("Matcher an equal set") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three", "four", "five"}}, nil, nil) { t.Error("Matcher should not match a superset") } - if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil) { + if matcher.Match("asd", map[string]interface{}{"setdata": []string{}}, nil, nil) { t.Error("Matcher should not match an empty set") } - if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil) { + if !matcher.Match("asd", map[string]interface{}{"setdata": []string{"one", "two", "three"}}, nil, nil) { t.Error("Matcher should match a subset") } } diff --git a/engine/grammar/matchers/regex.go b/engine/grammar/matchers/regex.go index 47dc99e8..d931933e 100644 --- a/engine/grammar/matchers/regex.go +++ b/engine/grammar/matchers/regex.go @@ -4,6 +4,8 @@ import ( "fmt" "reflect" "regexp" + + "github.com/splitio/go-toolkit/v5/injection" ) // RegexMatcher matches if the supplied key matches the feature flag's regex @@ -13,7 +15,7 @@ type RegexMatcher struct { } // Match returns true if the supplied key matches the feature flag's regex -func (m *RegexMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *RegexMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("RegexMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/regex_test.go b/engine/grammar/matchers/regex_test.go index 01badb9d..83abcad0 100644 --- a/engine/grammar/matchers/regex_test.go +++ b/engine/grammar/matchers/regex_test.go @@ -44,7 +44,7 @@ func TestRegexMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -55,7 +55,7 @@ func TestRegexMatcher(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.RegexMatcher and was %s", matcherType) } - matches := matcher.Match("asd", map[string]interface{}{"value": text}, nil) + matches := matcher.Match("asd", map[string]interface{}{"value": text}, nil, nil) if matches != shouldMatch { t.Errorf("Match for text %s and regex %s should be %t", text, regex, shouldMatch) } diff --git a/engine/grammar/matchers/semver.go b/engine/grammar/matchers/semver.go index eeb39b36..d9519866 100644 --- a/engine/grammar/matchers/semver.go +++ b/engine/grammar/matchers/semver.go @@ -5,6 +5,7 @@ import ( "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -15,7 +16,7 @@ type EqualToSemverMatcher struct { } // Match will match if the comparisonValue is equal to the matchingValue -func (e *EqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (e *EqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { if e.semver == nil { return false } @@ -64,7 +65,7 @@ type GreaterThanOrEqualToSemverMatcher struct { } // Match compares the semver of the key with the semver in the feature flag -func (g *GreaterThanOrEqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (g *GreaterThanOrEqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { if g.semver == nil { return false } @@ -113,7 +114,7 @@ type LessThanOrEqualToSemverMatcher struct { } // Match will match if the comparisonValue is less or equal to the matchingValue -func (l *LessThanOrEqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (l *LessThanOrEqualToSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { if l.semver == nil { return false } @@ -163,7 +164,7 @@ type BetweenSemverMatcher struct { } // Match will match if the comparisonValue is between to the matchingValue -func (b *BetweenSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (b *BetweenSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { if b.startSemver == nil || b.endSemver == nil { return false } @@ -217,7 +218,7 @@ type InListSemverMatcher struct { } // Match will match if the comparisonValue is in list to the matchingValue -func (i *InListSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (i *InListSemverMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { if i.semvers.IsEmpty() { return false } diff --git a/engine/grammar/matchers/semver_test.go b/engine/grammar/matchers/semver_test.go index 4bf0ba26..f265010f 100644 --- a/engine/grammar/matchers/semver_test.go +++ b/engine/grammar/matchers/semver_test.go @@ -79,7 +79,7 @@ func TestEqualToSemverMatcher(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -87,7 +87,7 @@ func TestEqualToSemverMatcher(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "1.0.0" - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should match") } } @@ -103,7 +103,7 @@ func TestPatchDiffers(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -111,7 +111,7 @@ func TestPatchDiffers(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "1.0.0" - if matcher.Match("sded", map[string]interface{}{}, nil) { + if matcher.Match("sded", map[string]interface{}{}, nil, nil) { t.Error("Equal should not match") } } @@ -127,7 +127,7 @@ func TestPreReleaseShouldReturnTrueWhenVersionsAreEqual(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -135,7 +135,7 @@ func TestPreReleaseShouldReturnTrueWhenVersionsAreEqual(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "1.2.3----RC-SNAPSHOT.12.9.1--.12.88" - if !matcher.Match("ass", attributes, nil) { + if !matcher.Match("ass", attributes, nil, nil) { t.Error("Equal should match") } } @@ -151,14 +151,14 @@ func TestPreReleaseShouldReturnFalseWhenSemverIsNil(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } attributes := make(map[string]interface{}) attributes[attrName] = "1.2.3" - if matcher.Match("ass", attributes, nil) { + if matcher.Match("ass", attributes, nil, nil) { t.Error("Equal should match") } } @@ -174,7 +174,7 @@ func TestPreReleaseShouldReturnFalseWhenVersionsDiffer(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -182,7 +182,7 @@ func TestPreReleaseShouldReturnFalseWhenVersionsDiffer(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "1.2.3----RC-SNAPSHOT.12.9.1--.12.99" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should not match") } } @@ -198,7 +198,7 @@ func TestMetadataShouldReturnTrueWhenVersionsAreEqual(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -206,7 +206,7 @@ func TestMetadataShouldReturnTrueWhenVersionsAreEqual(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2-rc.2+metadata-lalala" - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should match") } } @@ -222,7 +222,7 @@ func TestMetadataShouldReturnFalseWhenVersionsDiffer(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -230,7 +230,7 @@ func TestMetadataShouldReturnFalseWhenVersionsDiffer(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2-rc.2+metadata" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("Equal should not match") } } @@ -241,7 +241,7 @@ func TestShouldReturnErrorWithNilSemver(t *testing.T) { MatcherType: MatcherEqualToSemver, String: nil, } - _, err := BuildMatcher(dto, nil, logger) + _, err := BuildMatcher(dto, logger) if err == nil { t.Error("There should be errors when building the matcher") } @@ -264,7 +264,7 @@ func TestGreaterThanOrEqualToSemverMatcher(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -275,7 +275,7 @@ func TestGreaterThanOrEqualToSemverMatcher(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = twoSemvers.semver2 - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error(twoSemvers.semver1, " >= ", twoSemvers.semver2, " should match") } } @@ -292,14 +292,14 @@ func TestGreaterThanOrEqualToSemverMatcherWithNilSemver(t *testing.T) { MatcherType: MatcherTypeGreaterThanOrEqualToSemver, String: &semvers, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should not be errors when building the matcher") } attributes := make(map[string]interface{}) attributes[attrName] = "2.3.4" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("2.3.4 should not match") } } @@ -320,7 +320,7 @@ func TestLessThanOrEqualToSemverMatcher(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -332,7 +332,7 @@ func TestLessThanOrEqualToSemverMatcher(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = twoSemvers.semver1 - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error(twoSemvers.semver2, " <= ", twoSemvers.semver1, " should match") } } @@ -344,7 +344,7 @@ func TestLessThanOrEqualToSemverMatcherWithInvalidSemver(t *testing.T) { MatcherType: MatcherTypeLessThanOrEqualToSemver, String: nil, } - _, err := BuildMatcher(dto, nil, logger) + _, err := BuildMatcher(dto, logger) if err == nil { t.Error("There should be errors when building the matcher") } @@ -361,14 +361,14 @@ func TestLessThanOrEqualToSemverMatcherWithNilSemver(t *testing.T) { MatcherType: MatcherTypeLessThanOrEqualToSemver, String: &semvers, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should not be errors when building the matcher") } attributes := make(map[string]interface{}) attributes[attrName] = "2.3.4" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("2.3.4 should not match") } } @@ -392,7 +392,7 @@ func TestBetweenSemverMatcher(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -404,7 +404,7 @@ func TestBetweenSemverMatcher(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = threeSemvers.semver2 - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error(threeSemvers.semver2, " between ", threeSemvers.semver1, "and", threeSemvers.semver3, " should match") } } @@ -419,7 +419,7 @@ func TestBetweenSemverWithNilSemvers(t *testing.T) { End: nil, }, } - _, err := BuildMatcher(dto, nil, logger) + _, err := BuildMatcher(dto, logger) if err == nil { t.Error("There should be errors when building the matcher") } @@ -440,14 +440,14 @@ func TestBetweenSemverWithInvalidSemvers(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should not be errors when building the matcher") } attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2-rc.1.2" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("2.2.2-rc.1.2", " between should not match") } } @@ -466,7 +466,7 @@ func TestInListSemvers(t *testing.T) { MatcherType: MatcherTypeInListSemver, Whitelist: &dtos.WhitelistMatcherDataDTO{Whitelist: semvers}, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -478,7 +478,7 @@ func TestInListSemvers(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2-rc.1.2" - if !matcher.Match("asd", attributes, nil) { + if !matcher.Match("asd", attributes, nil, nil) { t.Error("2.2.2-rc.1.2", " in list ", semvers, " should match") } } @@ -497,7 +497,7 @@ func TestInListSemversNotMatch(t *testing.T) { MatcherType: MatcherTypeInListSemver, Whitelist: &dtos.WhitelistMatcherDataDTO{Whitelist: semvers}, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") } @@ -509,7 +509,7 @@ func TestInListSemversNotMatch(t *testing.T) { attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("2.2.2-rc.1.2", " in list ", semvers, " should not match") } } @@ -529,14 +529,14 @@ func TestInListInvalidSemvers(t *testing.T) { Attribute: &attrName, }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should not be errors when building the matcher") } attributes := make(map[string]interface{}) attributes[attrName] = "2.2.2" - if matcher.Match("asd", attributes, nil) { + if matcher.Match("asd", attributes, nil, nil) { t.Error("2.2.2", " in list ", semvers, " should not match") } } diff --git a/engine/grammar/matchers/startswith.go b/engine/grammar/matchers/startswith.go index 78cd13ca..81e0cc68 100644 --- a/engine/grammar/matchers/startswith.go +++ b/engine/grammar/matchers/startswith.go @@ -3,6 +3,8 @@ package matchers import ( "fmt" "strings" + + "github.com/splitio/go-toolkit/v5/injection" ) // StartsWithMatcher matches strings which start with one of the prefixes in the feature flag @@ -12,7 +14,7 @@ type StartsWithMatcher struct { } // Match returns true if the key provided starts with one of the prefixes in the feature flag. -func (m *StartsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *StartsWithMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("StartsWithMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/startswith_test.go b/engine/grammar/matchers/startswith_test.go index 060e8761..39917738 100644 --- a/engine/grammar/matchers/startswith_test.go +++ b/engine/grammar/matchers/startswith_test.go @@ -22,7 +22,7 @@ func TestStartsWith(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,19 +33,19 @@ func TestStartsWith(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.StartsWithMatcher and was %s", matcherType) } - if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "zzz"}, nil, nil) { t.Errorf("string without any of the prefixes shouldn't match") } - if matcher.Match("asd", map[string]interface{}{"value": ""}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": ""}, nil, nil) { t.Errorf("empty string shouldn't match") } - if !matcher.Match("asd", map[string]interface{}{"value": "abcpp"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "abcpp"}, nil, nil) { t.Errorf("string containing one of the prefixes should match") } - if matcher.Match("asd", map[string]interface{}{"value": "hdhfabcdefghimklsad"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "hdhfabcdefghimklsad"}, nil, nil) { t.Errorf("string containing some substrings but not as prefixes should not match") } } diff --git a/engine/grammar/matchers/whitelist.go b/engine/grammar/matchers/whitelist.go index 05961bf3..8efa8290 100644 --- a/engine/grammar/matchers/whitelist.go +++ b/engine/grammar/matchers/whitelist.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/splitio/go-toolkit/v5/datastructures/set" + "github.com/splitio/go-toolkit/v5/injection" ) // WhitelistMatcher matches if the key received is present in the matcher's whitelist @@ -13,7 +14,7 @@ type WhitelistMatcher struct { } // Match returns true if the key is present in the whitelist. -func (m *WhitelistMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool { +func (m *WhitelistMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string, ctx *injection.Context) bool { matchingKey, err := m.matchingKey(key, attributes) if err != nil { m.logger.Warning(fmt.Sprintf("WhitelistMatcher: %s", err.Error())) diff --git a/engine/grammar/matchers/whitelist_test.go b/engine/grammar/matchers/whitelist_test.go index ce0bb45e..630a3ed5 100644 --- a/engine/grammar/matchers/whitelist_test.go +++ b/engine/grammar/matchers/whitelist_test.go @@ -22,7 +22,7 @@ func TestWhitelistMatcherAttr(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -33,11 +33,11 @@ func TestWhitelistMatcherAttr(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.WhitelistMatcher and was %s", matcherType) } - if !matcher.Match("asd", map[string]interface{}{"value": "aaa"}, nil) { + if !matcher.Match("asd", map[string]interface{}{"value": "aaa"}, nil, nil) { t.Error("Item in whitelist should match") } - if matcher.Match("asd", map[string]interface{}{"value": "ccc"}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": "ccc"}, nil, nil) { t.Error("Item NOT in whitelist should NOT match") } } @@ -51,7 +51,7 @@ func TestWhitelistMatcherKey(t *testing.T) { }, } - matcher, err := BuildMatcher(dto, nil, logger) + matcher, err := BuildMatcher(dto, logger) if err != nil { t.Error("There should be no errors when building the matcher") t.Error(err) @@ -62,11 +62,11 @@ func TestWhitelistMatcherKey(t *testing.T) { t.Errorf("Incorrect matcher constructed. Should be *matchers.WhitelistMatcher and was %s", matcherType) } - if !matcher.Match("aaa", map[string]interface{}{"value": 1}, nil) { + if !matcher.Match("aaa", map[string]interface{}{"value": 1}, nil, nil) { t.Error("Item in whitelist should match") } - if matcher.Match("asd", map[string]interface{}{"value": 2}, nil) { + if matcher.Match("asd", map[string]interface{}{"value": 2}, nil, nil) { t.Error("Item NOT in whitelist should NOT match") } } diff --git a/engine/grammar/split.go b/engine/grammar/split.go index faab945f..cc19a93c 100644 --- a/engine/grammar/split.go +++ b/engine/grammar/split.go @@ -7,7 +7,6 @@ import ( "github.com/splitio/go-split-commons/v6/engine/evaluator/impressionlabels" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -18,8 +17,8 @@ type Split struct { } type SplitProducer interface { - GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *Split - GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *Split] + GetSplit(splitName string, logger logging.LoggerInterface) *Split + GetSplits(splitNames []string, logger logging.LoggerInterface) iter.Seq2[string, *Split] GetNamesByFlagSets(sets []string) map[string][]string } @@ -32,19 +31,19 @@ var conditionReplacementUnsupportedMatcher []*Condition = []*Condition{{ }} // NewSplit instantiates a new Split object and all it's internal structures mapped to model classes -func NewSplit(splitDTO *dtos.SplitDTO, ctx *injection.Context, logger logging.LoggerInterface) *Split { +func NewSplit(splitDTO *dtos.SplitDTO, logger logging.LoggerInterface) *Split { split := Split{ - conditions: processConditions(splitDTO, ctx, logger), + conditions: processConditions(splitDTO, logger), splitData: splitDTO, } return &split } -func processConditions(splitDTO *dtos.SplitDTO, ctx *injection.Context, logger logging.LoggerInterface) []*Condition { +func processConditions(splitDTO *dtos.SplitDTO, logger logging.LoggerInterface) []*Condition { conditionsToReturn := make([]*Condition, 0) for _, cond := range splitDTO.Conditions { - condition, err := NewCondition(&cond, ctx, logger) + condition, err := NewCondition(&cond, logger) if err != nil { logger.Debug("Overriding conditions due unexpected matcher received") return conditionReplacementUnsupportedMatcher diff --git a/engine/grammar/split_test.go b/engine/grammar/split_test.go index 18b93434..034a5dff 100644 --- a/engine/grammar/split_test.go +++ b/engine/grammar/split_test.go @@ -24,7 +24,7 @@ func TestSplitCreation(t *testing.T) { TrafficAllocationSeed: 333, TrafficTypeName: "tt1", } - split := NewSplit(&dto, nil, logger) + split := NewSplit(&dto, logger) if split.Algo() != SplitAlgoLegacy { t.Error("Algo() should return legacy") @@ -90,7 +90,7 @@ func TestSplitCreationWithConditionsMatcher(t *testing.T) { TrafficAllocationSeed: 333, TrafficTypeName: "tt1", } - split := NewSplit(&dto, nil, logger) + split := NewSplit(&dto, logger) if split.Algo() != SplitAlgoLegacy { t.Error("Algo() should return legacy") @@ -160,7 +160,7 @@ func TestSplitCreationWithUnsupportedMatcher(t *testing.T) { TrafficAllocationSeed: 333, TrafficTypeName: "tt1", } - split := NewSplit(&dto, nil, logger) + split := NewSplit(&dto, logger) if split.Algo() != SplitAlgoLegacy { t.Error("Algo() should return legacy") diff --git a/engine/validator/matchers.go b/engine/validator/matchers.go index 688dfec5..ed2c1566 100644 --- a/engine/validator/matchers.go +++ b/engine/validator/matchers.go @@ -7,7 +7,6 @@ import ( "github.com/splitio/go-split-commons/v6/engine/grammar" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers" "github.com/splitio/go-split-commons/v6/engine/grammar/matchers/datatypes" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -25,7 +24,7 @@ var unsupportedMatcherConditionReplacement []dtos.ConditionDTO = []dtos.Conditio func shouldOverrideConditions(conditions []dtos.ConditionDTO, logger logging.LoggerInterface) bool { for _, condition := range conditions { for _, matcher := range condition.MatcherGroup.Matchers { - _, err := matchers.BuildMatcher(&matcher, &injection.Context{}, logger) + _, err := matchers.BuildMatcher(&matcher, logger) if _, ok := err.(datatypes.UnsupportedMatcherError); ok { return true } diff --git a/storage/inmemory/mutexmap/splits.go b/storage/inmemory/mutexmap/splits.go index 6d20a971..ed59e77a 100644 --- a/storage/inmemory/mutexmap/splits.go +++ b/storage/inmemory/mutexmap/splits.go @@ -9,7 +9,6 @@ import ( "github.com/splitio/go-split-commons/v6/flagsets" "github.com/splitio/go-split-commons/v6/storage" "github.com/splitio/go-toolkit/v5/datastructures/set" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -277,7 +276,7 @@ func (m *MMSplitStorage) GetNamesByFlagSets(sets []string) map[string][]string { } // GetSplit returns a cached grammar.Split if it exists, otherwise creates a new one -func (m *MMSplitStorage) GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *grammar.Split { +func (m *MMSplitStorage) GetSplit(splitName string, logger logging.LoggerInterface) *grammar.Split { m.mutex.RLock() if cached, ok := m.splitCache[splitName]; ok { m.mutex.RUnlock() @@ -290,7 +289,7 @@ func (m *MMSplitStorage) GetSplit(splitName string, ctx *injection.Context, logg return nil } - split := grammar.NewSplit(splitDTO, ctx, logger) + split := grammar.NewSplit(splitDTO, logger) m.mutex.Lock() defer m.mutex.Unlock() @@ -299,10 +298,10 @@ func (m *MMSplitStorage) GetSplit(splitName string, ctx *injection.Context, logg return split } -func (m *MMSplitStorage) GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { +func (m *MMSplitStorage) GetSplits(splitNames []string, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { return func(yield func(string, *grammar.Split) bool) { for _, splitName := range splitNames { - if !yield(splitName, m.GetSplit(splitName, ctx, logger)) { + if !yield(splitName, m.GetSplit(splitName, logger)) { return } } diff --git a/storage/producer/simple_producer.go b/storage/producer/simple_producer.go index a293d395..4b15a217 100644 --- a/storage/producer/simple_producer.go +++ b/storage/producer/simple_producer.go @@ -5,7 +5,6 @@ import ( "github.com/splitio/go-split-commons/v6/engine/grammar" "github.com/splitio/go-split-commons/v6/storage" - "github.com/splitio/go-toolkit/v5/injection" "github.com/splitio/go-toolkit/v5/logging" ) @@ -18,21 +17,21 @@ func NewSimpleProducer(splitStorage storage.SplitStorageConsumer) *SimpleProduce return &SimpleProducer{splitStorage: splitStorage} } -func (p *SimpleProducer) GetSplit(splitName string, ctx *injection.Context, logger logging.LoggerInterface) *grammar.Split { +func (p *SimpleProducer) GetSplit(splitName string, logger logging.LoggerInterface) *grammar.Split { dto := p.splitStorage.Split(splitName) if dto == nil { return nil } - return grammar.NewSplit(dto, ctx, logger) + return grammar.NewSplit(dto, logger) } -func (p *SimpleProducer) GetSplits(splitNames []string, ctx *injection.Context, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { +func (p *SimpleProducer) GetSplits(splitNames []string, logger logging.LoggerInterface) iter.Seq2[string, *grammar.Split] { return func(yield func(string, *grammar.Split) bool) { dtos := p.splitStorage.FetchMany(splitNames) for splitName, dto := range dtos { var split *grammar.Split if dto != nil { - split = grammar.NewSplit(dto, ctx, logger) + split = grammar.NewSplit(dto, logger) } if !yield(splitName, split) { return From 3a638c7a279703f7db9c838a64af24d56f171656 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:38:16 -0500 Subject: [PATCH 3/7] Avoid temporarily creating treatment pointer --- engine/engine.go | 6 +++--- engine/engine_test.go | 8 ++++---- engine/evaluator/evaluator.go | 9 ++++----- engine/grammar/condition.go | 6 +++--- engine/grammar/condition_test.go | 8 ++++---- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index bc986b01..3821098d 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -26,7 +26,7 @@ func (e *Engine) DoEvaluation( bucketingKey string, attributes map[string]interface{}, ctx *injection.Context, -) (*string, string) { +) (string, string) { inRollOut := false for _, condition := range split.Conditions() { if !inRollOut && condition.ConditionType() == grammar.ConditionTypeRollout { @@ -38,7 +38,7 @@ func (e *Engine) DoEvaluation( " Returning default treatment", split.Name(), key, )) defaultTreatment := split.DefaultTreatment() - return &defaultTreatment, impressionlabels.NotInSplit + return defaultTreatment, impressionlabels.NotInSplit } inRollOut = true } @@ -50,7 +50,7 @@ func (e *Engine) DoEvaluation( return treatment, condition.Label() } } - return nil, impressionlabels.NoConditionMatched + return "", impressionlabels.NoConditionMatched } func (e *Engine) calculateBucket(algo int, bucketingKey string, seed int64) int { diff --git a/engine/engine_test.go b/engine/engine_test.go index 2ba504f6..133064e2 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -91,7 +91,7 @@ func TestTreatmentOnTrafficAllocation1(t *testing.T) { eng.logger = logger treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) - if *treatment == "default" { + if treatment == "default" { t.Error("It should not return default treatment.") } } @@ -140,7 +140,7 @@ func TestTreatmentOnTrafficAllocation99(t *testing.T) { eng.logger = logger treatment, _ := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) - if *treatment != "default" { + if treatment != "default" { t.Error("It should return default treatment.") } } @@ -244,7 +244,7 @@ func TestEvaluations(t *testing.T) { for _, tr := range treatmentsResults { treatment, _ := eng.DoEvaluation(split, tr.Key, tr.Key, nil, nil) - if *treatment != tr.Result { + if treatment != tr.Result { t.Error("Checking expected treatment " + tr.Result + " for key: " + tr.Key) } } @@ -273,7 +273,7 @@ func TestNoConditionMatched(t *testing.T) { treatment, err := eng.DoEvaluation(split, "aaaaaaklmnbv", "aaaaaaklmnbv", nil, nil) - if treatment != nil { + if len(treatment) > 0 { t.Error("It should be nil.") } diff --git a/engine/evaluator/evaluator.go b/engine/evaluator/evaluator.go index 13d93d32..07c56c1b 100644 --- a/engine/evaluator/evaluator.go +++ b/engine/evaluator/evaluator.go @@ -91,23 +91,22 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl ctx.AddDependency("segmentStorage", e.segmentStorage) ctx.AddDependency("evaluator", e) treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes, ctx) - if treatment == nil { + if len(treatment) == 0 { e.logger.Warning(fmt.Sprintf( "No condition matched, returning default treatment: %s", split.DefaultTreatment(), )) defaultTreatment := split.DefaultTreatment() - treatment = &defaultTreatment + treatment = defaultTreatment label = impressionlabels.NoConditionMatched } - if _, ok := split.Configurations()[*treatment]; ok { - treatmentConfig := split.Configurations()[*treatment] + if treatmentConfig, ok := split.Configurations()[treatment]; ok { config = &treatmentConfig } return &Result{ - Treatment: *treatment, + Treatment: treatment, Label: label, SplitChangeNumber: split.ChangeNumber(), Config: config, diff --git a/engine/grammar/condition.go b/engine/grammar/condition.go index 3d119a20..19b917c8 100644 --- a/engine/grammar/condition.go +++ b/engine/grammar/condition.go @@ -89,15 +89,15 @@ func (c *Condition) Matches(key string, bucketingKey *string, attributes map[str } // CalculateTreatment calulates the treatment for a specific condition based on the bucket -func (c *Condition) CalculateTreatment(bucket int) *string { +func (c *Condition) CalculateTreatment(bucket int) string { accum := 0 for _, partition := range c.partitions { accum += partition.partitionData.Size if bucket <= accum { - return &partition.partitionData.Treatment + return partition.partitionData.Treatment } } - return nil + return "" } func applyCombiner(results []bool, combiner string) bool { diff --git a/engine/grammar/condition_test.go b/engine/grammar/condition_test.go index aebf916c..3370c471 100644 --- a/engine/grammar/condition_test.go +++ b/engine/grammar/condition_test.go @@ -71,12 +71,12 @@ func TestConditionWrapperObject(t *testing.T) { } treatment1 := wrapped.CalculateTreatment(50) - if treatment1 != nil && *treatment1 != "on" { + if len(treatment1) > 0 && treatment1 != "on" { t.Error("CalculateTreatment returned incorrect treatment") } treatment2 := wrapped.CalculateTreatment(80) - if treatment2 != nil && *treatment2 != "off" { + if len(treatment2) > 0 && treatment2 != "off" { t.Error("CalculateTreatment returned incorrect treatment") } } @@ -141,12 +141,12 @@ func TestAnotherWrapper(t *testing.T) { } treatment1 := wrapped.CalculateTreatment(50) - if treatment1 != nil && *treatment1 != "on" { + if len(treatment1) > 0 && treatment1 != "on" { t.Error("CalculateTreatment returned incorrect treatment") } treatment2 := wrapped.CalculateTreatment(80) - if treatment2 != nil && *treatment2 != "off" { + if len(treatment2) > 0 && treatment2 != "off" { t.Error("CalculateTreatment returned incorrect treatment") } } From d5f65c3c4b3d981579834366d79b4db102075a61 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:41:51 -0500 Subject: [PATCH 4/7] Pre-allocate injection context instead of recreating every evaluation --- engine/evaluator/evaluator.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/engine/evaluator/evaluator.go b/engine/evaluator/evaluator.go index 07c56c1b..d3279a5b 100644 --- a/engine/evaluator/evaluator.go +++ b/engine/evaluator/evaluator.go @@ -41,6 +41,7 @@ type Evaluator struct { segmentStorage storage.SegmentStorageConsumer eng *engine.Engine logger logging.LoggerInterface + ctx *injection.Context } // NewEvaluator instantiates an Evaluator struct and returns a reference to it @@ -51,12 +52,17 @@ func NewEvaluator( logger logging.LoggerInterface, ) *Evaluator { - return &Evaluator{ + e := &Evaluator{ splitProducer: splitProducer, segmentStorage: segmentStorage, eng: eng, logger: logger, + ctx: injection.NewContext(), } + + e.ctx.AddDependency("segmentStorage", e.segmentStorage) + e.ctx.AddDependency("evaluator", e) + return e } func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFlag string, split *grammar.Split, attributes map[string]interface{}) *Result { @@ -87,10 +93,7 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl } } - ctx := injection.NewContext() - ctx.AddDependency("segmentStorage", e.segmentStorage) - ctx.AddDependency("evaluator", e) - treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes, ctx) + treatment, label := e.eng.DoEvaluation(split, key, bucketingKey, attributes, e.ctx) if len(treatment) == 0 { e.logger.Warning(fmt.Sprintf( "No condition matched, returning default treatment: %s", From 9682665c09203365cc6ba586b967e5836a251ff6 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:43:04 -0500 Subject: [PATCH 5/7] Optimize condition `Matches` to avoid creation of temporary slice --- engine/grammar/condition.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/engine/grammar/condition.go b/engine/grammar/condition.go index 19b917c8..77ab2668 100644 --- a/engine/grammar/condition.go +++ b/engine/grammar/condition.go @@ -78,14 +78,22 @@ func (c *Condition) Label() string { // Matches returns true if the condition matches for a specific key and/or set of attributes func (c *Condition) Matches(key string, bucketingKey *string, attributes map[string]interface{}, ctx *injection.Context) bool { - partial := make([]bool, len(c.matchers)) - for i, matcher := range c.matchers { - partial[i] = matcher.Match(key, attributes, bucketingKey, ctx) - if matcher.Negate() { - partial[i] = !partial[i] + switch c.combiner { + case "AND": + for _, matcher := range c.matchers { + result := matcher.Match(key, attributes, bucketingKey, ctx) + // false, false -> Fail + // true, false -> Continue + // false, true -> Continue + // true, true -> Fail + if result == matcher.Negate() { + return false + } } + return true + default: + return false } - return applyCombiner(partial, c.combiner) } // CalculateTreatment calulates the treatment for a specific condition based on the bucket @@ -99,16 +107,3 @@ func (c *Condition) CalculateTreatment(bucket int) string { } return "" } - -func applyCombiner(results []bool, combiner string) bool { - temp := true - switch combiner { - case "AND": - for _, result := range results { - temp = temp && result - } - default: - return false - } - return temp -} From 74f5e603116a1f23734431f926819d7dcc078817 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:43:48 -0500 Subject: [PATCH 6/7] preallocate capacity for EvaluateFeatures result --- engine/evaluator/evaluator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/evaluator/evaluator.go b/engine/evaluator/evaluator.go index d3279a5b..03927cae 100644 --- a/engine/evaluator/evaluator.go +++ b/engine/evaluator/evaluator.go @@ -135,7 +135,7 @@ func (e *Evaluator) EvaluateFeature(key string, bucketingKey *string, featureFla // EvaluateFeatures returns a struct with the resulting treatment and extra information for the impression func (e *Evaluator) EvaluateFeatures(key string, bucketingKey *string, featureFlags []string, attributes map[string]interface{}) Results { var results = Results{ - Evaluations: make(map[string]Result), + Evaluations: make(map[string]Result, len(featureFlags)), EvaluationTime: 0, } before := time.Now() From 59420f50f222d008f0306c4aff347f7141c4b263 Mon Sep 17 00:00:00 2001 From: Colton Schlosser <1498529+cltnschlosser@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:44:17 -0500 Subject: [PATCH 7/7] Use a typed set in WhitelistMatcher The go-toolkit set internally has interface{} key, which prohibts the go compiler from making optimizations that it can for string keyed maps. --- engine/grammar/matchers/whitelist.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/engine/grammar/matchers/whitelist.go b/engine/grammar/matchers/whitelist.go index 8efa8290..fd4c654f 100644 --- a/engine/grammar/matchers/whitelist.go +++ b/engine/grammar/matchers/whitelist.go @@ -3,14 +3,15 @@ package matchers import ( "fmt" - "github.com/splitio/go-toolkit/v5/datastructures/set" "github.com/splitio/go-toolkit/v5/injection" ) +var keyExists = struct{}{} + // WhitelistMatcher matches if the key received is present in the matcher's whitelist type WhitelistMatcher struct { Matcher - whitelist *set.ThreadUnsafeSet + whitelist map[string]struct{} } // Match returns true if the key is present in the whitelist. @@ -27,14 +28,15 @@ func (m *WhitelistMatcher) Match(key string, attributes map[string]interface{}, return false } - return m.whitelist.Has(stringMatchingKey) + _, has := m.whitelist[stringMatchingKey] + return has } // NewWhitelistMatcher returns a new WhitelistMatcher func NewWhitelistMatcher(negate bool, whitelist []string, attributeName *string) *WhitelistMatcher { - wlSet := set.NewSet() + wlSet := make(map[string]struct{}, len(whitelist)) for _, elem := range whitelist { - wlSet.Add(elem) + wlSet[elem] = keyExists } return &WhitelistMatcher{ Matcher: Matcher{