Skip to content

Commit 60dfef6

Browse files
committed
feat: introduced-jsonpath-library
1 parent 0f5250a commit 60dfef6

File tree

8 files changed

+14
-235
lines changed

8 files changed

+14
-235
lines changed

Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ PATH
44
flagsmith (4.3.0)
55
faraday (>= 2.0.1)
66
faraday-retry
7+
jsonpath (~> 1.1)
78
semantic
89

910
GEM
@@ -21,8 +22,11 @@ GEM
2122
faraday (~> 2.0)
2223
gem-release (2.2.2)
2324
json (2.7.1)
25+
jsonpath (1.1.5)
26+
multi_json
2427
language_server-protocol (3.17.0.3)
2528
method_source (1.0.0)
29+
multi_json (1.17.0)
2630
net-http (0.4.1)
2731
uri
2832
parallel (1.24.0)

flagsmith.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
3434

3535
spec.add_dependency 'faraday', '>= 2.0.1'
3636
spec.add_dependency 'faraday-retry'
37+
spec.add_dependency 'jsonpath', '~> 1.1'
3738
spec.add_dependency 'semantic'
3839
spec.metadata['rubygems_mfa_required'] = 'true'
3940
end

lib/flagsmith/engine/evaluation/core.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def evaluate_feature_value(feature, identity_key = nil)
122122
# Returns {value: any; reason?: string}
123123
def get_multivariate_feature_value(feature, identity_key)
124124
percentage_value = hashed_percentage_for_object_ids([feature[:key], identity_key])
125-
sorted_variants = (feature[:variants] || []).sort_by { |v| v[:priority] || Float::INFINITY }
125+
sorted_variants = (feature[:variants] || []).sort_by { |v| v[:priority] || WEAKEST_PRIORITY }
126126

127127
start_percentage = 0
128128
sorted_variants.each do |variant|
@@ -160,7 +160,7 @@ def get_identity_key(evaluation_context)
160160

161161
# returns boolean
162162
def higher_priority?(priority_a, priority_b)
163-
(priority_a || Float::INFINITY) < (priority_b || Float::INFINITY)
163+
(priority_a || WEAKEST_PRIORITY) < (priority_b || WEAKEST_PRIORITY)
164164
end
165165

166166
def get_targeting_match_reason(match_object)

lib/flagsmith/engine/features/models.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def multivariate_value(identity_id)
5858
# but `self` does.
5959
# 2. `other` have a feature segment with high priority
6060
def higher_segment_priority?(other)
61-
feature_segment.priority.to_i < (other&.feature_segment&.priority || Float::INFINITY)
61+
feature_segment.priority.to_i < (other&.feature_segment&.priority || WEAKEST_PRIORITY)
6262
rescue TypeError, NoMethodError
6363
false
6464
end

lib/flagsmith/engine/segments/evaluator.rb

Lines changed: 6 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'json'
4+
require 'jsonpath'
35
require_relative 'constants'
46
require_relative 'models'
57
require_relative '../utils/hash_func'
@@ -31,60 +33,7 @@ def get_identity_segments(context)
3133
matching_segments
3234
end
3335

34-
# Evaluates whether a given identity is in the provided segment.
35-
#
36-
# :param identity: identity model object to evaluate
37-
# :param segment: segment model object to evaluate
38-
# :param override_traits: pass in a list of traits to use instead of those on the
39-
# identity model itself
40-
# :return: True if the identity is in the segment, False otherwise
41-
def evaluate_identity_in_segment(identity, segment, override_traits = nil)
42-
segment.rules&.length&.positive? &&
43-
segment.rules.all? do |rule|
44-
traits_match_segment_rule(
45-
override_traits || identity.identity_traits,
46-
rule,
47-
segment.id,
48-
identity.django_id || identity.composite_key
49-
)
50-
end
51-
end
52-
53-
# rubocop:disable Metrics/MethodLength
54-
def traits_match_segment_rule(identity_traits, rule, segment_id, identity_id)
55-
matching_block = lambda { |condition|
56-
traits_match_segment_condition(identity_traits, condition, segment_id, identity_id)
57-
}
58-
59-
matches_conditions =
60-
if rule.conditions&.length&.positive?
61-
rule.conditions.send(rule.matching_function, &matching_block)
62-
else
63-
true
64-
end
65-
66-
matches_conditions &&
67-
rule.rules.all? { |r| traits_match_segment_rule(identity_traits, r, segment_id, identity_id) }
68-
end
69-
# rubocop:enable Metrics/MethodLength
70-
71-
def traits_match_segment_condition(identity_traits, condition, segment_id, identity_id)
72-
if condition.operator == PERCENTAGE_SPLIT
73-
return hashed_percentage_for_object_ids([segment_id,
74-
identity_id]) <= condition.value.to_f
75-
end
76-
77-
trait = identity_traits.find { |t| t.key.to_s == condition.property }
78-
79-
return handle_trait_existence_conditions(trait, condition.operator) if [IS_SET,
80-
IS_NOT_SET].include?(condition.operator)
81-
82-
return condition.match_trait_value?(trait.trait_value) if trait
83-
84-
false
85-
end
86-
87-
# Context-based helper functions (new approach)
36+
# Context-based helper functions
8837

8938
# Evaluates whether a segment rule matches using context
9039
#
@@ -143,9 +92,7 @@ def traits_match_segment_condition_from_context(condition, segment_key, context)
14392
end
14493

14594
return false if condition[:property].nil?
146-
14795
trait_value = get_trait_value(condition[:property], context)
148-
14996
return trait_value != nil if condition[:operator] == IS_SET
15097
return trait_value.nil? if condition[:operator] == IS_NOT_SET
15198

@@ -197,25 +144,15 @@ def get_trait_value(property, context)
197144
traits[property] || traits[property.to_sym]
198145
end
199146

200-
# Get value from context using JSONPath-like syntax
147+
# Get value from context using JSONPath syntax
201148
#
202149
# @param json_path [String] JSONPath expression (e.g., '$.identity.identifier')
203150
# @param context [Hash] The evaluation context
204151
# @return [Object, nil] The value at the path or nil
205152
def get_context_value(json_path, context)
206153
return nil unless context && json_path&.start_with?('$.')
207-
208-
# Simple JSONPath implementation - handle basic cases
209-
path_parts = json_path.sub('$.', '').split('.')
210-
current = context
211-
212-
path_parts.each do |part|
213-
return nil unless current.is_a?(Hash)
214-
215-
current = current[part.to_sym]
216-
end
217-
218-
current
154+
results = JsonPath.new(json_path, use_symbols: true).on(context)
155+
results.first
219156
rescue StandardError
220157
nil
221158
end
@@ -240,14 +177,6 @@ def non_primitive?(value)
240177

241178
value.is_a?(Hash) || value.is_a?(Array)
242179
end
243-
244-
private
245-
246-
def handle_trait_existence_conditions(matching_trait, operator)
247-
return operator == IS_NOT_SET if matching_trait.nil?
248-
249-
operator == IS_SET
250-
end
251180
end
252181
end
253182
end

lib/flagsmith/sdk/models/flags.rb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,18 +174,6 @@ def from_api(json_data, **args)
174174
)
175175
end
176176

177-
def from_feature_state_models(feature_states, identity_id: nil, **args)
178-
to_flag_object = lambda { |feature_state, acc|
179-
acc[normalize_key(feature_state.feature.name)] =
180-
Flagsmith::Flags::Flag.from_feature_state_model(feature_state, identity_id)
181-
}
182-
183-
new(
184-
feature_states.each_with_object({}, &to_flag_object),
185-
**args
186-
)
187-
end
188-
189177
def from_evaluation_result(evaluation_result, **args)
190178
to_flag_object = lambda { |flag_result, acc|
191179
flagsmith_id = flag_result.dig(:metadata, :flagsmith_id)

spec/engine/unit/core_spec.rb.old

Lines changed: 0 additions & 114 deletions
This file was deleted.

spec/engine/unit/segments/evaluator_spec.rb

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)