Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/semver #532

Merged
merged 47 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2968a19
added undefined matcher flow
Mar 5, 2024
5cbe5dc
polish
Mar 5, 2024
c770cff
Merge pull request #510 from splitio/undefined-matcher
chillaq Mar 5, 2024
75cc222
added semver class
Mar 26, 2024
21f408b
polish
Mar 26, 2024
6b83afc
added equalto semver matcher
Mar 28, 2024
93d73b0
polish
Mar 28, 2024
45fbd0b
polish
Mar 28, 2024
8aa6bb9
added greater than or equal to semver matcher
Mar 28, 2024
30232f8
added less than or equal semver matcher
Mar 29, 2024
2f95e4b
added semver between matcher
Mar 29, 2024
2749c65
added in list semver matcher
Mar 29, 2024
7fd6f34
polish
Mar 29, 2024
e9677d9
fixed matcher logic
Apr 1, 2024
dc0caa6
fixed matcher logic
Apr 1, 2024
9f1b7d9
semver matchers integrations spec
Apr 1, 2024
8a1d214
Used csv for spec and added builder for semver class
Apr 8, 2024
f12ee2a
added csv files
Apr 8, 2024
acad113
updated using build for semver class
Apr 9, 2024
1e4a238
used build for semver class
Apr 9, 2024
8bf838b
used build for semver class
Apr 9, 2024
cf60f11
used build for semver class
Apr 9, 2024
61b8c9e
fixed condition
Apr 9, 2024
8cc7ff5
Merge pull request #514 from splitio/semver-class
chillaq Apr 10, 2024
f875912
Merge pull request #516 from splitio/semver-equalto-matcher
chillaq Apr 10, 2024
608c259
Merge pull request #517 from splitio/semver-greater-or-equalto-matcher
chillaq Apr 10, 2024
229a9f7
Merge pull request #518 from splitio/semver-less-or-equalto-matcher
chillaq Apr 10, 2024
89826a3
Merge pull request #519 from splitio/semver-between-matcher
chillaq Apr 10, 2024
f76b944
Merge pull request #521 from splitio/semver-integration-spec
chillaq Apr 10, 2024
980c272
added version attribute and used it in compare
Apr 10, 2024
b9e8fd8
updated semver spec
Apr 10, 2024
b1529d2
Fixed compare logic in semver, equalto and inlist matchers.
Apr 10, 2024
a8761d7
Added nil check for all matchers and updated specs
Apr 11, 2024
5a084a8
polish
Apr 11, 2024
a302223
Merge branch 'Feature/Semver' into semver-inlist-matcher
chillaq Apr 11, 2024
7ec5da7
added version length check for compare
Apr 11, 2024
4ecad51
correcting specs and comparisons
Apr 11, 2024
a1cab94
Merge pull request #525 from splitio/semver-inlist-matcher
chillaq Apr 11, 2024
0f5ac2f
Merge branch 'development' into Feature/Semver
chillaq May 1, 2024
db9311a
updated query api order, fixed spec and added error to split logger
May 1, 2024
418e5d7
added debug to split logger
May 1, 2024
b7ecdd1
update debug logging in semver matchers
May 1, 2024
1bc71a5
fixed spec
May 1, 2024
abea9d2
polishing
May 1, 2024
581d6e3
polish
May 1, 2024
50fe596
polish
May 2, 2024
2a54d08
polish and test update
May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
require 'splitclient-rb/engine/matchers/equal_to_boolean_matcher'
require 'splitclient-rb/engine/matchers/equal_to_matcher'
require 'splitclient-rb/engine/matchers/matches_string_matcher'
require 'splitclient-rb/engine/matchers/semver'
require 'splitclient-rb/engine/matchers/equal_to_semver_matcher'
require 'splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher'
require 'splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher'
require 'splitclient-rb/engine/matchers/between_semver_matcher'
require 'splitclient-rb/engine/matchers/in_list_semver_matcher'
require 'splitclient-rb/engine/evaluator/splitter'
require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
require 'splitclient-rb/engine/impressions/unique_keys_tracker'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class SplitsRepository < Repository
}
],
label: "unsupported matcher type"
chillaq marked this conversation as resolved.
Show resolved Hide resolved
}]
}]

def initialize(config, flag_sets_repository, flag_set_filter)
super(config)
@tt_cache = {}
Expand Down
2 changes: 1 addition & 1 deletion lib/splitclient-rb/engine/api/splits.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def since(since, fetch_options = { cache_control_headers: false, till: nil, sets
start = Time.now

params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since }
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
@config.logger.debug("Fetching from splitChanges with #{params}: ")
response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
if response.status == 414
Expand Down
33 changes: 33 additions & 0 deletions lib/splitclient-rb/engine/matchers/between_semver_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module SplitIoClient
class BetweenSemverMatcher < Matcher
MATCHER_TYPE = 'BETWEEN_SEMVER'

attr_reader :attribute

def initialize(attribute, start_value, end_value, logger, validator)
super(logger)
@validator = validator
@attribute = attribute
@semver_start = SplitIoClient::Semver.build(start_value, logger)
@semver_end = SplitIoClient::Semver.build(end_value, logger)
@logger = logger
end

def match?(args)
return false unless verify_semver_arg?(args, 'BetweenSemverMatcher')

value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
unless !value_to_match.nil? && !@semver_start.nil? && !@semver_end.nil?
chillaq marked this conversation as resolved.
Show resolved Hide resolved
@logger.error('betweenStringMatcherData is required for BETWEEN_SEMVER matcher type')
return false

chillaq marked this conversation as resolved.
Show resolved Hide resolved
end
matches = ([0, -1].include?(@semver_start.compare(value_to_match)) &&
[0, 1].include?(@semver_end.compare(value_to_match)))
@logger.debug("[BetweenMatcher] #{value_to_match} matches -> #{matches}")
matches
end
end
end
28 changes: 28 additions & 0 deletions lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module SplitIoClient
class EqualToSemverMatcher < Matcher
MATCHER_TYPE = 'EQUAL_TO_SEMVER'

attr_reader :attribute

def initialize(attribute, string_value, logger, validator)
super(logger)
@validator = validator
@attribute = attribute
@semver = SplitIoClient::Semver.build(string_value, logger)
@logger = logger
end

def match?(args)
return false unless verify_semver_arg?(args, 'EqualsToSemverMatcher')

value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)

matches = (@semver.version == value_to_match.version)
@logger.debug("[EqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
matches
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module SplitIoClient
class GreaterThanOrEqualToSemverMatcher < Matcher
MATCHER_TYPE = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'

attr_reader :attribute

def initialize(attribute, string_value, logger, validator)
super(logger)
@validator = validator
@attribute = attribute
@semver = SplitIoClient::Semver.build(string_value, logger)
@logger = logger
end

def match?(args)
return false unless verify_semver_arg?(args, 'GreaterThanOrEqualsToSemverMatcher')

value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)

matches = [0, 1].include?(value_to_match.compare(@semver))
@logger.debug("[GreaterThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
matches
end
end
end
30 changes: 30 additions & 0 deletions lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module SplitIoClient
class InListSemverMatcher < Matcher
MATCHER_TYPE = 'IN_LIST_SEMVER'

attr_reader :attribute

def initialize(attribute, list_value, logger, validator)
super(logger)
@validator = validator
@attribute = attribute
@semver_list = list_value.map { |item| SplitIoClient::Semver.build(item, logger) }
chillaq marked this conversation as resolved.
Show resolved Hide resolved
@logger = logger
end

def match?(args)
return false unless verify_semver_arg?(args, 'InListSemverMatcher')
chillaq marked this conversation as resolved.
Show resolved Hide resolved

value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
unless !value_to_match.nil? && @semver_list.all? { |n| !n.nil? }
chillaq marked this conversation as resolved.
Show resolved Hide resolved
@logger.error('whitelistMatcherData is required for IN_LIST_SEMVER matcher type')
return false
end
matches = (@semver_list.map { |item| item.version == value_to_match.version }).any? { |item| item == true }
@logger.debug("[InListSemverMatcher] #{value_to_match} matches -> #{matches}")
matches
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module SplitIoClient
class LessThanOrEqualToSemverMatcher < Matcher
MATCHER_TYPE = 'LESS_THAN_OR_EQUAL_TO_SEMVER'

attr_reader :attribute

def initialize(attribute, string_value, logger, validator)
super(logger)
@validator = validator
@attribute = attribute
@semver = SplitIoClient::Semver.build(string_value, logger)
@logger = logger
end

def match?(args)
return false unless verify_semver_arg?(args, 'LessThanOrEqualsToSemverMatcher')

value_to_match = SplitIoClient::Semver.build(args[:attributes][@attribute.to_sym], @logger)
return false unless check_semver_value_to_match(value_to_match, MATCHER_TYPE)

matches = [0, -1].include?(value_to_match.compare(@semver))
@logger.debug("[LessThanOrEqualsToSemverMatcher] #{value_to_match} matches -> #{matches}")
matches
end
end
end
18 changes: 18 additions & 0 deletions lib/splitclient-rb/engine/matchers/matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,23 @@ def equals?(obj)
def string_type?
false
end

private

def verify_semver_arg?(args, matcher_name)
@logger.debug("[#{matcher_name}] evaluating value and attributes.")
return false unless @validator.valid_matcher_arguments(args)

true
end

def check_semver_value_to_match(value_to_match, matcher_spec_name)
unless !value_to_match.nil? && [email protected]?
chillaq marked this conversation as resolved.
Show resolved Hide resolved
@logger.error("stringMatcherData is required for #{matcher_spec_name} matcher type")
return false

end
true
end
end
end
188 changes: 188 additions & 0 deletions lib/splitclient-rb/engine/matchers/semver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# frozen_string_literal: true

module SplitIoClient
class Semver
METADATA_DELIMITER = '+'
PRE_RELEASE_DELIMITER = '-'
VALUE_DELIMITER = '.'

attr_reader :major, :minor, :patch, :pre_release, :is_stable, :version

def initialize(version)
@major = 0
@minor = 0
@patch = 0
@pre_release = []
@is_stable = false
@version = ''
@metadata = ''
parse(version)
end

#
# Class builder
#
# @param version [String] raw version as read from splitChanges response.
#
# @return [type] Semver instance
def self.build(version, logger)
new(version)
rescue NoMethodError => e
logger.error("Failed to parse Semver data, incorrect data type: #{e}")
nil
rescue StandardError => e
logger.error("Failed to parse Semver data: #{e}")
nil
end

#
# Check if there is any metadata characters in version.
#
# @return [type] String semver without the metadata
#
def remove_metadata_if_exists(old_version)
index = old_version.index(METADATA_DELIMITER)
return old_version if index.nil?

@metadata = old_version[index + 1, old_version.length]
old_version[0, index]
end

# Compare the current Semver object to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare(to_compare)
return 0 if @version == to_compare.version

# Compare major, minor, and patch versions numerically
return compare_attributes(to_compare) if compare_attributes(to_compare) != 0
chillaq marked this conversation as resolved.
Show resolved Hide resolved

# Compare pre-release versions lexically
compare_pre_release(to_compare)
end

private

def integer?(value)
value.to_i.to_s == value
end

#
# Parse the string in version to update the other internal variables
#
def parse(old_version)
without_metadata = remove_metadata_if_exists(old_version)

index = without_metadata.index(PRE_RELEASE_DELIMITER)
if index.nil?
@is_stable = true
else
pre_release_data = without_metadata[index + 1..-1]
without_metadata = without_metadata[0, index]
@pre_release = pre_release_data.split(VALUE_DELIMITER)
end
assign_major_minor_and_patch(without_metadata)
end

#
# Set the major, minor and patch internal variables based on string passed.
#
# @param version [String] raw version containing major.minor.patch numbers.
def assign_major_minor_and_patch(version)
parts = version.split(VALUE_DELIMITER)
if parts.length != 3 ||
!(integer?(parts[0]) &&
integer?(parts[1]) &&
integer?(parts[2]))
raise "Unable to convert to Semver, incorrect format: #{version}"
end

@major = parts[0].to_i
@minor = parts[1].to_i
@patch = parts[2].to_i
@version = "#{@major}#{VALUE_DELIMITER}#{@minor}#{VALUE_DELIMITER}#{@patch}"
@version += "#{PRE_RELEASE_DELIMITER}#{@pre_release.join('.')}" unless @pre_release.empty?
chillaq marked this conversation as resolved.
Show resolved Hide resolved
@version += "#{METADATA_DELIMITER}#{@metadata}" unless @metadata.empty?
end

#
# Compare 2 variables and return int as follows:
# 0: if var1 == var2
# 1: if var1 > var2
# -1: if var1 < var2
#
# @param var1 [type] String/Integer object that accept ==, < or > operators
# @param var2 [type] String/Integer object that accept ==, < or > operators
#
# @returns [Integer] based on comparison
def compare_vars(var1, var2)
return 0 if var1 == var2

return 1 if var1 > var2

-1
end

# Compare the current Semver object's major, minor, patch and is_stable attributes to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare_attributes(to_compare)
result = compare_vars(@major, to_compare.major)
return result if result != 0

result = compare_vars(@minor, to_compare.minor)
return result if result != 0

result = compare_vars(@patch, to_compare.patch)
return result if result != 0

return -1 if !@is_stable && to_compare.is_stable

return 1 if @is_stable && !to_compare.is_stable

0
end

# Compare the current Semver object's pre_release attribute to a given Semver object, return:
# 0: if self == passed
# 1: if self > passed
# -1: if self < passed
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer] based on comparison
def compare_pre_release(to_compare)
min_length = get_pre_min_length(to_compare)
0.upto(min_length - 1) do |i|
next if @pre_release[i] == to_compare.pre_release[i]

if integer?(@pre_release[i]) && integer?(to_compare.pre_release[i])
return compare_vars(@pre_release[i].to_i, to_compare.pre_release[i].to_i)
end

return compare_vars(@pre_release[i], to_compare.pre_release[i])
end
# Compare lengths of pre-release versions
compare_vars(@pre_release.length, to_compare.pre_release.length)
end

# Get minimum of current Semver object's pre_release attributes length to a given Semver object
#
# @param to_compare [trype] splitio.models.grammar.matchers.semver.Semver object
#
# @returns [Integer]
def get_pre_min_length(to_compare)
[@pre_release.length, to_compare.pre_release.length].min
end
end
end
Loading
Loading