Skip to content

Commit 4bc3716

Browse files
committed
Added RBS matcher
1 parent f856352 commit 4bc3716

7 files changed

Lines changed: 159 additions & 30 deletions

File tree

lib/splitclient-rb.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
require 'splitclient-rb/helpers/decryption_helper'
4949
require 'splitclient-rb/helpers/util'
5050
require 'splitclient-rb/helpers/repository_helper'
51+
require 'splitclient-rb/helpers/evaluator_helper'
5152
require 'splitclient-rb/split_factory'
5253
require 'splitclient-rb/split_factory_builder'
5354
require 'splitclient-rb/split_config'
@@ -97,6 +98,7 @@
9798
require 'splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher'
9899
require 'splitclient-rb/engine/matchers/between_semver_matcher'
99100
require 'splitclient-rb/engine/matchers/in_list_semver_matcher'
101+
require 'splitclient-rb/engine/matchers/rule_based_segment_matcher'
100102
require 'splitclient-rb/engine/evaluator/splitter'
101103
require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
102104
require 'splitclient-rb/engine/impressions/unique_keys_tracker'

lib/splitclient-rb/cache/repositories/segments_repository.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ def segment_keys_count
8383
0
8484
end
8585

86+
def contains?(segment_names)
87+
if segment_names.empty?
88+
return false
89+
end
90+
return segment_names.to_set.subset?(used_segment_names.to_set)
91+
end
92+
8693
private
8794

8895
def segment_data(name)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module SplitIoClient
4+
#
5+
# class to implement the user defined matcher
6+
#
7+
class RuleBasedSegmentMatcher < Matcher
8+
MATCHER_TYPE = 'IN_RULE_BASED_SEGMENT'
9+
10+
def initialize(rule_based_segments_repository, segments_repository, segment_name, config, evaluator)
11+
super(config.logger)
12+
@rule_based_segments_repository = rule_based_segments_repository
13+
@segments_repository = segments_repository
14+
@segment_name = segment_name
15+
@evaluator = evaluator
16+
@config = config
17+
end
18+
19+
#
20+
# evaluates if the key matches the matcher
21+
#
22+
# @param key [string] key value to be matched
23+
#
24+
# @return [boolean] evaluation of the key against the segment
25+
def match?(args)
26+
rule_based_segment = @rule_based_segments_repository.get_rule_based_segment(@segment_name)
27+
return false if rule_based_segment.nil?
28+
29+
return false if rule_based_segment[:excluded][:keys].include?([args[:value]])
30+
31+
return false if @segments_repository.contains?(rule_based_segment[:excluded][:segments])
32+
33+
matches = false
34+
rule_based_segment[:conditions].each do |c|
35+
condition = SplitIoClient::Condition.new(c, @config)
36+
next if condition.empty?
37+
38+
matches = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository).match?(args)
39+
end
40+
@logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matches}")
41+
42+
matches
43+
end
44+
end
45+
end

lib/splitclient-rb/engine/parser/condition.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ def negate
246246
# @return [void]
247247
def set_partitions
248248
partitions_list = []
249+
return partitions_list unless @data.key?('partitions')
250+
249251
@data[:partitions].each do |p|
250252
partition = SplitIoClient::Partition.new(p)
251253
partitions_list << partition

lib/splitclient-rb/engine/parser/evaluator.rb

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def match(split, keys, attributes)
5959
in_rollout = true
6060
end
6161

62-
condition_matched = matcher_type(condition).match?(
62+
condition_matched = Helpers::EvaluatorHelper::matcher_type(condition, @segments_repository).match?(
6363
matching_key: keys[:matching_key],
6464
bucketing_key: keys[:bucketing_key],
6565
evaluator: self,
@@ -80,38 +80,9 @@ def match(split, keys, attributes)
8080
treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber], split_configurations(split[:defaultTreatment], split))
8181
end
8282

83-
def matcher_type(condition)
84-
matchers = []
85-
86-
@segments_repository.adapter.pipelined do
87-
condition.matchers.each do |matcher|
88-
matchers << if matcher[:negate]
89-
condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, matcher))
90-
else
91-
matcher_instance(matcher[:matcherType], condition, matcher)
92-
end
93-
end
94-
end
95-
96-
final_matcher = condition.create_condition_matcher(matchers)
97-
98-
if final_matcher.nil?
99-
@logger.error('Invalid matcher type')
100-
else
101-
final_matcher
102-
end
103-
end
104-
10583
def treatment_hash(label, treatment, change_number = nil, configurations = nil)
10684
{ label: label, treatment: treatment, change_number: change_number, config: configurations }
10785
end
108-
109-
def matcher_instance(type, condition, matcher)
110-
condition.send(
111-
"matcher_#{type.downcase}",
112-
matcher: matcher, segments_repository: @segments_repository
113-
)
114-
end
11586
end
11687
end
11788
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
module SplitIoClient
4+
module Helpers
5+
class EvaluatorHelper
6+
def self.matcher_type(condition, segments_repository)
7+
matchers = []
8+
9+
segments_repository.adapter.pipelined do
10+
condition.matchers.each do |matcher|
11+
matchers << if matcher[:negate]
12+
condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, matcher))
13+
else
14+
matcher_instance(matcher[:matcherType], condition, matcher, segments_repository)
15+
end
16+
end
17+
end
18+
final_matcher = condition.create_condition_matcher(matchers)
19+
20+
if final_matcher.nil?
21+
config.logger.error('Invalid matcher type')
22+
else
23+
final_matcher
24+
end
25+
final_matcher
26+
end
27+
28+
def self.matcher_instance(type, condition, matcher, segments_repository)
29+
condition.send(
30+
"matcher_#{type.downcase}",
31+
matcher: matcher, segments_repository: segments_repository
32+
)
33+
end
34+
end
35+
end
36+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe SplitIoClient::RuleBasedSegmentMatcher do
6+
let(:config) { SplitIoClient::SplitConfig.new(debug_enabled: true) }
7+
let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) }
8+
let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([])}
9+
let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])}
10+
let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) }
11+
let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, true) }
12+
13+
14+
context '#string_type' do
15+
it 'is not string type matcher' do
16+
expect(described_class.new(nil, nil, nil, config, nil).string_type?).to be false
17+
end
18+
end
19+
20+
context 'test_matcher' do
21+
it 'return false if excluded key is passed' do
22+
rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config)
23+
rbs_repositoy.update([{name: 'foo', trafficTypeName: 'tt_name_1', conditions: [], excluded: {keys: ['key1'], segments: []}}], [], -1)
24+
matcher = described_class.new(rbs_repositoy, segments_repository, 'foo', config, nil)
25+
expect(matcher.match?(value: 'key1')).to be false
26+
end
27+
28+
it 'return false if excluded segment is passed' do
29+
rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config)
30+
segments_repository.add_to_segment({:name => 'segment1', :added => [], :removed => []})
31+
rbs_repositoy.update([{name: 'foo', trafficTypeName: 'tt_name_1', conditions: [], excluded: {keys: ['key1'], segments: ['segment1']}}], [], -1)
32+
matcher = described_class.new(rbs_repositoy, segments_repository, 'foo', config, nil)
33+
expect(matcher.match?(value: 'key2')).to be false
34+
end
35+
36+
it 'return true if condition matches' do
37+
rule_based_segment = { :name => 'corge', :trafficTypeName => 'tt_name_5',
38+
:excluded => {:keys => [], :segments => []},
39+
:conditions => [
40+
{
41+
:contitionType => 'WHITELIST',
42+
:label => 'some_label',
43+
:matcherGroup => {
44+
:matchers => [
45+
{
46+
:keySelector => nil,
47+
:matcherType => 'WHITELIST',
48+
:whitelistMatcherData => {
49+
:whitelist => ['k1', 'k2', 'k3']
50+
},
51+
:negate => false,
52+
}
53+
],
54+
:combiner => 'AND'
55+
}
56+
}]
57+
}
58+
59+
rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config)
60+
rbs_repositoy.update([rule_based_segment], [], -1)
61+
matcher = described_class.new(rbs_repositoy, segments_repository, 'corge', config, evaluator)
62+
expect(matcher.match?({:matching_key => 'user', :attributes => {}})).to be false
63+
expect(matcher.match?({:matching_key => 'k1', :attributes => {}})).to be true
64+
end
65+
end
66+
end

0 commit comments

Comments
 (0)