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.
- checksums.yaml +7 -0
- data/LICENSE.txt +13 -0
- data/README.md +61 -0
- data/lib/launchdarkly-server-sdk.rb +1 -0
- data/lib/ldclient-rb/cache_store.rb +45 -0
- data/lib/ldclient-rb/config.rb +658 -0
- data/lib/ldclient-rb/context.rb +565 -0
- data/lib/ldclient-rb/evaluation_detail.rb +387 -0
- data/lib/ldclient-rb/events.rb +642 -0
- data/lib/ldclient-rb/expiring_cache.rb +77 -0
- data/lib/ldclient-rb/flags_state.rb +88 -0
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +166 -0
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +109 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +129 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/evaluator.rb +539 -0
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +86 -0
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +131 -0
- data/lib/ldclient-rb/impl/event_sender.rb +100 -0
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +136 -0
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +170 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +300 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +229 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +306 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/clause.rb +45 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +254 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +132 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +72 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +46 -0
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +141 -0
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +55 -0
- data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
- data/lib/ldclient-rb/impl/util.rb +95 -0
- data/lib/ldclient-rb/impl.rb +13 -0
- data/lib/ldclient-rb/in_memory_store.rb +100 -0
- data/lib/ldclient-rb/integrations/consul.rb +45 -0
- data/lib/ldclient-rb/integrations/dynamodb.rb +92 -0
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +98 -0
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +663 -0
- data/lib/ldclient-rb/integrations/test_data.rb +213 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +246 -0
- data/lib/ldclient-rb/integrations.rb +6 -0
- data/lib/ldclient-rb/interfaces.rb +974 -0
- data/lib/ldclient-rb/ldclient.rb +822 -0
- data/lib/ldclient-rb/memoized_value.rb +32 -0
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +46 -0
- data/lib/ldclient-rb/polling.rb +102 -0
- data/lib/ldclient-rb/reference.rb +295 -0
- data/lib/ldclient-rb/requestor.rb +102 -0
- data/lib/ldclient-rb/simple_lru_cache.rb +25 -0
- data/lib/ldclient-rb/stream.rb +196 -0
- data/lib/ldclient-rb/util.rb +132 -0
- data/lib/ldclient-rb/version.rb +3 -0
- data/lib/ldclient-rb.rb +27 -0
- 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
|