module SplitIoClient module Engine module Parser class Evaluator def initialize(segments_repository, splits_repository, multiple = false) @splits_repository = splits_repository @segments_repository = segments_repository @multiple = multiple @cache = {} end def call(keys, split, attributes = nil) # DependencyMatcher here, split is actually a split_name in this case cache_result = split.is_a? String split = @splits_repository.get_split(split) if cache_result digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{split}#{attributes}") if Models::Split.archived?(split) return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, split[:changeNumber]) end treatment = if Models::Split.matchable?(split) if @multiple && @cache[digest] @cache[digest] else match(split, keys, attributes) end else treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber]) end @cache[digest] = treatment if cache_result treatment end private def match(split, keys, attributes) in_rollout = false key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key] legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false splitter = Splitter.new split[:conditions].each do |c| condition = SplitIoClient::Condition.new(c) next if condition.empty? if !in_rollout && condition.type == SplitIoClient::Condition::TYPE_ROLLOUT if split[:trafficAllocation] < 100 bucket = splitter.bucket(splitter.count_hash(key, split[:trafficAllocationSeed].to_i, legacy_algo)) if bucket >= split[:trafficAllocation] return treatment_hash(Models::Label::NOT_IN_SPLIT, split[:defaultTreatment], split[:changeNumber]) end end in_rollout = true end condition_matched = matcher_type(condition).match?( matching_key: keys[:matching_key], bucketing_key: keys[:bucketing_key], evaluator: self, attributes: attributes ) next unless condition_matched result = splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo]) if result.nil? return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber]) else return treatment_hash(c[:label], result, split[:changeNumber]) end end treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber]) end def matcher_type(condition) matchers = [] @segments_repository.adapter.pipelined do condition.matchers.each do |matcher| matchers << if matcher[:negate] condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, matcher)) else matcher_instance(matcher[:matcherType], condition, matcher) end end end final_matcher = condition.create_condition_matcher(matchers) if final_matcher.nil? @logger.error('Invalid matcher type') else final_matcher end end def treatment_hash(label, treatment, change_number = nil) { label: label, treatment: treatment, change_number: change_number } end def matcher_instance(type, condition, matcher) condition.send( "matcher_#{type.downcase}", matcher: matcher, segments_repository: @segments_repository ) end end end end end