Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2aeb65a
feat: added-engine-function-signatures
Zaimwa9 Oct 24, 2025
ba057cb
feat: moved-engine-to-core
Zaimwa9 Oct 24, 2025
bb96eb1
feat: implemented-process-segment-overrides
Zaimwa9 Oct 24, 2025
a2fd5a8
feat: implemented-evalute-segments-partially
Zaimwa9 Oct 24, 2025
0f08fb1
feat: implemented-should-apply-override
Zaimwa9 Oct 24, 2025
da08e8e
feat: implemented-get-identity-segments
Zaimwa9 Oct 27, 2025
af51bf7
feat: implemented-new-in-and-fixed-remaining-tests
Zaimwa9 Oct 27, 2025
0fb7587
feat: run-lint
Zaimwa9 Oct 28, 2025
741ceeb
feat: rebased
Zaimwa9 Oct 28, 2025
6a6a129
feat: misc
Zaimwa9 Oct 28, 2025
3bad95b
Merge branch 'feat/evaluation-context-mappers' of github.com:Flagsmit…
Zaimwa9 Oct 30, 2025
f0a53b7
feat: json-path-lib-implementation
Zaimwa9 Oct 30, 2025
ef1274a
remove dup
gagantrivedi Nov 5, 2025
962c01c
feat: made-legacy-functions-public
Zaimwa9 Nov 10, 2025
771a1a1
feat: updated-tests-to-match-engine-in-operator-accepting-numbers
Zaimwa9 Nov 10, 2025
561cd71
feat: rebased
Zaimwa9 Nov 10, 2025
21d7e74
feat: engine-agnostic-to-empty-identity-in-segment-evaluation
Zaimwa9 Nov 10, 2025
0baa0b4
feat: renamed-to-is-higher-priority
Zaimwa9 Nov 10, 2025
97cfdfb
feat: renamed-get-identity-segments-func
Zaimwa9 Nov 10, 2025
7188b95
feat: reverted-to-is-primitive
Zaimwa9 Nov 10, 2025
418ae33
feat: use-weakest-priority-constant
Zaimwa9 Nov 10, 2025
12144a3
feat: upgraded-engine-test-data-and-fixed-mv-evaluation-bug
Zaimwa9 Nov 10, 2025
fe63b45
feat: removed-targeting-reason-func
Zaimwa9 Nov 10, 2025
6420d15
feat: linter-rubocop-autocorrect
Zaimwa9 Nov 10, 2025
cbbd3c2
feat: linter
Zaimwa9 Nov 10, 2025
3b729fc
feat: linter
Zaimwa9 Nov 10, 2025
7c15dd5
Merge branch 'feat/evaluation-context-mappers' of github.com:Flagsmit…
Zaimwa9 Nov 10, 2025
13057f9
feat: moved-mappers-to-engine-namespace
Zaimwa9 Nov 10, 2025
7cee757
feat: rebased
Zaimwa9 Nov 11, 2025
2f3850c
Merge branch 'feat/evaluation-context-mappers' of github.com:Flagsmit…
Zaimwa9 Nov 11, 2025
37c424f
feat: enrich-context-with-identity-key
Zaimwa9 Nov 11, 2025
29e6bf6
feat: run-ci-on-all-branches
Zaimwa9 Nov 11, 2025
080f191
feat: removed-comments
Zaimwa9 Nov 11, 2025
fc1369a
feat!: sdk consumes context engine (#89)
Zaimwa9 Nov 12, 2025
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
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ PATH
flagsmith (4.3.0)
faraday (>= 2.0.1)
faraday-retry
jsonpath (~> 1.1)
semantic

GEM
Expand All @@ -21,8 +22,11 @@ GEM
faraday (~> 2.0)
gem-release (2.2.2)
json (2.7.1)
jsonpath (1.1.5)
multi_json
language_server-protocol (3.17.0.3)
method_source (1.0.0)
multi_json (1.17.0)
net-http (0.4.1)
uri
parallel (1.24.0)
Expand Down
1 change: 1 addition & 0 deletions flagsmith.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Gem::Specification.new do |spec|

spec.add_dependency 'faraday', '>= 2.0.1'
spec.add_dependency 'faraday-retry'
spec.add_dependency 'jsonpath', '~> 1.1'
spec.add_dependency 'semantic'
spec.metadata['rubygems_mfa_required'] = 'true'
end
130 changes: 126 additions & 4 deletions lib/flagsmith/engine/evaluation/core.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,142 @@
# frozen_string_literal: true

require_relative '../utils/hash_func'
require_relative '../features/constants'
require_relative '../segments/evaluator'

module Flagsmith
module Engine
module Evaluation
# Core evaluation logic for feature flags
module Core
extend self
include Flagsmith::Engine::Utils::HashFunc
include Flagsmith::Engine::Features::TargetingReasons
include Flagsmith::Engine::Segments::Evaluator
# Get evaluation result from evaluation context
#
# @param evaluation_context [Hash] The evaluation context
# @return [Hash] Evaluation result with flags and segments
def self.get_evaluation_result(_evaluation_context)
# TODO: Implement core evaluation logic
# returns EvaluationResultWithMetadata
def get_evaluation_result(evaluation_context)
evaluation_context = get_enriched_context(evaluation_context)
segments, segment_overrides = evaluate_segments(evaluation_context)
flags = evaluate_features(evaluation_context, segment_overrides)
{
flags: {},
segments: []
flags: flags,
segments: segments
}
end

# Returns { segments: EvaluationResultSegments; segmentOverrides: Record<string, SegmentOverride>; }
def evaluate_segments(evaluation_context)
return [], {} if evaluation_context[:segments].nil?

identity_segments = get_segments_from_context(evaluation_context)

segments = identity_segments.map do |segment|
{ name: segment[:name], metadata: segment[:metadata] }.compact
end

segment_overrides = process_segment_overrides(identity_segments)

[segments, segment_overrides]
end

# Returns Record<string: override.name, SegmentOverride>
def process_segment_overrides(identity_segments) # rubocop:disable Metrics/MethodLength
segment_overrides = {}

identity_segments.each do |segment|
Array(segment[:overrides]).each do |override|
next unless should_apply_override(override, segment_overrides)

segment_overrides[override[:name]] = {
feature: override,
segment_name: segment[:name]
}
end
end

segment_overrides
end

# returns EvaluationResultFlags<Metadata>
def evaluate_features(evaluation_context, segment_overrides)
identity_key = get_identity_key(evaluation_context)

(evaluation_context[:features] || {}).each_with_object({}) do |(_, feature), flags|
segment_override = segment_overrides[feature[:name]]
final_feature = segment_override ? segment_override[:feature] : feature

flag_result = build_flag_result(final_feature, identity_key, segment_override)
flags[final_feature[:name].to_sym] = flag_result
end
end

# Returns {value: any; reason?: string}
def evaluate_feature_value(feature, identity_key = nil)
return get_multivariate_feature_value(feature, identity_key) if feature[:variants]&.any? && identity_key

{ value: feature[:value], reason: nil }
end

# Returns {value: any; reason?: string}
def get_multivariate_feature_value(feature, identity_key)
percentage_value = hashed_percentage_for_object_ids([feature[:key], identity_key])
sorted_variants = (feature[:variants] || []).sort_by { |v| v[:priority] || WEAKEST_PRIORITY }

variant = find_matching_variant(sorted_variants, percentage_value)
variant || { value: feature[:value], reason: nil }
end

def find_matching_variant(sorted_variants, percentage_value)
start_percentage = 0
sorted_variants.each do |variant|
limit = start_percentage + variant[:weight]
return { value: variant[:value], reason: "#{TARGETING_REASON_SPLIT}; weight=#{variant[:weight]}" } if start_percentage <= percentage_value && percentage_value < limit

start_percentage = limit
end
nil
end

# returns boolean
def should_apply_override(override, existing_overrides)
current_override = existing_overrides[override[:name]]
!current_override || stronger_priority?(override[:priority], current_override[:feature][:priority])
end

private

def build_flag_result(feature, identity_key, segment_override)
evaluated = evaluate_feature_value(feature, identity_key)

flag_result = {
name: feature[:name],
enabled: feature[:enabled],
value: evaluated[:value],
reason: evaluated[:reason] || (segment_override ? "#{TARGETING_REASON_TARGETING_MATCH}; segment=#{segment_override[:segment_name]}" : TARGETING_REASON_DEFAULT)
}

flag_result[:metadata] = feature[:metadata] if feature[:metadata]
flag_result
end

# Extract identity key from evaluation context
#
# @param evaluation_context [Hash] The evaluation context
# @return [String, nil] The identity key or nil if no identity
def get_identity_key(evaluation_context)
return nil unless evaluation_context[:identity]

evaluation_context[:identity][:key] ||
"#{evaluation_context[:environment][:key]}_#{evaluation_context[:identity][:identifier]}"
end

# returns boolean
def stronger_priority?(priority_a, priority_b)
(priority_a || WEAKEST_PRIORITY) < (priority_b || WEAKEST_PRIORITY)
end
end
end
Expand Down
14 changes: 14 additions & 0 deletions lib/flagsmith/engine/features/constants.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Flagsmith
module Engine
module Features
# Targeting reason constants for evaluation results
module TargetingReasons
TARGETING_REASON_DEFAULT = 'DEFAULT'
TARGETING_REASON_TARGETING_MATCH = 'TARGETING_MATCH'
TARGETING_REASON_SPLIT = 'SPLIT'
end
end
end
end
Loading