From bbcae5c849b5d29e81adcb678d370b98a5a8d907 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Tue, 1 Mar 2022 11:04:12 +0200 Subject: [PATCH 01/15] feat: implement advanced constraints --- lib/unleash/constraint.rb | 148 +++++++++++++++- lib/unleash/context.rb | 3 +- lib/unleash/feature_toggle.rb | 4 +- spec/unleash/constraint_spec.rb | 301 ++++++++++++++++++++++++++++++++ 4 files changed, 447 insertions(+), 9 deletions(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 03479138..b35b0512 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -1,26 +1,160 @@ +require 'date' + module Unleash class Constraint - attr_accessor :context_name, :operator, :values + attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive + + CONTAINS_OPERATORS = [ + 'IN', + 'NOT_IN' + ].freeze + + STRING_OPERATORS = [ + 'STR_STARTS_WITH', + 'STR_ENDS_WITH', + 'STR_CONTAINS' + ].freeze + + NUMERIC_OPERATORS = [ + 'NUM_EQ', + 'NUM_GT', + 'NUM_GTE', + 'NUM_LT', + 'NUM_LTE' + ].freeze + + DATE_OPERATORS = [ + 'DATE_AFTER', + 'DATE_BEFORE' + ].freeze - VALID_OPERATORS = ['IN', 'NOT_IN'].freeze + SEMVER_OPERATORS = [ + 'SEMVER_EQ', + 'SEMVER_GT', + 'SEMVER_LT' + ].freeze - def initialize(context_name, operator, values = []) + VALID_OPERATORS = [ + CONTAINS_OPERATORS, + STRING_OPERATORS, + NUMERIC_OPERATORS, + DATE_OPERATORS, + SEMVER_OPERATORS + ].flatten.freeze + + def initialize(context_name, operator, value = [], inverted = false, case_insensitive = false) raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String) raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator - raise ArgumentError, "values does not hold an Array" unless values.is_a?(Array) + raise ArgumentError, "value must either hold an array or a single string" unless value.is_a?(Array) || value.is_a?(String) self.context_name = context_name self.operator = operator - self.values = values + self.value = value + self.inverted = inverted + self.case_insensitive = case_insensitive end def matches_context?(context) - Unleash.logger.debug "Unleash::Constraint matches_context? values: #{self.values} context.get_by_name(#{self.context_name})" \ + Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})" \ " #{context.get_by_name(self.context_name)} " + match = matches_constraint?(context) + self.inverted ? !match : match + end - is_included = self.values.include? context.get_by_name(self.context_name) + private + def matches_constraint?(context) + if CONTAINS_OPERATORS.include? self.operator + matches_contains_operator?(context) + elsif STRING_OPERATORS.include? self.operator + matches_string_operator?(context) + elsif NUMERIC_OPERATORS.include? self.operator + matches_numeric_operator?(context) + elsif DATE_OPERATORS.include? self.operator + matches_date_operator?(context) + elsif SEMVER_OPERATORS.include? self.operator + matches_semver_operator?(context) + else + Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Defaulting to false" + false + end + end + + def matches_contains_operator?(context) + is_included = self.value.include? context.get_by_name(self.context_name) operator == 'IN' ? is_included : !is_included end + + def matches_string_operator?(context) + context_value = context.get_by_name(self.context_name) + constraint_value = self.value + if self.case_insensitive + constraint_value = constraint_value.map(&:upcase) + context_value = context_value.upcase + end + case self.operator + when "STR_STARTS_WITH" + constraint_value.any?{ |value| context_value.start_with? value } + when "STR_ENDS_WITH" + constraint_value.any?{ |value| context_value.end_with? value } + when "STR_CONTAINS" + constraint_value.any?{ |value| context_value.include? value } + end + end + + def matches_numeric_operator?(context) + begin + context_value = Float(context.get_by_name(self.context_name)) + constraint_value = Float(self.value) + rescue ArgumentError + false + end + + case self.operator + when "NUM_EQ" + (constraint_value - context_value).abs < 0.001 + when "NUM_LT" + constraint_value > context_value + when "NUM_LTE" + constraint_value >= context_value + when "NUM_GT" + constraint_value < context_value + when "NUM_GTE" + constraint_value <= context_value + end + end + + def matches_date_operator?(context) + begin + context_value = DateTime.parse(context.get_by_name(self.context_name)) + constraint_value = DateTime.parse(self.value) + rescue ArgumentError + false + end + case self.operator + when "DATE_AFTER" + constraint_value < context_value + when "DATE_BEFORE" + constraint_value > context_value + end + end + + def matches_semver_operator?(context) + begin + context_value = Gem::Version.new(context.get_by_name(self.context_name)) + constraint_value = Gem::Version.new(self.value) + rescue ArgumentError + false + end + + case self.operator + when "SEMVER_EQ" + constraint_value == context_value + when "SEMVER_GT" + constraint_value < context_value + when "SEMVER_LT" + constraint_value > context_value + end + end end end diff --git a/lib/unleash/context.rb b/lib/unleash/context.rb index 3b786758..e7b867d7 100644 --- a/lib/unleash/context.rb +++ b/lib/unleash/context.rb @@ -1,6 +1,6 @@ module Unleash class Context - ATTRS = [:app_name, :environment, :user_id, :session_id, :remote_address].freeze + ATTRS = [:app_name, :environment, :user_id, :session_id, :remote_address, :current_time].freeze attr_accessor(*[ATTRS, :properties].flatten) @@ -12,6 +12,7 @@ def initialize(params = {}) self.user_id = value_for('userId', params) self.session_id = value_for('sessionId', params) self.remote_address = value_for('remoteAddress', params) + self.current_time = value_for('currentTime', params, Time.now.utc.iso8601.to_s) properties = value_for('properties', params) self.properties = properties.is_a?(Hash) ? properties.transform_keys(&:to_sym) : {} diff --git a/lib/unleash/feature_toggle.rb b/lib/unleash/feature_toggle.rb index a3dc4ede..8ff614db 100644 --- a/lib/unleash/feature_toggle.rb +++ b/lib/unleash/feature_toggle.rb @@ -139,7 +139,9 @@ def initialize_strategies(params) Constraint.new( c.fetch('contextName'), c.fetch('operator'), - c.fetch('values') + c.fetch('values', nil) || c.fetch('value', nil), + c.fetch('inverted', false), + c.fetch('caseInsensitive', false) ) end ) diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index bd3b6ab6..9e58a648 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -72,5 +72,306 @@ constraint = Unleash::Constraint.new('user_id', 'NOT_IN', ['456', '789']) expect(constraint.matches_context?(context)).to be_truthy end + + it 'matches based on property STR_STARTS_WITH value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'development' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['development']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['ment']) + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property STR_ENDS_WITH value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'development' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['development']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['dev']) + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property STR_CONTAINS value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'development' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['ment']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['dev']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['development']) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['DEVELOPMENT']) + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property NUM_EQ value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.141' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'NUM_EQ', '3.141') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'NUM_EQ', '2.718') + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property NUM_LT value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.141' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'NUM_LT', '2.718') + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'NUM_LT', '3.141') + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'NUM_LT', '6.282') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on property NUM_LTE value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.141' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'NUM_LTE', '2.718') + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'NUM_LTE', '3.141') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'NUM_LTE', '6.282') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on property NUM_GT value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.141' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'NUM_GT', '2.718') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'NUM_GT', '3.141') + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'NUM_GT', '6.282') + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property NUM_GTE value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.141' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'NUM_GTE', '2.718') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'NUM_GTE', '3.141') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'NUM_GTE', '6.282') + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property SEMVER_EQ value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.1.41-beta' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'SEMVER_EQ', '3.1.41-beta') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on property SEMVER_GT value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.1.41-gamma' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'SEMVER_GT', '3.1.41-beta') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on property SEMVER_LT value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: '3.1.41-alpha' + } + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('env', 'SEMVER_LT', '3.1.41-beta') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on property DATE_AFTER value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + currentTime: '2022-01-30T13:00:00.000Z' + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-29T13:00:00.000Z') + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-31T13:00:00.000Z') + expect(constraint.matches_context?(context)).to be_falsey + end + + it 'matches based on property DATE_BEFORE value' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + currentTime: '2022-01-30T13:00:00.000Z' + } + context = Unleash::Context.new(context_params) + + constraint = Unleash::Constraint.new('currentTime', 'DATE_BEFORE', '2022-01-29T13:00:00.000Z') + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('currentTime', 'DATE_BEFORE', '2022-01-31T13:00:00.000Z') + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on case insensitive property when operator is uppercased' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'development' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['DEV'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['MENT'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['LOP'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on case insensitive property when context is uppercased' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'DEVELOPMENT' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['lop'], case_insensitive: true) + expect(constraint.matches_context?(context)).to be_truthy + end + + it 'matches based on inverted property' do + context_params = { + user_id: '123', + session_id: 'verylongsesssionid', + remote_address: '127.0.0.1', + properties: { + env: 'development' + } + } + context = Unleash::Context.new(context_params) + constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev'], inverted: true) + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment'], inverted: true) + expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['lop'], inverted: true) + expect(constraint.matches_context?(context)).to be_falsey + end end end From fd15d6f497c0383a14a5c0b3c817987dc5f5f798 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Tue, 1 Mar 2022 11:40:21 +0200 Subject: [PATCH 02/15] chore: tease constraint matchers out into their own modules for readability --- lib/unleash/constraint.rb | 99 +++---------------- .../constraints/contains_constraints.rb | 10 ++ lib/unleash/constraints/date_constraints.rb | 21 ++++ .../constraints/numeric_constraints.rb | 27 +++++ lib/unleash/constraints/semver_constraints.rb | 23 +++++ lib/unleash/constraints/string_constraints.rb | 20 ++++ 6 files changed, 114 insertions(+), 86 deletions(-) create mode 100644 lib/unleash/constraints/contains_constraints.rb create mode 100644 lib/unleash/constraints/date_constraints.rb create mode 100644 lib/unleash/constraints/numeric_constraints.rb create mode 100644 lib/unleash/constraints/semver_constraints.rb create mode 100644 lib/unleash/constraints/string_constraints.rb diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index b35b0512..1419a873 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -1,13 +1,15 @@ require 'date' +require 'unleash/constraints/contains_constraints' +require 'unleash/constraints/date_constraints' +require 'unleash/constraints/numeric_constraints' +require 'unleash/constraints/semver_constraints' +require 'unleash/constraints/string_constraints' module Unleash class Constraint attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive - CONTAINS_OPERATORS = [ - 'IN', - 'NOT_IN' - ].freeze + STRING_OPERATORS = [ 'STR_STARTS_WITH', @@ -64,97 +66,22 @@ def matches_context?(context) private def matches_constraint?(context) + context_value = context.get_by_name(self.context_name) + if CONTAINS_OPERATORS.include? self.operator - matches_contains_operator?(context) + ConstraintMatcher::ContainsConstraint.matches?(self.operator, context_value, self.value) elsif STRING_OPERATORS.include? self.operator - matches_string_operator?(context) + ConstraintMatcher::StringConstraint.matches?(self.operator, context_value, self.value, self.case_insensitive) elsif NUMERIC_OPERATORS.include? self.operator - matches_numeric_operator?(context) + ConstraintMatcher::NumericConstraint.matches?(self.operator, context_value, self.value) elsif DATE_OPERATORS.include? self.operator - matches_date_operator?(context) + ConstraintMatcher::DateConstraint.matches?(self.operator, context_value, self.value) elsif SEMVER_OPERATORS.include? self.operator - matches_semver_operator?(context) + ConstraintMatcher::SemverConstraint.matches?(self.operator, context_value, self.value) else Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Defaulting to false" false end end - - def matches_contains_operator?(context) - is_included = self.value.include? context.get_by_name(self.context_name) - operator == 'IN' ? is_included : !is_included - end - - def matches_string_operator?(context) - context_value = context.get_by_name(self.context_name) - constraint_value = self.value - if self.case_insensitive - constraint_value = constraint_value.map(&:upcase) - context_value = context_value.upcase - end - case self.operator - when "STR_STARTS_WITH" - constraint_value.any?{ |value| context_value.start_with? value } - when "STR_ENDS_WITH" - constraint_value.any?{ |value| context_value.end_with? value } - when "STR_CONTAINS" - constraint_value.any?{ |value| context_value.include? value } - end - end - - def matches_numeric_operator?(context) - begin - context_value = Float(context.get_by_name(self.context_name)) - constraint_value = Float(self.value) - rescue ArgumentError - false - end - - case self.operator - when "NUM_EQ" - (constraint_value - context_value).abs < 0.001 - when "NUM_LT" - constraint_value > context_value - when "NUM_LTE" - constraint_value >= context_value - when "NUM_GT" - constraint_value < context_value - when "NUM_GTE" - constraint_value <= context_value - end - end - - def matches_date_operator?(context) - begin - context_value = DateTime.parse(context.get_by_name(self.context_name)) - constraint_value = DateTime.parse(self.value) - rescue ArgumentError - false - end - case self.operator - when "DATE_AFTER" - constraint_value < context_value - when "DATE_BEFORE" - constraint_value > context_value - end - end - - def matches_semver_operator?(context) - begin - context_value = Gem::Version.new(context.get_by_name(self.context_name)) - constraint_value = Gem::Version.new(self.value) - rescue ArgumentError - false - end - - case self.operator - when "SEMVER_EQ" - constraint_value == context_value - when "SEMVER_GT" - constraint_value < context_value - when "SEMVER_LT" - constraint_value > context_value - end - end end end diff --git a/lib/unleash/constraints/contains_constraints.rb b/lib/unleash/constraints/contains_constraints.rb new file mode 100644 index 00000000..ae7f6307 --- /dev/null +++ b/lib/unleash/constraints/contains_constraints.rb @@ -0,0 +1,10 @@ +module Unleash + module ConstraintMatcher + class ContainsConstraint + def self.matches?(operator, context_value, constraint_value) + is_included = constraint_value.include? context_value + operator == 'IN' ? is_included : !is_included + end + end + end +end diff --git a/lib/unleash/constraints/date_constraints.rb b/lib/unleash/constraints/date_constraints.rb new file mode 100644 index 00000000..5b0bbf19 --- /dev/null +++ b/lib/unleash/constraints/date_constraints.rb @@ -0,0 +1,21 @@ +module Unleash + module ConstraintMatcher + class DateConstraint + def self.matches?(operator, context_value, constraint_value) + begin + context_value = DateTime.parse(context_value) + constraint_value = DateTime.parse(constraint_value) + rescue ArgumentError + false + end + + case operator + when "DATE_AFTER" + constraint_value < context_value + when "DATE_BEFORE" + constraint_value > context_value + end + end + end + end +end diff --git a/lib/unleash/constraints/numeric_constraints.rb b/lib/unleash/constraints/numeric_constraints.rb new file mode 100644 index 00000000..57797402 --- /dev/null +++ b/lib/unleash/constraints/numeric_constraints.rb @@ -0,0 +1,27 @@ +module Unleash + module ConstraintMatcher + class NumericConstraint + def self.matches?(operator, context_value, constraint_value) + begin + context_value = Float(context_value) + constraint_value = Float(constraint_value) + rescue ArgumentError + false + end + + case operator + when "NUM_EQ" + (constraint_value - context_value).abs < 0.001 + when "NUM_LT" + constraint_value > context_value + when "NUM_LTE" + constraint_value >= context_value + when "NUM_GT" + constraint_value < context_value + when "NUM_GTE" + constraint_value <= context_value + end + end + end + end +end diff --git a/lib/unleash/constraints/semver_constraints.rb b/lib/unleash/constraints/semver_constraints.rb new file mode 100644 index 00000000..8fb6153b --- /dev/null +++ b/lib/unleash/constraints/semver_constraints.rb @@ -0,0 +1,23 @@ +module Unleash + module ConstraintMatcher + class SemverConstraint + def self.matches?(operator, context_value, constraint_value) + begin + context_value = Gem::Version.new(context_value) + constraint_value = Gem::Version.new(constraint_value) + rescue ArgumentError + false + end + + case operator + when "SEMVER_EQ" + constraint_value == context_value + when "SEMVER_GT" + constraint_value < context_value + when "SEMVER_LT" + constraint_value > context_value + end + end + end + end +end diff --git a/lib/unleash/constraints/string_constraints.rb b/lib/unleash/constraints/string_constraints.rb new file mode 100644 index 00000000..40ab2047 --- /dev/null +++ b/lib/unleash/constraints/string_constraints.rb @@ -0,0 +1,20 @@ +module Unleash + module ConstraintMatcher + class StringConstraint + def self.matches?(operator, context_value, constraint_value, case_insensitive = false) + if case_insensitive + constraint_value = constraint_value.map(&:upcase) + context_value = context_value.upcase + end + case operator + when "STR_STARTS_WITH" + constraint_value.any?{ |value| context_value.start_with? value } + when "STR_ENDS_WITH" + constraint_value.any?{ |value| context_value.end_with? value } + when "STR_CONTAINS" + constraint_value.any?{ |value| context_value.include? value } + end + end + end + end +end From 82267fd3740f9c8457a5af5fdc38cefcf61f00ff Mon Sep 17 00:00:00 2001 From: sighphyre Date: Tue, 1 Mar 2022 11:59:52 +0200 Subject: [PATCH 03/15] chore: move constraint type constants into their own match classes --- lib/unleash/constraint.rb | 49 +++++-------------- .../constraints/contains_constraints.rb | 9 ++++ lib/unleash/constraints/date_constraints.rb | 9 ++++ .../constraints/numeric_constraints.rb | 12 +++++ lib/unleash/constraints/semver_constraints.rb | 10 ++++ lib/unleash/constraints/string_constraints.rb | 10 ++++ 6 files changed, 62 insertions(+), 37 deletions(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 1419a873..062badd0 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -9,39 +9,12 @@ module Unleash class Constraint attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive - - - STRING_OPERATORS = [ - 'STR_STARTS_WITH', - 'STR_ENDS_WITH', - 'STR_CONTAINS' - ].freeze - - NUMERIC_OPERATORS = [ - 'NUM_EQ', - 'NUM_GT', - 'NUM_GTE', - 'NUM_LT', - 'NUM_LTE' - ].freeze - - DATE_OPERATORS = [ - 'DATE_AFTER', - 'DATE_BEFORE' - ].freeze - - SEMVER_OPERATORS = [ - 'SEMVER_EQ', - 'SEMVER_GT', - 'SEMVER_LT' - ].freeze - VALID_OPERATORS = [ - CONTAINS_OPERATORS, - STRING_OPERATORS, - NUMERIC_OPERATORS, - DATE_OPERATORS, - SEMVER_OPERATORS + ConstraintMatcher::ContainsConstraint::OPERATORS, + ConstraintMatcher::StringConstraint::OPERATORS, + ConstraintMatcher::NumericConstraint::OPERATORS, + ConstraintMatcher::DateConstraint::OPERATORS, + ConstraintMatcher::SemverConstraint::OPERATORS ].flatten.freeze def initialize(context_name, operator, value = [], inverted = false, case_insensitive = false) @@ -65,23 +38,25 @@ def matches_context?(context) private + # rubocop:disable Metrics/AbcSize def matches_constraint?(context) context_value = context.get_by_name(self.context_name) - if CONTAINS_OPERATORS.include? self.operator + if ConstraintMatcher::ContainsConstraint.include? self.operator ConstraintMatcher::ContainsConstraint.matches?(self.operator, context_value, self.value) - elsif STRING_OPERATORS.include? self.operator + elsif ConstraintMatcher::StringConstraint.include? self.operator ConstraintMatcher::StringConstraint.matches?(self.operator, context_value, self.value, self.case_insensitive) - elsif NUMERIC_OPERATORS.include? self.operator + elsif ConstraintMatcher::NumericConstraint.include? self.operator ConstraintMatcher::NumericConstraint.matches?(self.operator, context_value, self.value) - elsif DATE_OPERATORS.include? self.operator + elsif ConstraintMatcher::DateConstraint.include? self.operator ConstraintMatcher::DateConstraint.matches?(self.operator, context_value, self.value) - elsif SEMVER_OPERATORS.include? self.operator + elsif ConstraintMatcher::SemverConstraint.include? self.operator ConstraintMatcher::SemverConstraint.matches?(self.operator, context_value, self.value) else Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Defaulting to false" false end end + # rubocop:enable Metrics/AbcSize end end diff --git a/lib/unleash/constraints/contains_constraints.rb b/lib/unleash/constraints/contains_constraints.rb index ae7f6307..9da6e464 100644 --- a/lib/unleash/constraints/contains_constraints.rb +++ b/lib/unleash/constraints/contains_constraints.rb @@ -1,10 +1,19 @@ module Unleash module ConstraintMatcher class ContainsConstraint + OPERATORS = [ + 'IN', + 'NOT_IN' + ].freeze + def self.matches?(operator, context_value, constraint_value) is_included = constraint_value.include? context_value operator == 'IN' ? is_included : !is_included end + + def self.include?(operator) + OPERATORS.include? operator + end end end end diff --git a/lib/unleash/constraints/date_constraints.rb b/lib/unleash/constraints/date_constraints.rb index 5b0bbf19..02198dfc 100644 --- a/lib/unleash/constraints/date_constraints.rb +++ b/lib/unleash/constraints/date_constraints.rb @@ -1,6 +1,11 @@ module Unleash module ConstraintMatcher class DateConstraint + OPERATORS = [ + 'DATE_AFTER', + 'DATE_BEFORE' + ].freeze + def self.matches?(operator, context_value, constraint_value) begin context_value = DateTime.parse(context_value) @@ -16,6 +21,10 @@ def self.matches?(operator, context_value, constraint_value) constraint_value > context_value end end + + def self.include?(operator) + OPERATORS.include? operator + end end end end diff --git a/lib/unleash/constraints/numeric_constraints.rb b/lib/unleash/constraints/numeric_constraints.rb index 57797402..37ff0674 100644 --- a/lib/unleash/constraints/numeric_constraints.rb +++ b/lib/unleash/constraints/numeric_constraints.rb @@ -1,6 +1,14 @@ module Unleash module ConstraintMatcher class NumericConstraint + OPERATORS = [ + 'NUM_EQ', + 'NUM_GT', + 'NUM_GTE', + 'NUM_LT', + 'NUM_LTE' + ].freeze + def self.matches?(operator, context_value, constraint_value) begin context_value = Float(context_value) @@ -22,6 +30,10 @@ def self.matches?(operator, context_value, constraint_value) constraint_value <= context_value end end + + def self.include?(operator) + OPERATORS.include? operator + end end end end diff --git a/lib/unleash/constraints/semver_constraints.rb b/lib/unleash/constraints/semver_constraints.rb index 8fb6153b..71a40e87 100644 --- a/lib/unleash/constraints/semver_constraints.rb +++ b/lib/unleash/constraints/semver_constraints.rb @@ -1,6 +1,12 @@ module Unleash module ConstraintMatcher class SemverConstraint + OPERATORS = [ + 'SEMVER_EQ', + 'SEMVER_GT', + 'SEMVER_LT' + ].freeze + def self.matches?(operator, context_value, constraint_value) begin context_value = Gem::Version.new(context_value) @@ -18,6 +24,10 @@ def self.matches?(operator, context_value, constraint_value) constraint_value > context_value end end + + def self.include?(operator) + OPERATORS.include? operator + end end end end diff --git a/lib/unleash/constraints/string_constraints.rb b/lib/unleash/constraints/string_constraints.rb index 40ab2047..00ef67c9 100644 --- a/lib/unleash/constraints/string_constraints.rb +++ b/lib/unleash/constraints/string_constraints.rb @@ -1,6 +1,12 @@ module Unleash module ConstraintMatcher class StringConstraint + OPERATORS = [ + 'STR_STARTS_WITH', + 'STR_ENDS_WITH', + 'STR_CONTAINS' + ].freeze + def self.matches?(operator, context_value, constraint_value, case_insensitive = false) if case_insensitive constraint_value = constraint_value.map(&:upcase) @@ -15,6 +21,10 @@ def self.matches?(operator, context_value, constraint_value, case_insensitive = constraint_value.any?{ |value| context_value.include? value } end end + + def self.include?(operator) + OPERATORS.include? operator + end end end end From f292fea182bb07aa4be06c68e29bbb7004b00d12 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Tue, 1 Mar 2022 13:25:46 +0200 Subject: [PATCH 04/15] chore: point spec tests to v4.1.0 for advanced constraint spec test cases --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c18b00c0..534b97bc 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -33,7 +33,7 @@ jobs: - name: Install dependencies run: bundle install - name: Download test cases - run: git clone --depth 5 --branch v4.0.0 https://github.com/Unleash/client-specification.git client-specification + run: git clone --depth 5 --branch v4.1.0 https://github.com/Unleash/client-specification.git client-specification - name: rubocop uses: reviewdog/action-rubocop@v2 with: From be08c16ad375d55d09386e0115697e3563f3e786 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Tue, 1 Mar 2022 13:32:28 +0200 Subject: [PATCH 05/15] chore: change case sensitive parameters to use keyword args instead --- lib/unleash/constraint.rb | 4 ++-- lib/unleash/constraints/string_constraints.rb | 2 +- lib/unleash/feature_toggle.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 062badd0..89b9eac6 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -17,7 +17,7 @@ class Constraint ConstraintMatcher::SemverConstraint::OPERATORS ].flatten.freeze - def initialize(context_name, operator, value = [], inverted = false, case_insensitive = false) + def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false) raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String) raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator raise ArgumentError, "value must either hold an array or a single string" unless value.is_a?(Array) || value.is_a?(String) @@ -45,7 +45,7 @@ def matches_constraint?(context) if ConstraintMatcher::ContainsConstraint.include? self.operator ConstraintMatcher::ContainsConstraint.matches?(self.operator, context_value, self.value) elsif ConstraintMatcher::StringConstraint.include? self.operator - ConstraintMatcher::StringConstraint.matches?(self.operator, context_value, self.value, self.case_insensitive) + ConstraintMatcher::StringConstraint.matches?(self.operator, context_value, self.value, case_insensitive: self.case_insensitive) elsif ConstraintMatcher::NumericConstraint.include? self.operator ConstraintMatcher::NumericConstraint.matches?(self.operator, context_value, self.value) elsif ConstraintMatcher::DateConstraint.include? self.operator diff --git a/lib/unleash/constraints/string_constraints.rb b/lib/unleash/constraints/string_constraints.rb index 00ef67c9..ffbc734a 100644 --- a/lib/unleash/constraints/string_constraints.rb +++ b/lib/unleash/constraints/string_constraints.rb @@ -7,7 +7,7 @@ class StringConstraint 'STR_CONTAINS' ].freeze - def self.matches?(operator, context_value, constraint_value, case_insensitive = false) + def self.matches?(operator, context_value, constraint_value, case_insensitive: false) if case_insensitive constraint_value = constraint_value.map(&:upcase) context_value = context_value.upcase diff --git a/lib/unleash/feature_toggle.rb b/lib/unleash/feature_toggle.rb index 8ff614db..6c2ff01a 100644 --- a/lib/unleash/feature_toggle.rb +++ b/lib/unleash/feature_toggle.rb @@ -140,8 +140,8 @@ def initialize_strategies(params) c.fetch('contextName'), c.fetch('operator'), c.fetch('values', nil) || c.fetch('value', nil), - c.fetch('inverted', false), - c.fetch('caseInsensitive', false) + inverted: c.fetch('inverted', false), + case_insensitive: c.fetch('caseInsensitive', false) ) end ) From 377021b47728b28232c74b65eb8b847fd729c256 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Wed, 2 Mar 2022 09:20:11 +0200 Subject: [PATCH 06/15] Update spec/unleash/constraint_spec.rb Co-authored-by: Renato Arruda --- spec/unleash/constraint_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index 9e58a648..2daf125c 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -75,9 +75,6 @@ it 'matches based on property STR_STARTS_WITH value' do context_params = { - user_id: '123', - session_id: 'verylongsesssionid', - remote_address: '127.0.0.1', properties: { env: 'development' } From 22374230c047efcb1bfa35e2a235398e50680561 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Thu, 3 Mar 2022 09:16:49 +0200 Subject: [PATCH 07/15] Update lib/unleash/constraint.rb Co-authored-by: Renato Arruda --- lib/unleash/constraint.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 89b9eac6..fa36cd45 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -25,8 +25,8 @@ def initialize(context_name, operator, value = [], inverted: false, case_insensi self.context_name = context_name self.operator = operator self.value = value - self.inverted = inverted - self.case_insensitive = case_insensitive + self.inverted = !!inverted + self.case_insensitive = !!case_insensitive end def matches_context?(context) From 706d24c154bbfa58f26cb17feefe3a146aaf7db8 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 13:05:47 +0200 Subject: [PATCH 08/15] Update spec/unleash/constraint_spec.rb Co-authored-by: Renato Arruda --- spec/unleash/constraint_spec.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index 2daf125c..0f6c36ee 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -289,7 +289,28 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-29T13:00:00.000Z') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-29T13:00:00Z') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-29T13:00Z') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59.999999Z') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59.999Z') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59') + expect(constraint.matches_context?(context)).to be true + + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T13:00:00.000Z') + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-31T13:00:00.000Z') expect(constraint.matches_context?(context)).to be_falsey From 81f0da407d2843410685d24edf295699a28e9788 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 13:06:30 +0200 Subject: [PATCH 09/15] feat: refactor advanced constraints to use lambdas over classes --- lib/unleash/constraint.rb | 90 ++++++++++++------- .../constraints/contains_constraints.rb | 19 ---- lib/unleash/constraints/date_constraints.rb | 30 ------- .../constraints/numeric_constraints.rb | 39 -------- lib/unleash/constraints/semver_constraints.rb | 33 ------- lib/unleash/constraints/string_constraints.rb | 30 ------- 6 files changed, 59 insertions(+), 182 deletions(-) delete mode 100644 lib/unleash/constraints/contains_constraints.rb delete mode 100644 lib/unleash/constraints/date_constraints.rb delete mode 100644 lib/unleash/constraints/numeric_constraints.rb delete mode 100644 lib/unleash/constraints/semver_constraints.rb delete mode 100644 lib/unleash/constraints/string_constraints.rb diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index fa36cd45..2bf83ba8 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -1,29 +1,33 @@ require 'date' -require 'unleash/constraints/contains_constraints' -require 'unleash/constraints/date_constraints' -require 'unleash/constraints/numeric_constraints' -require 'unleash/constraints/semver_constraints' -require 'unleash/constraints/string_constraints' module Unleash class Constraint attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive - VALID_OPERATORS = [ - ConstraintMatcher::ContainsConstraint::OPERATORS, - ConstraintMatcher::StringConstraint::OPERATORS, - ConstraintMatcher::NumericConstraint::OPERATORS, - ConstraintMatcher::DateConstraint::OPERATORS, - ConstraintMatcher::SemverConstraint::OPERATORS - ].flatten.freeze - + OPERATORS = { + IN: ->(context_value, constraint_value){ constraint_value.include? context_value }, + NOT_IN: ->(context_value, constraint_value){ !constraint_value.include? context_value }, + STR_STARTS_WITH: ->(context_value, constraint_value){ constraint_value.any?{ |v| context_value.start_with? v } }, + STR_ENDS_WITH: ->(context_value, constraint_value){ constraint_value.any?{ |v| context_value.end_with? v } }, + STR_CONTAINS: ->(context_value, constraint_value){ constraint_value.any?{ |v| context_value.include? v } }, + NUM_EQ: ->(context_value, constraint_value){ on_valid_float(constraint_value, context_value){ |x, y| (x - y).abs < Float::EPSILON } }, + NUM_LT: ->(context_value, constraint_value){ on_valid_float(constraint_value, context_value){ |x, y| (x > y) } }, + NUM_LTE: ->(context_value, constraint_value){ on_valid_float(constraint_value, context_value){ |x, y| (x >= y) } }, + NUM_GT: ->(context_value, constraint_value){ on_valid_float(constraint_value, context_value){ |x, y| (x < y) } }, + NUM_GTE: ->(context_value, constraint_value){ on_valid_float(constraint_value, context_value){ |x, y| (x <= y) } }, + DATE_AFTER: ->(context_value, constraint_value){ on_valid_date(constraint_value, context_value){ |x, y| (x < y) } }, + DATE_BEFORE: ->(context_value, constraint_value){ on_valid_date(constraint_value, context_value){ |x, y| (x > y) } }, + SEMVER_EQ: ->(context_value, constraint_value){ on_valid_version(constraint_value, context_value){ |x, y| (x == y) } }, + SEMVER_GT: ->(context_value, constraint_value){ on_valid_version(constraint_value, context_value){ |x, y| (x < y) } }, + SEMVER_LT: ->(context_value, constraint_value){ on_valid_version(constraint_value, context_value){ |x, y| (x > y) } } + }.freeze def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false) raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String) - raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator + raise ArgumentError, "operator does not hold a valid value:" + OPERATORS.keys unless OPERATORS.include? operator.to_sym raise ArgumentError, "value must either hold an array or a single string" unless value.is_a?(Array) || value.is_a?(String) self.context_name = context_name - self.operator = operator + self.operator = operator.to_sym self.value = value self.inverted = !!inverted self.case_insensitive = !!case_insensitive @@ -36,27 +40,51 @@ def matches_context?(context) self.inverted ? !match : match end + def self.on_valid_date(val1, val2) + val1 = DateTime.parse(val1) + val2 = DateTime.parse(val2) + yield(val1, val2) + rescue ArgumentError + Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \ + or constraint_value (#{val2}) into a date. This will always return false." + false + end + + def self.on_valid_float(val1, val2) + val1 = Float(val1) + val2 = Float(val2) + yield(val1, val2) + rescue ArgumentError + Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \ + or constraint_value (#{val2}) into a number. This will always return false." + false + end + + def self.on_valid_version(val1, val2) + val1 = Gem::Version.new(val1) + val2 = Gem::Version.new(val2) + yield(val1, val2) + rescue ArgumentError + Unleash.logger.warn "Unleash::ConstraintMatcher unable to parse either context_value (#{val1}) \ + or constraint_value (#{val2}) into a version. This will always return false." + false + end + private - # rubocop:disable Metrics/AbcSize def matches_constraint?(context) - context_value = context.get_by_name(self.context_name) - - if ConstraintMatcher::ContainsConstraint.include? self.operator - ConstraintMatcher::ContainsConstraint.matches?(self.operator, context_value, self.value) - elsif ConstraintMatcher::StringConstraint.include? self.operator - ConstraintMatcher::StringConstraint.matches?(self.operator, context_value, self.value, case_insensitive: self.case_insensitive) - elsif ConstraintMatcher::NumericConstraint.include? self.operator - ConstraintMatcher::NumericConstraint.matches?(self.operator, context_value, self.value) - elsif ConstraintMatcher::DateConstraint.include? self.operator - ConstraintMatcher::DateConstraint.matches?(self.operator, context_value, self.value) - elsif ConstraintMatcher::SemverConstraint.include? self.operator - ConstraintMatcher::SemverConstraint.matches?(self.operator, context_value, self.value) - else - Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Defaulting to false" + unless OPERATORS.include?(self.operator) + Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false." false end + + v = self.value.dup + context_value = context.get_by_name(self.context_name) + + v.map!(&:upcase) if self.case_insensitive + context_value.upcase! if self.case_insensitive + + OPERATORS[self.operator].call(context_value, v) end - # rubocop:enable Metrics/AbcSize end end diff --git a/lib/unleash/constraints/contains_constraints.rb b/lib/unleash/constraints/contains_constraints.rb deleted file mode 100644 index 9da6e464..00000000 --- a/lib/unleash/constraints/contains_constraints.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Unleash - module ConstraintMatcher - class ContainsConstraint - OPERATORS = [ - 'IN', - 'NOT_IN' - ].freeze - - def self.matches?(operator, context_value, constraint_value) - is_included = constraint_value.include? context_value - operator == 'IN' ? is_included : !is_included - end - - def self.include?(operator) - OPERATORS.include? operator - end - end - end -end diff --git a/lib/unleash/constraints/date_constraints.rb b/lib/unleash/constraints/date_constraints.rb deleted file mode 100644 index 02198dfc..00000000 --- a/lib/unleash/constraints/date_constraints.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Unleash - module ConstraintMatcher - class DateConstraint - OPERATORS = [ - 'DATE_AFTER', - 'DATE_BEFORE' - ].freeze - - def self.matches?(operator, context_value, constraint_value) - begin - context_value = DateTime.parse(context_value) - constraint_value = DateTime.parse(constraint_value) - rescue ArgumentError - false - end - - case operator - when "DATE_AFTER" - constraint_value < context_value - when "DATE_BEFORE" - constraint_value > context_value - end - end - - def self.include?(operator) - OPERATORS.include? operator - end - end - end -end diff --git a/lib/unleash/constraints/numeric_constraints.rb b/lib/unleash/constraints/numeric_constraints.rb deleted file mode 100644 index 37ff0674..00000000 --- a/lib/unleash/constraints/numeric_constraints.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Unleash - module ConstraintMatcher - class NumericConstraint - OPERATORS = [ - 'NUM_EQ', - 'NUM_GT', - 'NUM_GTE', - 'NUM_LT', - 'NUM_LTE' - ].freeze - - def self.matches?(operator, context_value, constraint_value) - begin - context_value = Float(context_value) - constraint_value = Float(constraint_value) - rescue ArgumentError - false - end - - case operator - when "NUM_EQ" - (constraint_value - context_value).abs < 0.001 - when "NUM_LT" - constraint_value > context_value - when "NUM_LTE" - constraint_value >= context_value - when "NUM_GT" - constraint_value < context_value - when "NUM_GTE" - constraint_value <= context_value - end - end - - def self.include?(operator) - OPERATORS.include? operator - end - end - end -end diff --git a/lib/unleash/constraints/semver_constraints.rb b/lib/unleash/constraints/semver_constraints.rb deleted file mode 100644 index 71a40e87..00000000 --- a/lib/unleash/constraints/semver_constraints.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Unleash - module ConstraintMatcher - class SemverConstraint - OPERATORS = [ - 'SEMVER_EQ', - 'SEMVER_GT', - 'SEMVER_LT' - ].freeze - - def self.matches?(operator, context_value, constraint_value) - begin - context_value = Gem::Version.new(context_value) - constraint_value = Gem::Version.new(constraint_value) - rescue ArgumentError - false - end - - case operator - when "SEMVER_EQ" - constraint_value == context_value - when "SEMVER_GT" - constraint_value < context_value - when "SEMVER_LT" - constraint_value > context_value - end - end - - def self.include?(operator) - OPERATORS.include? operator - end - end - end -end diff --git a/lib/unleash/constraints/string_constraints.rb b/lib/unleash/constraints/string_constraints.rb deleted file mode 100644 index ffbc734a..00000000 --- a/lib/unleash/constraints/string_constraints.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Unleash - module ConstraintMatcher - class StringConstraint - OPERATORS = [ - 'STR_STARTS_WITH', - 'STR_ENDS_WITH', - 'STR_CONTAINS' - ].freeze - - def self.matches?(operator, context_value, constraint_value, case_insensitive: false) - if case_insensitive - constraint_value = constraint_value.map(&:upcase) - context_value = context_value.upcase - end - case operator - when "STR_STARTS_WITH" - constraint_value.any?{ |value| context_value.start_with? value } - when "STR_ENDS_WITH" - constraint_value.any?{ |value| context_value.end_with? value } - when "STR_CONTAINS" - constraint_value.any?{ |value| context_value.include? value } - end - end - - def self.include?(operator) - OPERATORS.include? operator - end - end - end -end From c0db9f3827251c6de69d91168b9ef594b88549ec Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 13:13:42 +0200 Subject: [PATCH 10/15] feat: add test case for 0.1 + 0.2 == 0.3 for numeq constraint --- spec/unleash/constraint_spec.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index 0f6c36ee..17596f10 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -139,15 +139,18 @@ session_id: 'verylongsesssionid', remote_address: '127.0.0.1', properties: { - env: '3.141' + env: '0.3' } } context = Unleash::Context.new(context_params) - constraint = Unleash::Constraint.new('env', 'NUM_EQ', '3.141') + constraint = Unleash::Constraint.new('env', 'NUM_EQ', '0.3') expect(constraint.matches_context?(context)).to be_truthy - constraint = Unleash::Constraint.new('env', 'NUM_EQ', '2.718') + constraint = Unleash::Constraint.new('env', 'NUM_EQ', '0.2') expect(constraint.matches_context?(context)).to be_falsey + + constraint = Unleash::Constraint.new('env', 'NUM_EQ', (0.1 + 0.2).to_s) + expect(constraint.matches_context?(context)).to be_truthy end it 'matches based on property NUM_LT value' do @@ -296,19 +299,19 @@ constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-29T13:00Z') expect(constraint.matches_context?(context)).to be true - + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59.999999Z') expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59.999Z') expect(constraint.matches_context?(context)).to be true - + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59:59') expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T12:59') expect(constraint.matches_context?(context)).to be true - + constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-30T13:00:00.000Z') expect(constraint.matches_context?(context)).to be false From 8e713aa9041333f65d503c8336fc4f1098298e0a Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 13:14:31 +0200 Subject: [PATCH 11/15] chore: move constraint tests to use booleans over truthy values --- spec/unleash/constraint_spec.rb | 102 ++++++++++++++++---------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index 17596f10..21c7d6db 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -18,16 +18,16 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'IN', ['dev']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'IN', ['dev', 'pre']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NOT_IN', ['dev', 'pre']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NOT_IN', ['pre', 'prod']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property NOT_IN value' do @@ -41,13 +41,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NOT_IN', ['dev']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NOT_IN', ['dev', 'pre']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NOT_IN', ['pre', 'prod']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on user_id IN/NOT_IN user_id' do @@ -61,16 +61,16 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('user_id', 'IN', ['123', '456']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('user_id', 'IN', ['456', '789']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('user_id', 'NOT_IN', ['123', '456']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('user_id', 'NOT_IN', ['456', '789']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property STR_STARTS_WITH value' do @@ -81,13 +81,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['development']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['ment']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property STR_ENDS_WITH value' do @@ -101,13 +101,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['development']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['dev']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property STR_CONTAINS value' do @@ -121,16 +121,16 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['ment']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['dev']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['development']) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['DEVELOPMENT']) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property NUM_EQ value' do @@ -144,13 +144,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NUM_EQ', '0.3') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NUM_EQ', '0.2') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NUM_EQ', (0.1 + 0.2).to_s) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property NUM_LT value' do @@ -165,13 +165,13 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NUM_LT', '2.718') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NUM_LT', '3.141') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NUM_LT', '6.282') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property NUM_LTE value' do @@ -186,13 +186,13 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NUM_LTE', '2.718') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NUM_LTE', '3.141') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NUM_LTE', '6.282') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property NUM_GT value' do @@ -207,13 +207,13 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NUM_GT', '2.718') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NUM_GT', '3.141') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'NUM_GT', '6.282') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property NUM_GTE value' do @@ -228,13 +228,13 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'NUM_GTE', '2.718') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NUM_GTE', '3.141') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'NUM_GTE', '6.282') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property SEMVER_EQ value' do @@ -249,7 +249,7 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'SEMVER_EQ', '3.1.41-beta') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property SEMVER_GT value' do @@ -264,7 +264,7 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'SEMVER_GT', '3.1.41-beta') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property SEMVER_LT value' do @@ -279,7 +279,7 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'SEMVER_LT', '3.1.41-beta') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on property DATE_AFTER value' do @@ -316,7 +316,7 @@ expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('currentTime', 'DATE_AFTER', '2022-01-31T13:00:00.000Z') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end it 'matches based on property DATE_BEFORE value' do @@ -329,10 +329,10 @@ context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('currentTime', 'DATE_BEFORE', '2022-01-29T13:00:00.000Z') - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('currentTime', 'DATE_BEFORE', '2022-01-31T13:00:00.000Z') - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on case insensitive property when operator is uppercased' do @@ -346,13 +346,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['DEV'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['MENT'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['LOP'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on case insensitive property when context is uppercased' do @@ -366,13 +366,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['lop'], case_insensitive: true) - expect(constraint.matches_context?(context)).to be_truthy + expect(constraint.matches_context?(context)).to be true end it 'matches based on inverted property' do @@ -386,13 +386,13 @@ } context = Unleash::Context.new(context_params) constraint = Unleash::Constraint.new('env', 'STR_STARTS_WITH', ['dev'], inverted: true) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'STR_ENDS_WITH', ['ment'], inverted: true) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['lop'], inverted: true) - expect(constraint.matches_context?(context)).to be_falsey + expect(constraint.matches_context?(context)).to be false end end end From a707212bc173d59a1a1d7ecd1067e058cb9c495f Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 13:47:57 +0200 Subject: [PATCH 12/15] feat: add validation for op value type for advanced constraints --- lib/unleash/constraint.rb | 11 ++++++++++- spec/unleash/constraint_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 2bf83ba8..51fb2775 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -21,10 +21,14 @@ class Constraint SEMVER_GT: ->(context_value, constraint_value){ on_valid_version(constraint_value, context_value){ |x, y| (x < y) } }, SEMVER_LT: ->(context_value, constraint_value){ on_valid_version(constraint_value, context_value){ |x, y| (x > y) } } }.freeze + + VALID_LIST_TYPES = ["IN", "NOT_IN", "STR_STARTS_WITH", "STR_ENDS_WITH", "STR_CONTAINS"].map(&:to_sym).freeze + def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false) raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String) raise ArgumentError, "operator does not hold a valid value:" + OPERATORS.keys unless OPERATORS.include? operator.to_sym - raise ArgumentError, "value must either hold an array or a single string" unless value.is_a?(Array) || value.is_a?(String) + + self.validate_constraint_value_type(operator.to_sym, value) self.context_name = context_name self.operator = operator.to_sym @@ -72,6 +76,11 @@ def self.on_valid_version(val1, val2) private + def validate_constraint_value_type(operator, value) + raise ArgumentError, "context_name is not an Array" if VALID_LIST_TYPES.include?(operator) && value.is_a?(String) + raise ArgumentError, "context_name is not a String" if !VALID_LIST_TYPES.include?(operator) && value.is_a?(Array) + end + def matches_constraint?(context) unless OPERATORS.include?(self.operator) Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false." diff --git a/spec/unleash/constraint_spec.rb b/spec/unleash/constraint_spec.rb index 21c7d6db..36cb0c47 100644 --- a/spec/unleash/constraint_spec.rb +++ b/spec/unleash/constraint_spec.rb @@ -394,5 +394,25 @@ constraint = Unleash::Constraint.new('env', 'STR_CONTAINS', ['lop'], inverted: true) expect(constraint.matches_context?(context)).to be false end + + it 'rejects constraint construction for invalid value types for operator' do + array_constraints = ['STR_CONTAINS', 'STR_ENDS_WITH', 'STR_STARTS_WITH', 'IN', 'NOT_IN'] + + array_constraints.each do |operator_name| + Unleash::Constraint.new('env', operator_name, []) + expect do + Unleash::Constraint.new('env', operator_name, '') + end.to raise_error + end + + string_constraints = ['NUM_EQ', 'NUM_GT', 'NUM_GTE', 'NUM_LT', 'NUM_LTE', + 'DATE_AFTER', 'DATE_BEFORE', 'SEMVER_EQ', 'SEMVER_GT', 'SEMVER_LT'] + string_constraints.each do |operator_name| + Unleash::Constraint.new('env', operator_name, '') + expect do + Unleash::Constraint.new('env', operator_name, []) + end.to raise_error + end + end end end From 139197e96323a67e883980ed176a0b44973f28b6 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 14:30:24 +0200 Subject: [PATCH 13/15] fix: cover context datetime default with tests --- spec/unleash/context_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/unleash/context_spec.rb b/spec/unleash/context_spec.rb index e9e28db6..79a444bf 100644 --- a/spec/unleash/context_spec.rb +++ b/spec/unleash/context_spec.rb @@ -134,4 +134,21 @@ expect(context.get_by_name('CountryCode')).to eq('UK') expect(context.get_by_name(:CountryCode)).to eq('UK') end + + it "creates default date for current time if not populated" do + params = {} + context = Unleash::Context.new(params) + + expect(context.get_by_name(:CurrentTime)).not_to eq(nil) + end + + it "creates doesn't create a default date if passed in" do + date = DateTime.now + params = { + 'currentTime': date + } + context = Unleash::Context.new(params) + + expect(context.get_by_name(:CurrentTime).to_s).to eq(date.to_s) + end end From ffc366bcf86e66029ed8f04aa724f02eb475d958 Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 14:35:30 +0200 Subject: [PATCH 14/15] move constraint validation out of private methods because ruby 2.5 doesn't like this --- lib/unleash/constraint.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index 51fb2775..db6eb1be 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -74,13 +74,13 @@ def self.on_valid_version(val1, val2) false end - private - def validate_constraint_value_type(operator, value) raise ArgumentError, "context_name is not an Array" if VALID_LIST_TYPES.include?(operator) && value.is_a?(String) raise ArgumentError, "context_name is not a String" if !VALID_LIST_TYPES.include?(operator) && value.is_a?(Array) end + private + def matches_constraint?(context) unless OPERATORS.include?(self.operator) Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false." From 06f3775d7924162ffa2e2085adda528feee173be Mon Sep 17 00:00:00 2001 From: sighphyre Date: Mon, 7 Mar 2022 14:48:01 +0200 Subject: [PATCH 15/15] chore: comment reason for not making validation for constraints private --- lib/unleash/constraint.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/unleash/constraint.rb b/lib/unleash/constraint.rb index db6eb1be..6fd0c539 100644 --- a/lib/unleash/constraint.rb +++ b/lib/unleash/constraint.rb @@ -74,6 +74,7 @@ def self.on_valid_version(val1, val2) false end + # This should be a private method but for some reason this fails on Ruby 2.5 def validate_constraint_value_type(operator, value) raise ArgumentError, "context_name is not an Array" if VALID_LIST_TYPES.include?(operator) && value.is_a?(String) raise ArgumentError, "context_name is not a String" if !VALID_LIST_TYPES.include?(operator) && value.is_a?(Array)