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,387 @@
1
+ module LaunchDarkly
2
+ # An object returned by {LDClient#variation_detail}, combining the result of a flag evaluation with
3
+ # an explanation of how it was calculated.
4
+ class EvaluationDetail
5
+ # Creates a new instance.
6
+ #
7
+ # @param value the result value of the flag evaluation; may be of any type
8
+ # @param variation_index [int|nil] the index of the value within the flag's list of variations, or
9
+ # `nil` if the application default value was returned
10
+ # @param reason [EvaluationReason] an object describing the main factor that influenced the result
11
+ # @raise [ArgumentError] if `variation_index` or `reason` is not of the correct type
12
+ def initialize(value, variation_index, reason)
13
+ raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
14
+ raise ArgumentError.new("reason must be an EvaluationReason") unless reason.is_a? EvaluationReason
15
+
16
+ @value = value
17
+ @variation_index = variation_index
18
+ @reason = reason
19
+ end
20
+
21
+ #
22
+ # The result of the flag evaluation. This will be either one of the flag's variations, or the
23
+ # default value that was passed to {LDClient#variation_detail}. It is the same as the return
24
+ # value of {LDClient#variation}.
25
+ #
26
+ # @return [Object]
27
+ #
28
+ attr_reader :value
29
+
30
+ #
31
+ # The index of the returned value within the flag's list of variations. The first variation is
32
+ # 0, the second is 1, etc. This is `nil` if the default value was returned.
33
+ #
34
+ # @return [int|nil]
35
+ #
36
+ attr_reader :variation_index
37
+
38
+ #
39
+ # An object describing the main factor that influenced the flag evaluation value.
40
+ #
41
+ # @return [EvaluationReason]
42
+ #
43
+ attr_reader :reason
44
+
45
+ #
46
+ # Tests whether the flag evaluation returned a default value. This is the same as checking
47
+ # whether {#variation_index} is nil.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def default_value?
52
+ variation_index.nil?
53
+ end
54
+
55
+ def ==(other)
56
+ @value == other.value && @variation_index == other.variation_index && @reason == other.reason
57
+ end
58
+ end
59
+
60
+ # Describes the reason that a flag evaluation produced a particular value. This is returned by
61
+ # methods such as {LDClient#variation_detail} as the `reason` property of an {EvaluationDetail}.
62
+ #
63
+ # The `kind` property is always defined, but other properties will have non-nil values only for
64
+ # certain values of `kind`. All properties are immutable.
65
+ #
66
+ # There is a standard JSON representation of evaluation reasons when they appear in analytics events.
67
+ # Use `as_json` or `to_json` to convert to this representation.
68
+ #
69
+ # Use factory methods such as {EvaluationReason#off} to obtain instances of this class.
70
+ class EvaluationReason
71
+ # Value for {#kind} indicating that the flag was off and therefore returned its configured off value.
72
+ OFF = :OFF
73
+
74
+ # Value for {#kind} indicating that the flag was on but the context did not match any targets or rules.
75
+ FALLTHROUGH = :FALLTHROUGH
76
+
77
+ # Value for {#kind} indicating that the context key was specifically targeted for this flag.
78
+ TARGET_MATCH = :TARGET_MATCH
79
+
80
+ # Value for {#kind} indicating that the context matched one of the flag's rules.
81
+ RULE_MATCH = :RULE_MATCH
82
+
83
+ # Value for {#kind} indicating that the flag was considered off because it had at least one
84
+ # prerequisite flag that either was off or did not return the desired variation.
85
+ PREREQUISITE_FAILED = :PREREQUISITE_FAILED
86
+
87
+ # Value for {#kind} indicating that the flag could not be evaluated, e.g. because it does not exist
88
+ # or due to an unexpected error. In this case the result value will be the application default value
89
+ # that the caller passed to the client. Check {#error_kind} for more details on the problem.
90
+ ERROR = :ERROR
91
+
92
+ # Value for {#error_kind} indicating that the caller tried to evaluate a flag before the client had
93
+ # successfully initialized.
94
+ ERROR_CLIENT_NOT_READY = :CLIENT_NOT_READY
95
+
96
+ # Value for {#error_kind} indicating that the caller provided a flag key that did not match any known flag.
97
+ ERROR_FLAG_NOT_FOUND = :FLAG_NOT_FOUND
98
+
99
+ # Value for {#error_kind} indicating that there was an internal inconsistency in the flag data, e.g.
100
+ # a rule specified a nonexistent variation. An error message will always be logged in this case.
101
+ ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
102
+
103
+ # Value for {#error_kind} indicating that there was an inconsistency between the expected type of the flag, and the
104
+ # actual type of the variation evaluated.
105
+ ERROR_WRONG_TYPE = :WRONG_TYPE
106
+
107
+ # Value for {#error_kind} indicating that the caller passed `nil` for the context parameter, or the
108
+ # context was invalid.
109
+ ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED
110
+
111
+ # Value for {#error_kind} indicating that an unexpected exception stopped flag evaluation. An error
112
+ # message will always be logged in this case.
113
+ ERROR_EXCEPTION = :EXCEPTION
114
+
115
+ # Indicates the general category of the reason. Will always be one of the class constants such
116
+ # as {#OFF}.
117
+ # @return [Symbol]
118
+ attr_reader :kind
119
+
120
+ # The index of the rule that was matched (0 for the first rule in the feature flag). If
121
+ # {#kind} is not {#RULE_MATCH}, this will be `nil`.
122
+ # @return [Integer|nil]
123
+ attr_reader :rule_index
124
+
125
+ # A unique string identifier for the matched rule, which will not change if other rules are added
126
+ # or deleted. If {#kind} is not {#RULE_MATCH}, this will be `nil`.
127
+ # @return [String]
128
+ attr_reader :rule_id
129
+
130
+ # A boolean or nil value representing if the rule or fallthrough has an experiment rollout.
131
+ # @return [Boolean|nil]
132
+ attr_reader :in_experiment
133
+
134
+ # The key of the prerequisite flag that did not return the desired variation. If {#kind} is not
135
+ # {#PREREQUISITE_FAILED}, this will be `nil`.
136
+ # @return [String]
137
+ attr_reader :prerequisite_key
138
+
139
+ # A value indicating the general category of error. This should be one of the class constants such
140
+ # as {#ERROR_FLAG_NOT_FOUND}. If {#kind} is not {#ERROR}, it will be `nil`.
141
+ # @return [Symbol]
142
+ attr_reader :error_kind
143
+
144
+ # Describes the validity of Big Segment information, if and only if the flag evaluation required
145
+ # querying at least one Big Segment. Otherwise it returns `nil`. Possible values are defined by
146
+ # {BigSegmentsStatus}.
147
+ #
148
+ # Big Segments are a specific kind of context segments. For more information, read the LaunchDarkly
149
+ # documentation: https://docs.launchdarkly.com/home/users/big-segments
150
+ # @return [Symbol]
151
+ attr_reader :big_segments_status
152
+
153
+ # Returns an instance whose {#kind} is {#OFF}.
154
+ # @return [EvaluationReason]
155
+ def self.off
156
+ @@off
157
+ end
158
+
159
+ # Returns an instance whose {#kind} is {#FALLTHROUGH}.
160
+ # @return [EvaluationReason]
161
+ def self.fallthrough(in_experiment=false)
162
+ if in_experiment
163
+ @@fallthrough_with_experiment
164
+ else
165
+ @@fallthrough
166
+ end
167
+ end
168
+
169
+ # Returns an instance whose {#kind} is {#TARGET_MATCH}.
170
+ # @return [EvaluationReason]
171
+ def self.target_match
172
+ @@target_match
173
+ end
174
+
175
+ # Returns an instance whose {#kind} is {#RULE_MATCH}.
176
+ #
177
+ # @param rule_index [Number] the index of the rule that was matched (0 for the first rule in
178
+ # the feature flag)
179
+ # @param rule_id [String] unique string identifier for the matched rule
180
+ # @return [EvaluationReason]
181
+ # @raise [ArgumentError] if `rule_index` is not a number or `rule_id` is not a string
182
+ def self.rule_match(rule_index, rule_id, in_experiment=false)
183
+ raise ArgumentError.new("rule_index must be a number") unless rule_index.is_a? Numeric
184
+ raise ArgumentError.new("rule_id must be a string") if !rule_id.nil? && !(rule_id.is_a? String) # in test data, ID could be nil
185
+
186
+ if in_experiment
187
+ er = new(:RULE_MATCH, rule_index, rule_id, nil, nil, true)
188
+ else
189
+ er = new(:RULE_MATCH, rule_index, rule_id, nil, nil)
190
+ end
191
+ er
192
+ end
193
+
194
+ # Returns an instance whose {#kind} is {#PREREQUISITE_FAILED}.
195
+ #
196
+ # @param prerequisite_key [String] key of the prerequisite flag that did not return the desired variation
197
+ # @return [EvaluationReason]
198
+ # @raise [ArgumentError] if `prerequisite_key` is nil or not a string
199
+ def self.prerequisite_failed(prerequisite_key)
200
+ raise ArgumentError.new("prerequisite_key must be a string") unless prerequisite_key.is_a? String
201
+ new(:PREREQUISITE_FAILED, nil, nil, prerequisite_key, nil)
202
+ end
203
+
204
+ # Returns an instance whose {#kind} is {#ERROR}.
205
+ #
206
+ # @param error_kind [Symbol] value indicating the general category of error
207
+ # @return [EvaluationReason]
208
+ # @raise [ArgumentError] if `error_kind` is not a symbol
209
+ def self.error(error_kind)
210
+ raise ArgumentError.new("error_kind must be a symbol") unless error_kind.is_a? Symbol
211
+ e = @@error_instances[error_kind]
212
+ e.nil? ? make_error(error_kind) : e
213
+ end
214
+
215
+ def ==(other)
216
+ if other.is_a? EvaluationReason
217
+ @kind == other.kind && @rule_index == other.rule_index && @rule_id == other.rule_id &&
218
+ @prerequisite_key == other.prerequisite_key && @error_kind == other.error_kind &&
219
+ @big_segments_status == other.big_segments_status
220
+ elsif other.is_a? Hash
221
+ @kind.to_s == other[:kind] && @rule_index == other[:ruleIndex] && @rule_id == other[:ruleId] &&
222
+ @prerequisite_key == other[:prerequisiteKey] &&
223
+ (other[:errorKind] == @error_kind.nil? ? nil : @error_kind.to_s) &&
224
+ (other[:bigSegmentsStatus] == @big_segments_status.nil? ? nil : @big_segments_status.to_s)
225
+ end
226
+ end
227
+
228
+ # Equivalent to {#inspect}.
229
+ # @return [String]
230
+ def to_s
231
+ inspect
232
+ end
233
+
234
+ # Returns a concise string representation of the reason. Examples: `"FALLTHROUGH"`,
235
+ # `"ERROR(FLAG_NOT_FOUND)"`. The exact syntax is not guaranteed to remain the same; this is meant
236
+ # for debugging.
237
+ # @return [String]
238
+ def inspect
239
+ case @kind
240
+ when :RULE_MATCH
241
+ if @in_experiment
242
+ "RULE_MATCH(#{@rule_index},#{@rule_id},#{@in_experiment})"
243
+ else
244
+ "RULE_MATCH(#{@rule_index},#{@rule_id})"
245
+ end
246
+ when :PREREQUISITE_FAILED
247
+ "PREREQUISITE_FAILED(#{@prerequisite_key})"
248
+ when :ERROR
249
+ "ERROR(#{@error_kind})"
250
+ when :FALLTHROUGH
251
+ @in_experiment ? "FALLTHROUGH(#{@in_experiment})" : @kind.to_s
252
+ else
253
+ @kind.to_s
254
+ end
255
+ end
256
+
257
+ # Returns a hash that can be used as a JSON representation of the reason, in the format used
258
+ # in LaunchDarkly analytics events.
259
+ # @return [Hash]
260
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
261
+ # Note that this implementation is somewhat inefficient; it allocates a new hash every time.
262
+ # However, in normal usage the SDK only serializes reasons if 1. full event tracking is
263
+ # enabled for a flag and the application called variation_detail, or 2. experimentation is
264
+ # enabled for an evaluation. We can't reuse these hashes because an application could call
265
+ # as_json and then modify the result.
266
+ ret = case @kind
267
+ when :RULE_MATCH
268
+ if @in_experiment
269
+ { kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id, inExperiment: @in_experiment }
270
+ else
271
+ { kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id }
272
+ end
273
+ when :PREREQUISITE_FAILED
274
+ { kind: @kind, prerequisiteKey: @prerequisite_key }
275
+ when :ERROR
276
+ { kind: @kind, errorKind: @error_kind }
277
+ when :FALLTHROUGH
278
+ if @in_experiment
279
+ { kind: @kind, inExperiment: @in_experiment }
280
+ else
281
+ { kind: @kind }
282
+ end
283
+ else
284
+ { kind: @kind }
285
+ end
286
+ unless @big_segments_status.nil?
287
+ ret[:bigSegmentsStatus] = @big_segments_status
288
+ end
289
+ ret
290
+ end
291
+
292
+ # Same as {#as_json}, but converts the JSON structure into a string.
293
+ # @return [String]
294
+ def to_json(*a)
295
+ as_json.to_json(*a)
296
+ end
297
+
298
+ # Allows this object to be treated as a hash corresponding to its JSON representation. For
299
+ # instance, if `reason.kind` is {#RULE_MATCH}, then `reason[:kind]` will be `"RULE_MATCH"` and
300
+ # `reason[:ruleIndex]` will be equal to `reason.rule_index`.
301
+ def [](key)
302
+ case key
303
+ when :kind
304
+ @kind.to_s
305
+ when :ruleIndex
306
+ @rule_index
307
+ when :ruleId
308
+ @rule_id
309
+ when :prerequisiteKey
310
+ @prerequisite_key
311
+ when :errorKind
312
+ @error_kind.nil? ? nil : @error_kind.to_s
313
+ when :bigSegmentsStatus
314
+ @big_segments_status.nil? ? nil : @big_segments_status.to_s
315
+ else
316
+ nil
317
+ end
318
+ end
319
+
320
+ def with_big_segments_status(big_segments_status)
321
+ return self if @big_segments_status == big_segments_status
322
+ EvaluationReason.new(@kind, @rule_index, @rule_id, @prerequisite_key, @error_kind, @in_experiment, big_segments_status)
323
+ end
324
+
325
+ #
326
+ # Constructor that sets all properties. Applications should not normally use this constructor,
327
+ # but should use class methods like {#off} to avoid creating unnecessary instances.
328
+ #
329
+ def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind, in_experiment=nil,
330
+ big_segments_status = nil)
331
+ @kind = kind.to_sym
332
+ @rule_index = rule_index
333
+ @rule_id = rule_id
334
+ @rule_id.freeze unless rule_id.nil?
335
+ @prerequisite_key = prerequisite_key
336
+ @prerequisite_key.freeze unless prerequisite_key.nil?
337
+ @error_kind = error_kind
338
+ @in_experiment = in_experiment
339
+ @big_segments_status = big_segments_status
340
+ end
341
+
342
+ private_class_method def self.make_error(error_kind)
343
+ new(:ERROR, nil, nil, nil, error_kind)
344
+ end
345
+
346
+ @@fallthrough_with_experiment = new(:FALLTHROUGH, nil, nil, nil, nil, true)
347
+ @@fallthrough = new(:FALLTHROUGH, nil, nil, nil, nil)
348
+ @@off = new(:OFF, nil, nil, nil, nil)
349
+ @@target_match = new(:TARGET_MATCH, nil, nil, nil, nil)
350
+ @@error_instances = {
351
+ ERROR_CLIENT_NOT_READY => make_error(ERROR_CLIENT_NOT_READY),
352
+ ERROR_FLAG_NOT_FOUND => make_error(ERROR_FLAG_NOT_FOUND),
353
+ ERROR_MALFORMED_FLAG => make_error(ERROR_MALFORMED_FLAG),
354
+ ERROR_USER_NOT_SPECIFIED => make_error(ERROR_USER_NOT_SPECIFIED),
355
+ ERROR_EXCEPTION => make_error(ERROR_EXCEPTION),
356
+ }
357
+ end
358
+
359
+ #
360
+ # Defines the possible values of {EvaluationReason#big_segments_status}.
361
+ #
362
+ module BigSegmentsStatus
363
+ #
364
+ # Indicates that the Big Segment query involved in the flag evaluation was successful, and
365
+ # that the segment state is considered up to date.
366
+ #
367
+ HEALTHY = :HEALTHY
368
+
369
+ #
370
+ # Indicates that the Big Segment query involved in the flag evaluation was successful, but
371
+ # that the segment state may not be up to date.
372
+ #
373
+ STALE = :STALE
374
+
375
+ #
376
+ # Indicates that Big Segments could not be queried for the flag evaluation because the SDK
377
+ # configuration did not include a Big Segment store.
378
+ #
379
+ NOT_CONFIGURED = :NOT_CONFIGURED
380
+
381
+ #
382
+ # Indicates that the Big Segment query involved in the flag evaluation failed, for instance
383
+ # due to a database error.
384
+ #
385
+ STORE_ERROR = :STORE_ERROR
386
+ end
387
+ end