launchdarkly-server-sdk 6.3.1 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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