Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

Commit

Permalink
add logic to support semver comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
vpai committed Mar 6, 2024
1 parent cd026c2 commit e36d250
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
48 changes: 46 additions & 2 deletions lib/eppo_client/rules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ def evaluate_condition(subject_attributes, condition)
when OperatorType::NOT_ONE_OF
!condition.value.map(&:downcase).include?(subject_value.to_s.downcase)
else
subject_value.is_a?(Numeric) && evaluate_numeric_condition(subject_value, condition)
# Numeric operator: value could be numeric or semver.
if subject_value.is_a?(Numeric)
evaluate_numeric_condition(subject_value, condition)
elsif valid_semver?(subject_value)
compare_semver(subject_value, condition.value, condition.operator)
end
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
Expand All @@ -82,6 +87,45 @@ def evaluate_numeric_condition(subject_value, condition)
end
# rubocop:enable Metrics/MethodLength

# rubocop:disable Metrics/MethodLength
def compare_semver(attribute_value, condition_value, operator)

Check notice

Code scanning / Rubocop

A complexity metric that is strongly correlated to the number of test cases needed to validate a method. Note

Metrics/CyclomaticComplexity: Cyclomatic complexity for compare_semver is too high. [7/6]
unless valid_semver?(attribute_value) && valid_semver?(condition_value)
return false
end

case operator
when OperatorType::GT
Gem::Version.new(attribute_value) > Gem::Version.new(condition_value)
when OperatorType::GTE
Gem::Version.new(attribute_value) >= Gem::Version.new(condition_value)
when OperatorType::LT
Gem::Version.new(attribute_value) < Gem::Version.new(condition_value)
when OperatorType::LTE
Gem::Version.new(attribute_value) <= Gem::Version.new(condition_value)
else
false
end
end
# rubocop:enable Metrics/MethodLength

# The semver format follows MAJOR.MINOR.PATCH, optionally including pre-release

Check notice

Code scanning / Rubocop

Limit lines to 80 characters. Note

Layout/LineLength: Line is too long. [81/80]
# and build metadata.
#
# Valid semver strings examples (method returns `true`):
# - Basic SemVer: "2.0.0"
# - Pre-release Version: "1.0.0-alpha"
# - Pre-release w/ Multiple Identifiers: "1.0.0-alpha.1"
# - Build Metadata: "1.0.0+20130313144700"
# - Pre-release & Build Metadata: "1.0.0-rc.1+build.1"
#
# Invalid examples (method returns `false`):
# - "1.0", "01.0.0", "1.0.0.0", "1.0b.0"
# - "1.0.0 beta", "1.0.0$", "1.0.0+build@", ".1.0", ""
def valid_semver?(string)
semver_regex = /\A\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?\z/
!string.match(semver_regex).nil?
end

module_function :find_matching_rule, :matches_rule, :evaluate_condition,
:evaluate_numeric_condition
:evaluate_numeric_condition, :valid_semver?, :compare_semver
end
2 changes: 1 addition & 1 deletion lib/eppo_client/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module EppoClient
VERSION = '0.2.3'
VERSION = '0.2.4'
end
19 changes: 19 additions & 0 deletions spec/rules_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
rule_with_empty_conditions = EppoClient::Rule.new(allocation_key: 'allocation', conditions: [])

# rubocop:disable Metrics/BlockLength
# rubocop:disable Layout/LineLength
describe EppoClient::Rule do
it 'tests find_matching_rule_when_no_rules_match' do
subject_attributes = { 'age' => 20, 'country' => 'US' }
Expand Down Expand Up @@ -44,6 +45,23 @@
expect(EppoClient.find_matching_rule({ 'age' => '99' }, [numeric_rule])).to be_nil
end

it 'tests find matching rule for semver string' do
semver_greater_than_condition = EppoClient::Condition.new(
operator: EppoClient::OperatorType::GTE, value: '1.0.0', attribute: 'version'
)
semver_less_than_condition = EppoClient::Condition.new(
operator: EppoClient::OperatorType::LTE, value: '2.0.0', attribute: 'version'
)
semver_rule = EppoClient::Rule.new(
allocation_key: 'allocation',
conditions: [semver_less_than_condition, semver_greater_than_condition]
)

expect(EppoClient.find_matching_rule({ 'version' => '1.1.0' }, [semver_rule])).to be(semver_rule)
expect(EppoClient.find_matching_rule({ 'version' => '2.0.0' }, [semver_rule])).to be(semver_rule)
expect(EppoClient.find_matching_rule({ 'version' => '2.1.0' }, [semver_rule])).to be_nil
end

it 'tests find matching rule with numeric value and regex' do
condition = EppoClient::Condition.new(
operator: EppoClient::OperatorType::MATCHES, value: '[0-9]+', attribute: 'age'
Expand Down Expand Up @@ -138,3 +156,4 @@
end
end
# rubocop:enable Metrics/BlockLength
# rubocop:disable Layout/LineLength

Check warning

Code scanning / Rubocop

Checks for a `# rubocop:enable` after `# rubocop:disable`. Warning test

Lint/MissingCopEnableDirective: Re-enable Layout/LineLength cop with # rubocop:enable after disabling it.

Check warning

Code scanning / Rubocop

Checks for rubocop:disable comments that can be removed. Note: this cop is not disabled when disabling all cops. It must be explicitly disabled. Warning test

Lint/RedundantCopDisableDirective: Unnecessary disabling of Layout/LineLength.

0 comments on commit e36d250

Please sign in to comment.