launchdarkly-server-sdk 8.8.3-java

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +13 -0
  3. data/README.md +61 -0
  4. data/lib/launchdarkly-server-sdk.rb +1 -0
  5. data/lib/ldclient-rb/cache_store.rb +45 -0
  6. data/lib/ldclient-rb/config.rb +658 -0
  7. data/lib/ldclient-rb/context.rb +565 -0
  8. data/lib/ldclient-rb/evaluation_detail.rb +387 -0
  9. data/lib/ldclient-rb/events.rb +642 -0
  10. data/lib/ldclient-rb/expiring_cache.rb +77 -0
  11. data/lib/ldclient-rb/flags_state.rb +88 -0
  12. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  13. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  14. data/lib/ldclient-rb/impl/context.rb +96 -0
  15. data/lib/ldclient-rb/impl/context_filter.rb +166 -0
  16. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  17. data/lib/ldclient-rb/impl/data_store.rb +109 -0
  18. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  19. data/lib/ldclient-rb/impl/diagnostic_events.rb +129 -0
  20. data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
  21. data/lib/ldclient-rb/impl/evaluator.rb +539 -0
  22. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +86 -0
  23. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  24. data/lib/ldclient-rb/impl/evaluator_operators.rb +131 -0
  25. data/lib/ldclient-rb/impl/event_sender.rb +100 -0
  26. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  27. data/lib/ldclient-rb/impl/event_types.rb +136 -0
  28. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  29. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +170 -0
  30. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +300 -0
  31. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +229 -0
  32. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +306 -0
  33. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  34. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  35. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  36. data/lib/ldclient-rb/impl/model/clause.rb +45 -0
  37. data/lib/ldclient-rb/impl/model/feature_flag.rb +254 -0
  38. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  39. data/lib/ldclient-rb/impl/model/segment.rb +132 -0
  40. data/lib/ldclient-rb/impl/model/serialization.rb +72 -0
  41. data/lib/ldclient-rb/impl/repeating_task.rb +46 -0
  42. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  43. data/lib/ldclient-rb/impl/store_client_wrapper.rb +141 -0
  44. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +55 -0
  45. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  46. data/lib/ldclient-rb/impl/util.rb +95 -0
  47. data/lib/ldclient-rb/impl.rb +13 -0
  48. data/lib/ldclient-rb/in_memory_store.rb +100 -0
  49. data/lib/ldclient-rb/integrations/consul.rb +45 -0
  50. data/lib/ldclient-rb/integrations/dynamodb.rb +92 -0
  51. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  52. data/lib/ldclient-rb/integrations/redis.rb +98 -0
  53. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +663 -0
  54. data/lib/ldclient-rb/integrations/test_data.rb +213 -0
  55. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +246 -0
  56. data/lib/ldclient-rb/integrations.rb +6 -0
  57. data/lib/ldclient-rb/interfaces.rb +974 -0
  58. data/lib/ldclient-rb/ldclient.rb +822 -0
  59. data/lib/ldclient-rb/memoized_value.rb +32 -0
  60. data/lib/ldclient-rb/migrations.rb +230 -0
  61. data/lib/ldclient-rb/non_blocking_thread_pool.rb +46 -0
  62. data/lib/ldclient-rb/polling.rb +102 -0
  63. data/lib/ldclient-rb/reference.rb +295 -0
  64. data/lib/ldclient-rb/requestor.rb +102 -0
  65. data/lib/ldclient-rb/simple_lru_cache.rb +25 -0
  66. data/lib/ldclient-rb/stream.rb +196 -0
  67. data/lib/ldclient-rb/util.rb +132 -0
  68. data/lib/ldclient-rb/version.rb +3 -0
  69. data/lib/ldclient-rb.rb +27 -0
  70. metadata +400 -0
@@ -0,0 +1,136 @@
1
+ require "set"
2
+ require "ldclient-rb/impl/sampler"
3
+ require "logger"
4
+
5
+ module LaunchDarkly
6
+ module Impl
7
+ module Migrations
8
+ class OpTracker
9
+ include LaunchDarkly::Interfaces::Migrations::OpTracker
10
+
11
+ #
12
+ # @param logger [Logger] logger
13
+ # @param key [string] key
14
+ # @param flag [LaunchDarkly::Impl::Model::FeatureFlag] flag
15
+ # @param context [LaunchDarkly::LDContext] context
16
+ # @param detail [LaunchDarkly::EvaluationDetail] detail
17
+ # @param default_stage [Symbol] default_stage
18
+ #
19
+ def initialize(logger, key, flag, context, detail, default_stage)
20
+ @logger = logger
21
+ @key = key
22
+ @flag = flag
23
+ @context = context
24
+ @detail = detail
25
+ @default_stage = default_stage
26
+ @sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
27
+
28
+ @mutex = Mutex.new
29
+
30
+ # @type [Symbol, nil]
31
+ @operation = nil
32
+
33
+ # @type [Set<Symbol>]
34
+ @invoked = Set.new
35
+ # @type [Boolean, nil]
36
+ @consistent = nil
37
+
38
+ # @type [Int]
39
+ @consistent_ratio = @flag&.migration_settings&.check_ratio
40
+ @consistent_ratio = 1 if @consistent_ratio.nil?
41
+
42
+ # @type [Set<Symbol>]
43
+ @errors = Set.new
44
+ # @type [Hash<Symbol, Float>]
45
+ @latencies = Hash.new
46
+ end
47
+
48
+ def operation(operation)
49
+ return unless LaunchDarkly::Migrations::VALID_OPERATIONS.include? operation
50
+
51
+ @mutex.synchronize do
52
+ @operation = operation
53
+ end
54
+ end
55
+
56
+ def invoked(origin)
57
+ return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
58
+
59
+ @mutex.synchronize do
60
+ @invoked.add(origin)
61
+ end
62
+ end
63
+
64
+ def consistent(is_consistent)
65
+ @mutex.synchronize do
66
+ if @sampler.sample(@consistent_ratio)
67
+ begin
68
+ @consistent = is_consistent.call
69
+ rescue => e
70
+ LaunchDarkly::Util.log_exception(@logger, "Exception raised during consistency check; failed to record measurement", e)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def error(origin)
77
+ return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
78
+
79
+ @mutex.synchronize do
80
+ @errors.add(origin)
81
+ end
82
+ end
83
+
84
+ def latency(origin, duration)
85
+ return unless LaunchDarkly::Migrations::VALID_ORIGINS.include? origin
86
+ return unless duration.is_a? Numeric
87
+ return if duration < 0
88
+
89
+ @mutex.synchronize do
90
+ @latencies[origin] = duration
91
+ end
92
+ end
93
+
94
+ def build
95
+ @mutex.synchronize do
96
+ return "operation cannot contain an empty key" if @key.empty?
97
+ return "operation not provided" if @operation.nil?
98
+ return "no origins were invoked" if @invoked.empty?
99
+ return "provided context was invalid" unless @context.valid?
100
+
101
+ result = check_invoked_consistency
102
+ return result unless result == true
103
+
104
+ LaunchDarkly::Impl::MigrationOpEvent.new(
105
+ LaunchDarkly::Impl::Util.current_time_millis,
106
+ @context,
107
+ @key,
108
+ @flag,
109
+ @operation,
110
+ @default_stage,
111
+ @detail,
112
+ @invoked,
113
+ @consistent,
114
+ @consistent_ratio,
115
+ @errors,
116
+ @latencies
117
+ )
118
+ end
119
+ end
120
+
121
+ private def check_invoked_consistency
122
+ LaunchDarkly::Migrations::VALID_ORIGINS.each do |origin|
123
+ next if @invoked.include? origin
124
+
125
+ return "provided latency for origin '#{origin}' without recording invocation" if @latencies.include? origin
126
+ return "provided error for origin '#{origin}' without recording invocation" if @errors.include? origin
127
+ end
128
+
129
+ return "provided consistency without recording both invocations" if !@consistent.nil? && @invoked.size != 2
130
+
131
+ true
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,45 @@
1
+ require "ldclient-rb/reference"
2
+
3
+
4
+ # See serialization.rb for implementation notes on the data model classes.
5
+
6
+ module LaunchDarkly
7
+ module Impl
8
+ module Model
9
+ class Clause
10
+ def initialize(data, errors_out = nil)
11
+ @data = data
12
+ @context_kind = data[:contextKind]
13
+ @op = data[:op].to_sym
14
+ if @op == :segmentMatch
15
+ @attribute = nil
16
+ else
17
+ @attribute = (@context_kind.nil? || @context_kind.empty?) ? Reference.create_literal(data[:attribute]) : Reference.create(data[:attribute])
18
+ unless errors_out.nil? || @attribute.error.nil?
19
+ errors_out << "clause has invalid attribute: #{@attribute.error}"
20
+ end
21
+ end
22
+ @values = data[:values] || []
23
+ @negate = !!data[:negate]
24
+ end
25
+
26
+ # @return [Hash]
27
+ attr_reader :data
28
+ # @return [String|nil]
29
+ attr_reader :context_kind
30
+ # @return [LaunchDarkly::Reference]
31
+ attr_reader :attribute
32
+ # @return [Symbol]
33
+ attr_reader :op
34
+ # @return [Array]
35
+ attr_reader :values
36
+ # @return [Boolean]
37
+ attr_reader :negate
38
+
39
+ def as_json
40
+ @data
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,254 @@
1
+ require "ldclient-rb/impl/evaluator_helpers"
2
+ require "ldclient-rb/impl/model/clause"
3
+ require "set"
4
+
5
+ # See serialization.rb for implementation notes on the data model classes.
6
+
7
+ def check_variation_range(flag, errors_out, variation, description)
8
+ unless flag.nil? || errors_out.nil? || variation.nil?
9
+ if variation < 0 || variation >= flag.variations.length
10
+ errors_out << "#{description} has invalid variation index"
11
+ end
12
+ end
13
+ end
14
+
15
+ module LaunchDarkly
16
+ module Impl
17
+ module Model
18
+ class FeatureFlag
19
+ # @param data [Hash]
20
+ # @param logger [Logger|nil]
21
+ def initialize(data, logger = nil)
22
+ raise ArgumentError, "expected hash but got #{data.class}" unless data.is_a?(Hash)
23
+ errors = []
24
+ @data = data
25
+ @key = data[:key]
26
+ @version = data[:version]
27
+ @deleted = !!data[:deleted]
28
+ return if @deleted
29
+ migration_settings = data[:migration] || {}
30
+ @migration_settings = MigrationSettings.new(migration_settings[:checkRatio])
31
+ @sampling_ratio = data[:samplingRatio]
32
+ @exclude_from_summaries = !!data[:excludeFromSummaries]
33
+ @variations = data[:variations] || []
34
+ @on = !!data[:on]
35
+ fallthrough = data[:fallthrough] || {}
36
+ @fallthrough = VariationOrRollout.new(fallthrough[:variation], fallthrough[:rollout], self, errors, "fallthrough")
37
+ @off_variation = data[:offVariation]
38
+ check_variation_range(self, errors, @off_variation, "off variation")
39
+ @prerequisites = (data[:prerequisites] || []).map do |prereq_data|
40
+ Prerequisite.new(prereq_data, self)
41
+ end
42
+ @targets = (data[:targets] || []).map do |target_data|
43
+ Target.new(target_data, self, errors)
44
+ end
45
+ @context_targets = (data[:contextTargets] || []).map do |target_data|
46
+ Target.new(target_data, self, errors)
47
+ end
48
+ @rules = (data[:rules] || []).map.with_index do |rule_data, index|
49
+ FlagRule.new(rule_data, index, self, errors)
50
+ end
51
+ @salt = data[:salt]
52
+ @off_result = EvaluatorHelpers.evaluation_detail_for_off_variation(self, EvaluationReason::off)
53
+ @fallthrough_results = Preprocessor.precompute_multi_variation_results(self,
54
+ EvaluationReason::fallthrough(false), EvaluationReason::fallthrough(true))
55
+ unless logger.nil?
56
+ errors.each do |message|
57
+ logger.error("[LDClient] Data inconsistency in feature flag \"#{@key}\": #{message}")
58
+ end
59
+ end
60
+ end
61
+
62
+ # @return [Hash]
63
+ attr_reader :data
64
+ # @return [String]
65
+ attr_reader :key
66
+ # @return [Integer]
67
+ attr_reader :version
68
+ # @return [Boolean]
69
+ attr_reader :deleted
70
+ # @return [MigrationSettings, nil]
71
+ attr_reader :migration_settings
72
+ # @return [Integer, nil]
73
+ attr_reader :sampling_ratio
74
+ # @return [Boolean, nil]
75
+ attr_reader :exclude_from_summaries
76
+ # @return [Array]
77
+ attr_reader :variations
78
+ # @return [Boolean]
79
+ attr_reader :on
80
+ # @return [Integer|nil]
81
+ attr_reader :off_variation
82
+ # @return [LaunchDarkly::Impl::Model::VariationOrRollout]
83
+ attr_reader :fallthrough
84
+ # @return [LaunchDarkly::EvaluationDetail]
85
+ attr_reader :off_result
86
+ # @return [LaunchDarkly::Impl::Model::EvalResultFactoryMultiVariations]
87
+ attr_reader :fallthrough_results
88
+ # @return [Array<LaunchDarkly::Impl::Model::Prerequisite>]
89
+ attr_reader :prerequisites
90
+ # @return [Array<LaunchDarkly::Impl::Model::Target>]
91
+ attr_reader :targets
92
+ # @return [Array<LaunchDarkly::Impl::Model::Target>]
93
+ attr_reader :context_targets
94
+ # @return [Array<LaunchDarkly::Impl::Model::FlagRule>]
95
+ attr_reader :rules
96
+ # @return [String]
97
+ attr_reader :salt
98
+
99
+ # This method allows us to read properties of the object as if it's just a hash. Currently this is
100
+ # necessary because some data store logic is still written to expect hashes; we can remove it once
101
+ # we migrate entirely to using attributes of the class.
102
+ def [](key)
103
+ @data[key]
104
+ end
105
+
106
+ def ==(other)
107
+ other.is_a?(FeatureFlag) && other.data == self.data
108
+ end
109
+
110
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
111
+ @data
112
+ end
113
+
114
+ # Same as as_json, but converts the JSON structure into a string.
115
+ def to_json(*a)
116
+ as_json.to_json(*a)
117
+ end
118
+ end
119
+
120
+ class Prerequisite
121
+ def initialize(data, flag)
122
+ @data = data
123
+ @key = data[:key]
124
+ @variation = data[:variation]
125
+ @failure_result = EvaluatorHelpers.evaluation_detail_for_off_variation(flag,
126
+ EvaluationReason::prerequisite_failed(@key))
127
+ end
128
+
129
+ # @return [Hash]
130
+ attr_reader :data
131
+ # @return [String]
132
+ attr_reader :key
133
+ # @return [Integer]
134
+ attr_reader :variation
135
+ # @return [LaunchDarkly::EvaluationDetail]
136
+ attr_reader :failure_result
137
+ end
138
+
139
+ class Target
140
+ def initialize(data, flag, errors_out = nil)
141
+ @kind = data[:contextKind] || LDContext::KIND_DEFAULT
142
+ @data = data
143
+ @values = Set.new(data[:values] || [])
144
+ @variation = data[:variation]
145
+ @match_result = EvaluatorHelpers.evaluation_detail_for_variation(flag,
146
+ data[:variation], EvaluationReason::target_match)
147
+ check_variation_range(flag, errors_out, @variation, "target")
148
+ end
149
+
150
+ # @return [String]
151
+ attr_reader :kind
152
+ # @return [Hash]
153
+ attr_reader :data
154
+ # @return [Set]
155
+ attr_reader :values
156
+ # @return [Integer]
157
+ attr_reader :variation
158
+ # @return [LaunchDarkly::EvaluationDetail]
159
+ attr_reader :match_result
160
+ end
161
+
162
+ class FlagRule
163
+ def initialize(data, rule_index, flag, errors_out = nil)
164
+ @data = data
165
+ @clauses = (data[:clauses] || []).map do |clause_data|
166
+ Clause.new(clause_data, errors_out)
167
+ end
168
+ @variation_or_rollout = VariationOrRollout.new(data[:variation], data[:rollout], flag, errors_out, 'rule')
169
+ rule_id = data[:id]
170
+ match_reason = EvaluationReason::rule_match(rule_index, rule_id)
171
+ match_reason_in_experiment = EvaluationReason::rule_match(rule_index, rule_id, true)
172
+ @match_results = Preprocessor.precompute_multi_variation_results(flag, match_reason, match_reason_in_experiment)
173
+ end
174
+
175
+ # @return [Hash]
176
+ attr_reader :data
177
+ # @return [Array<LaunchDarkly::Impl::Model::Clause>]
178
+ attr_reader :clauses
179
+ # @return [LaunchDarkly::Impl::Model::EvalResultFactoryMultiVariations]
180
+ attr_reader :match_results
181
+ # @return [LaunchDarkly::Impl::Model::VariationOrRollout]
182
+ attr_reader :variation_or_rollout
183
+ end
184
+
185
+
186
+ class MigrationSettings
187
+ #
188
+ # @param check_ratio [Int, nil]
189
+ #
190
+ def initialize(check_ratio)
191
+ @check_ratio = check_ratio
192
+ end
193
+
194
+ # @return [Integer, nil]
195
+ attr_reader :check_ratio
196
+ end
197
+
198
+ class VariationOrRollout
199
+ def initialize(variation, rollout_data, flag = nil, errors_out = nil, description = nil)
200
+ @variation = variation
201
+ check_variation_range(flag, errors_out, variation, description)
202
+ @rollout = rollout_data.nil? ? nil : Rollout.new(rollout_data, flag, errors_out, description)
203
+ end
204
+
205
+ # @return [Integer|nil]
206
+ attr_reader :variation
207
+ # @return [Rollout|nil] currently we do not have a model class for the rollout
208
+ attr_reader :rollout
209
+ end
210
+
211
+ class Rollout
212
+ def initialize(data, flag = nil, errors_out = nil, description = nil)
213
+ @context_kind = data[:contextKind]
214
+ @variations = (data[:variations] || []).map { |v| WeightedVariation.new(v, flag, errors_out, description) }
215
+ @bucket_by = data[:bucketBy]
216
+ @kind = data[:kind]
217
+ @is_experiment = @kind == "experiment"
218
+ @seed = data[:seed]
219
+ end
220
+
221
+ # @return [String|nil]
222
+ attr_reader :context_kind
223
+ # @return [Array<WeightedVariation>]
224
+ attr_reader :variations
225
+ # @return [String|nil]
226
+ attr_reader :bucket_by
227
+ # @return [String|nil]
228
+ attr_reader :kind
229
+ # @return [Boolean]
230
+ attr_reader :is_experiment
231
+ # @return [Integer|nil]
232
+ attr_reader :seed
233
+ end
234
+
235
+ class WeightedVariation
236
+ def initialize(data, flag = nil, errors_out = nil, description = nil)
237
+ @variation = data[:variation]
238
+ @weight = data[:weight]
239
+ @untracked = !!data[:untracked]
240
+ check_variation_range(flag, errors_out, @variation, description)
241
+ end
242
+
243
+ # @return [Integer]
244
+ attr_reader :variation
245
+ # @return [Integer]
246
+ attr_reader :weight
247
+ # @return [Boolean]
248
+ attr_reader :untracked
249
+ end
250
+
251
+ # Clause is defined in its own file because clauses are used by both flags and segments
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,64 @@
1
+ require "ldclient-rb/impl/evaluator_helpers"
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ module Model
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 [LaunchDarkly::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
+ # @return [LaunchDarkly::EvaluationDetail]
39
+ def for_variation(index, in_experiment)
40
+ if index < 0 || index >= @factories.length
41
+ EvaluationDetail.new(nil, nil, EvaluationReason.error(EvaluationReason::ERROR_MALFORMED_FLAG))
42
+ else
43
+ @factories[index].get_result(in_experiment)
44
+ end
45
+ end
46
+ end
47
+
48
+ class Preprocessor
49
+ # @param flag [LaunchDarkly::Impl::Model::FeatureFlag]
50
+ # @param regular_reason [LaunchDarkly::EvaluationReason]
51
+ # @param in_experiment_reason [LaunchDarkly::EvaluationReason]
52
+ # @return [EvalResultFactoryMultiVariations]
53
+ def self.precompute_multi_variation_results(flag, regular_reason, in_experiment_reason)
54
+ factories = []
55
+ vars = flag[:variations] || []
56
+ vars.each_index do |index|
57
+ factories << EvalResultsForSingleVariation.new(vars[index], index, regular_reason, in_experiment_reason)
58
+ end
59
+ EvalResultFactoryMultiVariations.new(factories)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,132 @@
1
+ require "ldclient-rb/impl/model/clause"
2
+ require "ldclient-rb/impl/model/preprocessed_data"
3
+ require "set"
4
+
5
+ # See serialization.rb for implementation notes on the data model classes.
6
+
7
+ module LaunchDarkly
8
+ module Impl
9
+ module Model
10
+ class Segment
11
+ # @param data [Hash]
12
+ # @param logger [Logger|nil]
13
+ def initialize(data, logger = nil)
14
+ raise ArgumentError, "expected hash but got #{data.class}" unless data.is_a?(Hash)
15
+ errors = []
16
+ @data = data
17
+ @key = data[:key]
18
+ @version = data[:version]
19
+ @deleted = !!data[:deleted]
20
+ return if @deleted
21
+ @included = data[:included] || []
22
+ @excluded = data[:excluded] || []
23
+ @included_contexts = (data[:includedContexts] || []).map do |target_data|
24
+ SegmentTarget.new(target_data)
25
+ end
26
+ @excluded_contexts = (data[:excludedContexts] || []).map do |target_data|
27
+ SegmentTarget.new(target_data)
28
+ end
29
+ @rules = (data[:rules] || []).map do |rule_data|
30
+ SegmentRule.new(rule_data, errors)
31
+ end
32
+ @unbounded = !!data[:unbounded]
33
+ @unbounded_context_kind = data[:unboundedContextKind] || LDContext::KIND_DEFAULT
34
+ @generation = data[:generation]
35
+ @salt = data[:salt]
36
+ unless logger.nil?
37
+ errors.each do |message|
38
+ logger.error("[LDClient] Data inconsistency in segment \"#{@key}\": #{message}")
39
+ end
40
+ end
41
+ end
42
+
43
+ # @return [Hash]
44
+ attr_reader :data
45
+ # @return [String]
46
+ attr_reader :key
47
+ # @return [Integer]
48
+ attr_reader :version
49
+ # @return [Boolean]
50
+ attr_reader :deleted
51
+ # @return [Array<String>]
52
+ attr_reader :included
53
+ # @return [Array<String>]
54
+ attr_reader :excluded
55
+ # @return [Array<LaunchDarkly::Impl::Model::SegmentTarget>]
56
+ attr_reader :included_contexts
57
+ # @return [Array<LaunchDarkly::Impl::Model::SegmentTarget>]
58
+ attr_reader :excluded_contexts
59
+ # @return [Array<SegmentRule>]
60
+ attr_reader :rules
61
+ # @return [Boolean]
62
+ attr_reader :unbounded
63
+ # @return [String]
64
+ attr_reader :unbounded_context_kind
65
+ # @return [Integer|nil]
66
+ attr_reader :generation
67
+ # @return [String]
68
+ attr_reader :salt
69
+
70
+ # This method allows us to read properties of the object as if it's just a hash. Currently this is
71
+ # necessary because some data store logic is still written to expect hashes; we can remove it once
72
+ # we migrate entirely to using attributes of the class.
73
+ def [](key)
74
+ @data[key]
75
+ end
76
+
77
+ def ==(other)
78
+ other.is_a?(Segment) && other.data == self.data
79
+ end
80
+
81
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
82
+ @data
83
+ end
84
+
85
+ # Same as as_json, but converts the JSON structure into a string.
86
+ def to_json(*a)
87
+ as_json.to_json(*a)
88
+ end
89
+ end
90
+
91
+ class SegmentTarget
92
+ def initialize(data)
93
+ @data = data
94
+ @context_kind = data[:contextKind]
95
+ @values = Set.new(data[:values] || [])
96
+ end
97
+
98
+ # @return [Hash]
99
+ attr_reader :data
100
+ # @return [String]
101
+ attr_reader :context_kind
102
+ # @return [Set]
103
+ attr_reader :values
104
+ end
105
+
106
+ class SegmentRule
107
+ def initialize(data, errors_out = nil)
108
+ @data = data
109
+ @clauses = (data[:clauses] || []).map do |clause_data|
110
+ Clause.new(clause_data, errors_out)
111
+ end
112
+ @weight = data[:weight]
113
+ @bucket_by = data[:bucketBy]
114
+ @rollout_context_kind = data[:rolloutContextKind]
115
+ end
116
+
117
+ # @return [Hash]
118
+ attr_reader :data
119
+ # @return [Array<LaunchDarkly::Impl::Model::Clause>]
120
+ attr_reader :clauses
121
+ # @return [Integer|nil]
122
+ attr_reader :weight
123
+ # @return [String|nil]
124
+ attr_reader :bucket_by
125
+ # @return [String|nil]
126
+ attr_reader :rollout_context_kind
127
+ end
128
+
129
+ # Clause is defined in its own file because clauses are used by both flags and segments
130
+ end
131
+ end
132
+ end