From fb2b0a7ebf01fca825fc9c71f1209843216ea882 Mon Sep 17 00:00:00 2001 From: Evandro Myller Date: Fri, 30 Oct 2026 10:49:19 -0300 Subject: [PATCH 1/2] fix: Stop raising when comparing a numeric trait to a rule value of the other numeric type Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/flagsmith/engine/segments/models.rb | 7 +++---- spec/engine/unit/segments/models_spec.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/flagsmith/engine/segments/models.rb b/lib/flagsmith/engine/segments/models.rb index 70a6d03..475a191 100644 --- a/lib/flagsmith/engine/segments/models.rb +++ b/lib/flagsmith/engine/segments/models.rb @@ -77,6 +77,8 @@ def match_with_type_conversion(trait_value) type_as_trait_value = format_to_type_of(trait_value) formatted_value = type_as_trait_value ? type_as_trait_value.call(@value) : @value MATCHING_FUNCTIONS[operator]&.call(trait_value, formatted_value) + rescue ArgumentError # Rule value not coercible to the trait value's type, e.g. 2 > '1.5' + false end TYPE_CONVERTERS = { @@ -88,10 +90,7 @@ def match_with_type_conversion(trait_value) i = v.to_i i.to_s == v.to_s ? i : v }, - 'Float' => lambda { |v| - f = v.to_f - f.to_s == v.to_s ? f : v - } + 'Float' => ->(v) { Float(v, exception: false) || v } }.freeze def format_to_type_of(input) diff --git a/spec/engine/unit/segments/models_spec.rb b/spec/engine/unit/segments/models_spec.rb index 1c50637..0c24465 100644 --- a/spec/engine/unit/segments/models_spec.rb +++ b/spec/engine/unit/segments/models_spec.rb @@ -13,6 +13,7 @@ ['EQUAL', true, 'false', false], ['EQUAL', 1.23, '1.23', true], ['EQUAL', 1.23, '4.56', false], + ['EQUAL', 1.0, '1', true], ['GREATER_THAN', nil, '1', false], ['GREATER_THAN', 2, '1', true], ['GREATER_THAN', 1, '1', false], @@ -20,6 +21,8 @@ ['GREATER_THAN', 2.1, '2.0', true], ['GREATER_THAN', 2.1, '2.1', false], ['GREATER_THAN', 2.0, '2.1', false], + ['GREATER_THAN', 2.1, '2', true], + ['GREATER_THAN', 2, '1.5', false], ['GREATER_THAN_INCLUSIVE', nil, '1', false], ['GREATER_THAN_INCLUSIVE', 2, '1', true], ['GREATER_THAN_INCLUSIVE', 1, '1', true], @@ -27,6 +30,8 @@ ['GREATER_THAN_INCLUSIVE', 2.1, '2.0', true], ['GREATER_THAN_INCLUSIVE', 2.1, '2.1', true], ['GREATER_THAN_INCLUSIVE', 2.0, '2.1', false], + ['GREATER_THAN_INCLUSIVE', 2.0, '2', true], + ['GREATER_THAN_INCLUSIVE', 2, '1.5', false], ['LESS_THAN', nil, '2', false], ['LESS_THAN', 1, '2', true], ['LESS_THAN', 1, '1', false], @@ -34,6 +39,8 @@ ['LESS_THAN', 2.0, '2.1', true], ['LESS_THAN', 2.1, '2.1', false], ['LESS_THAN', 2.1, '2.0', false], + ['LESS_THAN', 1.9, '2', true], + ['LESS_THAN', 1, '1.5', false], ['LESS_THAN_INCLUSIVE', nil, '2', false], ['LESS_THAN_INCLUSIVE', 1, '2', true], ['LESS_THAN_INCLUSIVE', 1, '1', true], @@ -41,6 +48,8 @@ ['LESS_THAN_INCLUSIVE', 2.0, '2.1', true], ['LESS_THAN_INCLUSIVE', 2.1, '2.1', true], ['LESS_THAN_INCLUSIVE', 2.1, '2.0', false], + ['LESS_THAN_INCLUSIVE', 2.0, '2', true], + ['LESS_THAN_INCLUSIVE', 1, '1.5', false], ['NOT_EQUAL', 'bar', 'baz', true], ['NOT_EQUAL', 'bar', 'bar', false], ['NOT_EQUAL', 1, '2', true], From 9eb23e2661e94e71f76fed1de0041ee05ec11caf Mon Sep 17 00:00:00 2001 From: Evandro Myller Date: Fri, 30 Oct 2026 11:25:59 -0300 Subject: [PATCH 2/2] fix: Don't match numeric traits against uncoercible rule values, nor raise on CONTAINS Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/flagsmith/engine/segments/models.rb | 11 ++++------- spec/engine/unit/segments/models_spec.rb | 5 +++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/flagsmith/engine/segments/models.rb b/lib/flagsmith/engine/segments/models.rb index 475a191..c5d8079 100644 --- a/lib/flagsmith/engine/segments/models.rb +++ b/lib/flagsmith/engine/segments/models.rb @@ -42,9 +42,9 @@ class Condition LESS_THAN => ->(other_value, self_value) { (other_value || false) && other_value < self_value }, LESS_THAN_INCLUSIVE => ->(other_value, self_value) { (other_value || false) && other_value <= self_value }, NOT_EQUAL => ->(other_value, self_value) { other_value != self_value }, - CONTAINS => ->(other_value, self_value) { (other_value || false) && other_value.include?(self_value) }, + CONTAINS => ->(other_value, self_value) { other_value.is_a?(String) && other_value.include?(self_value) }, - NOT_CONTAINS => ->(other_value, self_value) { (other_value || false) && !other_value.include?(self_value) }, + NOT_CONTAINS => ->(other_value, self_value) { other_value.is_a?(String) && !other_value.include?(self_value) }, REGEX => ->(other_value, self_value) { (other_value || false) && other_value.to_s.match?(self_value) } }.freeze @@ -86,11 +86,8 @@ def match_with_type_conversion(trait_value) 'Semantic::Version' => ->(v) { Semantic::Version.new(v.to_s.gsub(/:semver$/, '')) }, 'TrueClass' => ->(v) { ['True', 'true', 'TRUE', true, 1, '1'].include?(v) }, 'FalseClass' => ->(v) { !['False', 'false', 'FALSE', false].include?(v) }, - 'Integer' => lambda { |v| - i = v.to_i - i.to_s == v.to_s ? i : v - }, - 'Float' => ->(v) { Float(v, exception: false) || v } + 'Integer' => ->(v) { Integer(v.to_s, 10) }, + 'Float' => ->(v) { Float(v.to_s) } }.freeze def format_to_type_of(input) diff --git a/spec/engine/unit/segments/models_spec.rb b/spec/engine/unit/segments/models_spec.rb index 0c24465..c534e79 100644 --- a/spec/engine/unit/segments/models_spec.rb +++ b/spec/engine/unit/segments/models_spec.rb @@ -58,14 +58,19 @@ ['NOT_EQUAL', false, 'true', true], ['NOT_EQUAL', false, 'false', false], ['NOT_EQUAL', true, 'true', false], + ['NOT_EQUAL', 95, '90.5', false], + ['NOT_EQUAL', 1, 'foo', false], + ['NOT_EQUAL', 1.5, 'foo', false], ['CONTAINS', nil, 'b', false], ['CONTAINS', 'bar', 'b', true], ['CONTAINS', 'bar', 'bar', true], ['CONTAINS', 'bar', 'baz', false], + ['CONTAINS', 95, '9', false], ['NOT_CONTAINS', nil, 'b', false], ['NOT_CONTAINS', 'bar', 'b', false], ['NOT_CONTAINS', 'bar', 'bar', false], ['NOT_CONTAINS', 'bar', 'baz', true], + ['NOT_CONTAINS', 95, 'abc', false], ['REGEX', nil, /[a-z]+/, false], ['REGEX', 'foo', /[a-z]+/, true], ['REGEX', 'FOO', /[a-z]+/, false],