launchdarkly-server-sdk 6.3.1 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,17 @@
1
1
  require "ldclient-rb/evaluation_detail"
2
2
  require "ldclient-rb/impl/evaluator_bucketing"
3
+ require "ldclient-rb/impl/evaluator_helpers"
3
4
  require "ldclient-rb/impl/evaluator_operators"
4
5
 
5
6
  module LaunchDarkly
6
7
  module Impl
8
+ # Used internally to record that we evaluated a prerequisite flag.
9
+ PrerequisiteEvalRecord = Struct.new(
10
+ :prereq_flag, # the prerequisite flag that we evaluated
11
+ :prereq_of_flag, # the flag that it was a prerequisite of
12
+ :detail # the EvaluationDetail representing the evaluation result
13
+ )
14
+
7
15
  # Encapsulates the feature flag evaluation logic. The Evaluator has no knowledge of the rest of the SDK environment;
8
16
  # if it needs to retrieve flags or segments that are referenced by a flag, it does so through a simple function that
9
17
  # is provided in the constructor. It also produces feature requests as appropriate for any referenced prerequisite
@@ -22,7 +30,7 @@ module LaunchDarkly
22
30
  @get_big_segments_membership = get_big_segments_membership
23
31
  @logger = logger
24
32
  end
25
-
33
+
26
34
  # Used internally to hold an evaluation result and additional state that may be accumulated during an
27
35
  # evaluation. It's simpler and a bit more efficient to represent these as mutable properties rather than
28
36
  # trying to use a pure functional approach, and since we're not exposing this object to any application code
@@ -34,7 +42,7 @@ module LaunchDarkly
34
42
  # evaluation.
35
43
  EvalResult = Struct.new(
36
44
  :detail, # the EvaluationDetail representing the evaluation result
37
- :events, # an array of evaluation events generated by prerequisites, or nil
45
+ :prereq_evals, # an array of PrerequisiteEvalRecord instances, or nil
38
46
  :big_segments_status,
39
47
  :big_segments_membership
40
48
  )
@@ -50,17 +58,15 @@ module LaunchDarkly
50
58
  #
51
59
  # @param flag [Object] the flag
52
60
  # @param user [Object] the user properties
53
- # @param event_factory [EventFactory] called to construct a feature request event when a prerequisite flag is
54
- # evaluated; the caller is responsible for constructing the feature event for the top-level evaluation
55
61
  # @return [EvalResult] the evaluation result
56
- def evaluate(flag, user, event_factory)
62
+ def evaluate(flag, user)
57
63
  result = EvalResult.new
58
64
  if user.nil? || user[:key].nil?
59
65
  result.detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED)
60
66
  return result
61
67
  end
62
68
 
63
- detail = eval_internal(flag, user, result, event_factory)
69
+ detail = eval_internal(flag, user, result)
64
70
  if !result.big_segments_status.nil?
65
71
  # If big_segments_status is non-nil at the end of the evaluation, it means a query was done at
66
72
  # some point and we will want to include the status in the evaluation reason.
@@ -80,21 +86,19 @@ module LaunchDarkly
80
86
 
81
87
  private
82
88
 
83
- def eval_internal(flag, user, state, event_factory)
89
+ def eval_internal(flag, user, state)
84
90
  if !flag[:on]
85
- return get_off_value(flag, EvaluationReason::off)
91
+ return EvaluatorHelpers.off_result(flag)
86
92
  end
87
93
 
88
- prereq_failure_reason = check_prerequisites(flag, user, state, event_factory)
89
- if !prereq_failure_reason.nil?
90
- return get_off_value(flag, prereq_failure_reason)
91
- end
94
+ prereq_failure_result = check_prerequisites(flag, user, state)
95
+ return prereq_failure_result if !prereq_failure_result.nil?
92
96
 
93
97
  # Check user target matches
94
98
  (flag[:targets] || []).each do |target|
95
99
  (target[:values] || []).each do |value|
96
100
  if value == user[:key]
97
- return get_variation(flag, target[:variation], EvaluationReason::target_match)
101
+ return EvaluatorHelpers.target_match_result(target, flag)
98
102
  end
99
103
  end
100
104
  end
@@ -106,19 +110,21 @@ module LaunchDarkly
106
110
  if rule_match_user(rule, user, state)
107
111
  reason = rule[:_reason] # try to use cached reason for this rule
108
112
  reason = EvaluationReason::rule_match(i, rule[:id]) if reason.nil?
109
- return get_value_for_variation_or_rollout(flag, rule, user, reason)
113
+ return get_value_for_variation_or_rollout(flag, rule, user, reason,
114
+ EvaluatorHelpers.rule_precomputed_results(rule))
110
115
  end
111
116
  end
112
117
 
113
118
  # Check the fallthrough rule
114
119
  if !flag[:fallthrough].nil?
115
- return get_value_for_variation_or_rollout(flag, flag[:fallthrough], user, EvaluationReason::fallthrough)
120
+ return get_value_for_variation_or_rollout(flag, flag[:fallthrough], user, EvaluationReason::fallthrough,
121
+ EvaluatorHelpers.fallthrough_precomputed_results(flag))
116
122
  end
117
123
 
118
124
  return EvaluationDetail.new(nil, nil, EvaluationReason::fallthrough)
119
125
  end
120
126
 
121
- def check_prerequisites(flag, user, state, event_factory)
127
+ def check_prerequisites(flag, user, state)
122
128
  (flag[:prerequisites] || []).each do |prerequisite|
123
129
  prereq_ok = true
124
130
  prereq_key = prerequisite[:key]
@@ -129,23 +135,22 @@ module LaunchDarkly
129
135
  prereq_ok = false
130
136
  else
131
137
  begin
132
- prereq_res = eval_internal(prereq_flag, user, state, event_factory)
138
+ prereq_res = eval_internal(prereq_flag, user, state)
133
139
  # Note that if the prerequisite flag is off, we don't consider it a match no matter what its
134
140
  # off variation was. But we still need to evaluate it in order to generate an event.
135
141
  if !prereq_flag[:on] || prereq_res.variation_index != prerequisite[:variation]
136
142
  prereq_ok = false
137
143
  end
138
- event = event_factory.new_eval_event(prereq_flag, user, prereq_res, nil, flag)
139
- state.events = [] if state.events.nil?
140
- state.events.push(event)
144
+ prereq_eval = PrerequisiteEvalRecord.new(prereq_flag, flag, prereq_res)
145
+ state.prereq_evals = [] if state.prereq_evals.nil?
146
+ state.prereq_evals.push(prereq_eval)
141
147
  rescue => exn
142
148
  Util.log_exception(@logger, "Error evaluating prerequisite flag \"#{prereq_key}\" for flag \"#{flag[:key]}\"", exn)
143
149
  prereq_ok = false
144
150
  end
145
151
  end
146
152
  if !prereq_ok
147
- reason = prerequisite[:_reason] # try to use cached reason
148
- return reason.nil? ? EvaluationReason::prerequisite_failed(prereq_key) : reason
153
+ return EvaluatorHelpers.prerequisite_failed_result(prerequisite, flag)
149
154
  end
150
155
  end
151
156
  nil
@@ -248,35 +253,26 @@ module LaunchDarkly
248
253
  end
249
254
 
250
255
  private
251
-
252
- def get_variation(flag, index, reason)
253
- if index < 0 || index >= flag[:variations].length
254
- @logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index")
255
- return Evaluator.error_result(EvaluationReason::ERROR_MALFORMED_FLAG)
256
- end
257
- EvaluationDetail.new(flag[:variations][index], index, reason)
258
- end
259
-
260
- def get_off_value(flag, reason)
261
- if flag[:offVariation].nil? # off variation unspecified - return default value
262
- return EvaluationDetail.new(nil, nil, reason)
263
- end
264
- get_variation(flag, flag[:offVariation], reason)
265
- end
266
-
267
- def get_value_for_variation_or_rollout(flag, vr, user, reason)
256
+
257
+ def get_value_for_variation_or_rollout(flag, vr, user, reason, precomputed_results)
268
258
  index, in_experiment = EvaluatorBucketing.variation_index_for_user(flag, vr, user)
269
- #if in experiment is true, set reason to a different reason instance/singleton with in_experiment set
270
- if in_experiment && reason.kind == :FALLTHROUGH
271
- reason = EvaluationReason::fallthrough(in_experiment)
272
- elsif in_experiment && reason.kind == :RULE_MATCH
273
- reason = EvaluationReason::rule_match(reason.rule_index, reason.rule_id, in_experiment)
274
- end
275
259
  if index.nil?
276
260
  @logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": variation/rollout object with no variation or rollout")
277
261
  return Evaluator.error_result(EvaluationReason::ERROR_MALFORMED_FLAG)
278
262
  end
279
- return get_variation(flag, index, reason)
263
+ if precomputed_results
264
+ return precomputed_results.for_variation(index, in_experiment)
265
+ else
266
+ #if in experiment is true, set reason to a different reason instance/singleton with in_experiment set
267
+ if in_experiment
268
+ if reason.kind == :FALLTHROUGH
269
+ reason = EvaluationReason::fallthrough(in_experiment)
270
+ elsif reason.kind == :RULE_MATCH
271
+ reason = EvaluationReason::rule_match(reason.rule_index, reason.rule_id, in_experiment)
272
+ end
273
+ end
274
+ return EvaluatorHelpers.evaluation_detail_for_variation(flag, index, reason)
275
+ end
280
276
  end
281
277
  end
282
278
  end
@@ -0,0 +1,53 @@
1
+ require "ldclient-rb/evaluation_detail"
2
+
3
+ # This file contains any pieces of low-level evaluation logic that don't need to be inside the Evaluator
4
+ # class, because they don't depend on any SDK state outside of their input parameters.
5
+
6
+ module LaunchDarkly
7
+ module Impl
8
+ module EvaluatorHelpers
9
+ def self.off_result(flag, logger = nil)
10
+ pre = flag[:_preprocessed]
11
+ pre ? pre.off_result : evaluation_detail_for_off_variation(flag, EvaluationReason::off, logger)
12
+ end
13
+
14
+ def self.target_match_result(target, flag, logger = nil)
15
+ pre = target[:_preprocessed]
16
+ pre ? pre.match_result : evaluation_detail_for_variation(
17
+ flag, target[:variation], EvaluationReason::target_match, logger)
18
+ end
19
+
20
+ def self.prerequisite_failed_result(prereq, flag, logger = nil)
21
+ pre = prereq[:_preprocessed]
22
+ pre ? pre.failed_result : evaluation_detail_for_off_variation(
23
+ flag, EvaluationReason::prerequisite_failed(prereq[:key]), logger
24
+ )
25
+ end
26
+
27
+ def self.fallthrough_precomputed_results(flag)
28
+ pre = flag[:_preprocessed]
29
+ pre ? pre.fallthrough_factory : nil
30
+ end
31
+
32
+ def self.rule_precomputed_results(rule)
33
+ pre = rule[:_preprocessed]
34
+ pre ? pre.all_match_results : nil
35
+ end
36
+
37
+ def self.evaluation_detail_for_off_variation(flag, reason, logger = nil)
38
+ index = flag[:offVariation]
39
+ index.nil? ? EvaluationDetail.new(nil, nil, reason) : evaluation_detail_for_variation(flag, index, reason, logger)
40
+ end
41
+
42
+ def self.evaluation_detail_for_variation(flag, index, reason, logger = nil)
43
+ vars = flag[:variations] || []
44
+ if index < 0 || index >= vars.length
45
+ logger.error("[LDClient] Data inconsistency in feature flag \"#{flag[:key]}\": invalid variation index") unless logger.nil?
46
+ EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
47
+ else
48
+ EvaluationDetail.new(vars[index], index, reason)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -89,7 +89,7 @@ module LaunchDarkly
89
89
 
90
90
  private
91
91
 
92
- BUILTINS = Set[:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
92
+ BUILTINS = Set[:key, :secondary, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
93
93
  NUMERIC_VERSION_COMPONENTS_REGEX = Regexp.new("^[0-9.]*")
94
94
 
95
95
  private_constant :BUILTINS
@@ -0,0 +1,63 @@
1
+ require "ldclient-rb/impl/event_types"
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ EventSummary = Struct.new(:start_date, :end_date, :counters)
6
+
7
+ EventSummaryFlagInfo = Struct.new(:default, :versions)
8
+
9
+ EventSummaryFlagVariationCounter = Struct.new(:value, :count)
10
+
11
+ # Manages the state of summarizable information for the EventProcessor, including the
12
+ # event counters and user deduplication. Note that the methods of this class are
13
+ # deliberately not thread-safe; the EventProcessor is responsible for enforcing
14
+ # synchronization across both the summarizer and the event queue.
15
+ class EventSummarizer
16
+ class Counter
17
+ end
18
+
19
+ def initialize
20
+ clear
21
+ end
22
+
23
+ # Adds this event to our counters, if it is a type of event we need to count.
24
+ def summarize_event(event)
25
+ return if !event.is_a?(LaunchDarkly::Impl::EvalEvent)
26
+
27
+ counters_for_flag = @counters[event.key]
28
+ if counters_for_flag.nil?
29
+ counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new)
30
+ @counters[event.key] = counters_for_flag
31
+ end
32
+ counters_for_flag_version = counters_for_flag.versions[event.version]
33
+ if counters_for_flag_version.nil?
34
+ counters_for_flag_version = Hash.new
35
+ counters_for_flag.versions[event.version] = counters_for_flag_version
36
+ end
37
+ variation_counter = counters_for_flag_version[event.variation]
38
+ if variation_counter.nil?
39
+ counters_for_flag_version[event.variation] = EventSummaryFlagVariationCounter.new(event.value, 1)
40
+ else
41
+ variation_counter.count = variation_counter.count + 1
42
+ end
43
+ time = event.timestamp
44
+ if !time.nil?
45
+ @start_date = time if @start_date == 0 || time < @start_date
46
+ @end_date = time if time > @end_date
47
+ end
48
+ end
49
+
50
+ # Returns a snapshot of the current summarized event data, and resets this state.
51
+ def snapshot
52
+ ret = EventSummary.new(@start_date, @end_date, @counters)
53
+ ret
54
+ end
55
+
56
+ def clear
57
+ @start_date = 0
58
+ @end_date = 0
59
+ @counters = {}
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,90 @@
1
+ module LaunchDarkly
2
+ module Impl
3
+ class Event
4
+ def initialize(timestamp, user)
5
+ @timestamp = timestamp
6
+ @user = user
7
+ end
8
+
9
+ attr_reader :timestamp
10
+ attr_reader :kind
11
+ attr_reader :user
12
+ end
13
+
14
+ class EvalEvent < Event
15
+ def initialize(timestamp, user, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
16
+ track_events = false, debug_until = nil, prereq_of = nil)
17
+ super(timestamp, user)
18
+ @key = key
19
+ @version = version
20
+ @variation = variation
21
+ @value = value
22
+ @reason = reason
23
+ @default = default
24
+ # avoid setting rarely-used attributes if they have no value - this saves a little space per instance
25
+ @track_events = track_events if track_events
26
+ @debug_until = debug_until if debug_until
27
+ @prereq_of = prereq_of if prereq_of
28
+ end
29
+
30
+ attr_reader :key
31
+ attr_reader :version
32
+ attr_reader :variation
33
+ attr_reader :value
34
+ attr_reader :reason
35
+ attr_reader :default
36
+ attr_reader :track_events
37
+ attr_reader :debug_until
38
+ attr_reader :prereq_of
39
+ end
40
+
41
+ class IdentifyEvent < Event
42
+ def initialize(timestamp, user)
43
+ super(timestamp, user)
44
+ end
45
+ end
46
+
47
+ class CustomEvent < Event
48
+ def initialize(timestamp, user, key, data = nil, metric_value = nil)
49
+ super(timestamp, user)
50
+ @key = key
51
+ @data = data if !data.nil?
52
+ @metric_value = metric_value if !metric_value.nil?
53
+ end
54
+
55
+ attr_reader :key
56
+ attr_reader :data
57
+ attr_reader :metric_value
58
+ end
59
+
60
+ class AliasEvent < Event
61
+ def initialize(timestamp, key, context_kind, previous_key, previous_context_kind)
62
+ super(timestamp, nil)
63
+ @key = key
64
+ @context_kind = context_kind
65
+ @previous_key = previous_key
66
+ @previous_context_kind = previous_context_kind
67
+ end
68
+
69
+ attr_reader :key
70
+ attr_reader :context_kind
71
+ attr_reader :previous_key
72
+ attr_reader :previous_context_kind
73
+ end
74
+
75
+ class IndexEvent < Event
76
+ def initialize(timestamp, user)
77
+ super(timestamp, user)
78
+ end
79
+ end
80
+
81
+ class DebugEvent < Event
82
+ def initialize(eval_event)
83
+ super(eval_event.timestamp, eval_event.user)
84
+ @eval_event = eval_event
85
+ end
86
+
87
+ attr_reader :eval_event
88
+ end
89
+ end
90
+ end
@@ -35,7 +35,7 @@ module LaunchDarkly
35
35
  @client = Aws::DynamoDB::Client.new(opts[:dynamodb_opts] || {})
36
36
  end
37
37
 
38
- @logger.info("${description}: using DynamoDB table \"#{table_name}\"")
38
+ @logger.info("#{description}: using DynamoDB table \"#{table_name}\"")
39
39
  end
40
40
 
41
41
  def stop
@@ -0,0 +1,177 @@
1
+ require "ldclient-rb/impl/evaluator_helpers"
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ module DataModelPreprocessing
6
+ #
7
+ # Container for a precomputed result that includes a specific variation index and value, an
8
+ # evaluation reason, and optionally an alternate evaluation reason that corresponds to the
9
+ # "in experiment" state.
10
+ #
11
+ class EvalResultsForSingleVariation
12
+ def initialize(value, variation_index, regular_reason, in_experiment_reason = nil)
13
+ @regular_result = EvaluationDetail.new(value, variation_index, regular_reason)
14
+ @in_experiment_result = in_experiment_reason ?
15
+ EvaluationDetail.new(value, variation_index, in_experiment_reason) :
16
+ @regular_result
17
+ end
18
+
19
+ # @param in_experiment [Boolean] indicates whether we want the result to include
20
+ # "inExperiment: true" in the reason or not
21
+ # @return [EvaluationDetail]
22
+ def get_result(in_experiment = false)
23
+ in_experiment ? @in_experiment_result : @regular_result
24
+ end
25
+ end
26
+
27
+ #
28
+ # Container for a set of precomputed results, one for each possible flag variation.
29
+ #
30
+ class EvalResultFactoryMultiVariations
31
+ def initialize(variation_factories)
32
+ @factories = variation_factories
33
+ end
34
+
35
+ # @param index [Integer] the variation index
36
+ # @param in_experiment [Boolean] indicates whether we want the result to include
37
+ # "inExperiment: true" in the reason or not
38
+ def for_variation(index, in_experiment)
39
+ if index < 0 || index >= @factories.length
40
+ EvaluationDetail.new(nil, nil, EvaluationReason.error(EvaluationReason::ERROR_MALFORMED_FLAG))
41
+ else
42
+ @factories[index].get_result(in_experiment)
43
+ end
44
+ end
45
+ end
46
+
47
+ # Base class for all of the preprocessed data classes we embed in our data model. Using this class
48
+ # ensures that none of its properties will be included in JSON representations. It also overrides
49
+ # == to say that it is always equal with another instance of the same class; equality tests on
50
+ # this class are only ever done in test code, and we want the contents of these classes to be
51
+ # ignored in test code unless we are looking at specific attributes.
52
+ class PreprocessedDataBase
53
+ def as_json(*)
54
+ nil
55
+ end
56
+
57
+ def to_json(*a)
58
+ "null"
59
+ end
60
+
61
+ def ==(other)
62
+ other.class == self.class
63
+ end
64
+ end
65
+
66
+ class FlagPreprocessed < PreprocessedDataBase
67
+ def initialize(off_result, fallthrough_factory)
68
+ @off_result = off_result
69
+ @fallthrough_factory = fallthrough_factory
70
+ end
71
+
72
+ # @return [EvalResultsForSingleVariation]
73
+ attr_reader :off_result
74
+ # @return [EvalResultFactoryMultiVariations]
75
+ attr_reader :fallthrough_factory
76
+ end
77
+
78
+ class PrerequisitePreprocessed < PreprocessedDataBase
79
+ def initialize(failed_result)
80
+ @failed_result = failed_result
81
+ end
82
+
83
+ # @return [EvalResultsForSingleVariation]
84
+ attr_reader :failed_result
85
+ end
86
+
87
+ class TargetPreprocessed < PreprocessedDataBase
88
+ def initialize(match_result)
89
+ @match_result = match_result
90
+ end
91
+
92
+ # @return [EvalResultsForSingleVariation]
93
+ attr_reader :match_result
94
+ end
95
+
96
+ class FlagRulePreprocessed < PreprocessedDataBase
97
+ def initialize(all_match_results)
98
+ @all_match_results = all_match_results
99
+ end
100
+
101
+ # @return [EvalResultsForSingleVariation]
102
+ attr_reader :all_match_results
103
+ end
104
+
105
+ class Preprocessor
106
+ def initialize(logger = nil)
107
+ @logger = logger
108
+ end
109
+
110
+ def preprocess_item!(kind, item)
111
+ if kind.eql? FEATURES
112
+ preprocess_flag!(item)
113
+ elsif kind.eql? SEGMENTS
114
+ preprocess_segment!(item)
115
+ end
116
+ end
117
+
118
+ def preprocess_all_items!(kind, items_map)
119
+ return items_map if !items_map
120
+ items_map.each do |key, item|
121
+ preprocess_item!(kind, item)
122
+ end
123
+ end
124
+
125
+ def preprocess_flag!(flag)
126
+ flag[:_preprocessed] = FlagPreprocessed.new(
127
+ EvaluatorHelpers.off_result(flag),
128
+ precompute_multi_variation_results(flag, EvaluationReason::fallthrough(false), EvaluationReason::fallthrough(true))
129
+ )
130
+ (flag[:prerequisites] || []).each do |prereq|
131
+ preprocess_prerequisite!(prereq, flag)
132
+ end
133
+ (flag[:targets] || []).each do |target|
134
+ preprocess_target!(target, flag)
135
+ end
136
+ rules = flag[:rules]
137
+ (rules || []).each_index do |index|
138
+ preprocess_flag_rule!(rules[index], index, flag)
139
+ end
140
+ end
141
+
142
+ def preprocess_segment!(segment)
143
+ # nothing to do for segments currently
144
+ end
145
+
146
+ private def preprocess_prerequisite!(prereq, flag)
147
+ prereq[:_preprocessed] = PrerequisitePreprocessed.new(
148
+ EvaluatorHelpers.prerequisite_failed_result(prereq, flag, @logger)
149
+ )
150
+ end
151
+
152
+ private def preprocess_target!(target, flag)
153
+ target[:_preprocessed] = TargetPreprocessed.new(
154
+ EvaluatorHelpers.target_match_result(target, flag, @logger)
155
+ )
156
+ end
157
+
158
+ private def preprocess_flag_rule!(rule, index, flag)
159
+ match_reason = EvaluationReason::rule_match(index, rule[:id])
160
+ match_reason_in_experiment = EvaluationReason::rule_match(index, rule[:id], true)
161
+ rule[:_preprocessed] = FlagRulePreprocessed.new(
162
+ precompute_multi_variation_results(flag, match_reason, match_reason_in_experiment)
163
+ )
164
+ end
165
+
166
+ private def precompute_multi_variation_results(flag, regular_reason, in_experiment_reason)
167
+ factories = []
168
+ vars = flag[:variations] || []
169
+ vars.each_index do |index|
170
+ factories << EvalResultsForSingleVariation.new(vars[index], index, regular_reason, in_experiment_reason)
171
+ end
172
+ EvalResultFactoryMultiVariations.new(factories)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -1,13 +1,14 @@
1
+ require "ldclient-rb/impl/model/preprocessed_data"
1
2
 
2
3
  module LaunchDarkly
3
4
  module Impl
4
5
  module Model
5
6
  # Abstraction of deserializing a feature flag or segment that was read from a data store or
6
7
  # received from LaunchDarkly.
7
- def self.deserialize(kind, json)
8
+ def self.deserialize(kind, json, logger = nil)
8
9
  return nil if json.nil?
9
10
  item = JSON.parse(json, symbolize_names: true)
10
- postprocess_item_after_deserializing!(kind, item)
11
+ DataModelPreprocessing::Preprocessor.new(logger).preprocess_item!(kind, item)
11
12
  item
12
13
  end
13
14
 
@@ -18,45 +19,14 @@ module LaunchDarkly
18
19
  end
19
20
 
20
21
  # Translates a { flags: ..., segments: ... } object received from LaunchDarkly to the data store format.
21
- def self.make_all_store_data(received_data)
22
+ def self.make_all_store_data(received_data, logger = nil)
23
+ preprocessor = DataModelPreprocessing::Preprocessor.new(logger)
22
24
  flags = received_data[:flags]
23
- postprocess_items_after_deserializing!(FEATURES, flags)
25
+ preprocessor.preprocess_all_items!(FEATURES, flags)
24
26
  segments = received_data[:segments]
25
- postprocess_items_after_deserializing!(SEGMENTS, segments)
27
+ preprocessor.preprocess_all_items!(SEGMENTS, segments)
26
28
  { FEATURES => flags, SEGMENTS => segments }
27
29
  end
28
-
29
- # Called after we have deserialized a model item from JSON (because we received it from LaunchDarkly,
30
- # or read it from a persistent data store). This allows us to precompute some derived attributes that
31
- # will never change during the lifetime of that item.
32
- def self.postprocess_item_after_deserializing!(kind, item)
33
- return if !item
34
- # Currently we are special-casing this for FEATURES; eventually it will be handled by delegating
35
- # to the "kind" object or the item class.
36
- if kind.eql? FEATURES
37
- # For feature flags, we precompute all possible parameterized EvaluationReason instances.
38
- prereqs = item[:prerequisites]
39
- if !prereqs.nil?
40
- prereqs.each do |prereq|
41
- prereq[:_reason] = EvaluationReason::prerequisite_failed(prereq[:key])
42
- end
43
- end
44
- rules = item[:rules]
45
- if !rules.nil?
46
- rules.each_index do |i|
47
- rule = rules[i]
48
- rule[:_reason] = EvaluationReason::rule_match(i, rule[:id])
49
- end
50
- end
51
- end
52
- end
53
-
54
- def self.postprocess_items_after_deserializing!(kind, items_map)
55
- return items_map if !items_map
56
- items_map.each do |key, item|
57
- postprocess_item_after_deserializing!(kind, item)
58
- end
59
- end
60
30
  end
61
31
  end
62
32
  end