launchdarkly-server-sdk 7.3.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -788,5 +788,102 @@ module LaunchDarkly
788
788
  end
789
789
  end
790
790
  end
791
+
792
+ #
793
+ # Namespace for feature-flag based technology migration support.
794
+ #
795
+ module Migrations
796
+ #
797
+ # A migrator is the interface through which migration support is executed. A migrator is configured through the
798
+ # {LaunchDarkly::Migrations::MigratorBuilder} class.
799
+ #
800
+ module Migrator
801
+ #
802
+ # Uses the provided flag key and context to execute a migration-backed read operation.
803
+ #
804
+ # @param key [String]
805
+ # @param context [LaunchDarkly::LDContext]
806
+ # @param default_stage [Symbol]
807
+ # @param payload [Object, nil]
808
+ #
809
+ # @return [LaunchDarkly::Migrations::OperationResult]
810
+ #
811
+ def read(key, context, default_stage, payload = nil) end
812
+
813
+ #
814
+ # Uses the provided flag key and context to execute a migration-backed write operation.
815
+ #
816
+ # @param key [String]
817
+ # @param context [LaunchDarkly::LDContext]
818
+ # @param default_stage [Symbol]
819
+ # @param payload [Object, nil]
820
+ #
821
+ # @return [LaunchDarkly::Migrations::WriteResult]
822
+ #
823
+ def write(key, context, default_stage, payload = nil) end
824
+ end
825
+
826
+ #
827
+ # An OpTracker is responsible for managing the collection of measurements that which a user might wish to record
828
+ # throughout a migration-assisted operation.
829
+ #
830
+ # Example measurements include latency, errors, and consistency.
831
+ #
832
+ # This data can be provided to the {LaunchDarkly::LDClient.track_migration_op} method to relay this metric
833
+ # information upstream to LaunchDarkly services.
834
+ #
835
+ module OpTracker
836
+ #
837
+ # Sets the migration related operation associated with these tracking measurements.
838
+ #
839
+ # @param [Symbol] op The read or write operation symbol.
840
+ #
841
+ def operation(op) end
842
+
843
+ #
844
+ # Allows recording which origins were called during a migration.
845
+ #
846
+ # @param [Symbol] origin Designation for the old or new origin.
847
+ #
848
+ def invoked(origin) end
849
+
850
+ #
851
+ # Allows recording the results of a consistency check.
852
+ #
853
+ # This method accepts a callable which should take no parameters and return a single boolean to represent the
854
+ # consistency check results for a read operation.
855
+ #
856
+ # A callable is provided in case sampling rules do not require consistency checking to run. In this case, we can
857
+ # avoid the overhead of a function by not using the callable.
858
+ #
859
+ # @param [#call] is_consistent closure to return result of comparison check
860
+ #
861
+ def consistent(is_consistent) end
862
+
863
+ #
864
+ # Allows recording whether an error occurred during the operation.
865
+ #
866
+ # @param [Symbol] origin Designation for the old or new origin.
867
+ #
868
+ def error(origin) end
869
+
870
+ #
871
+ # Allows tracking the recorded latency for an individual operation.
872
+ #
873
+ # @param [Symbol] origin Designation for the old or new origin.
874
+ # @param [Float] duration Duration measurement in milliseconds (ms).
875
+ #
876
+ def latency(origin, duration) end
877
+
878
+ #
879
+ # Creates an instance of {LaunchDarkly::Impl::MigrationOpEventData}.
880
+ #
881
+ # @return [LaunchDarkly::Impl::MigrationOpEvent, String] A migration op event or a string describing the error.
882
+ # failure.
883
+ #
884
+ def build
885
+ end
886
+ end
887
+ end
791
888
  end
792
889
  end
@@ -6,8 +6,10 @@ require "ldclient-rb/impl/diagnostic_events"
6
6
  require "ldclient-rb/impl/evaluator"
7
7
  require "ldclient-rb/impl/flag_tracker"
8
8
  require "ldclient-rb/impl/store_client_wrapper"
9
+ require "ldclient-rb/impl/migrations/tracker"
9
10
  require "concurrent/atomics"
10
11
  require "digest/sha1"
12
+ require "forwardable"
11
13
  require "logger"
12
14
  require "benchmark"
13
15
  require "json"
@@ -20,6 +22,10 @@ module LaunchDarkly
20
22
  #
21
23
  class LDClient
22
24
  include Impl
25
+ extend Forwardable
26
+
27
+ def_delegators :@config, :logger
28
+
23
29
  #
24
30
  # Creates a new client instance that connects to LaunchDarkly. A custom
25
31
  # configuration parameter can also supplied to specify advanced options,
@@ -148,7 +154,7 @@ module LaunchDarkly
148
154
  # @return [String, nil] a hash string or nil if the provided context was invalid
149
155
  #
150
156
  def secure_mode_hash(context)
151
- context = Impl::Context::make_context(context)
157
+ context = Impl::Context.make_context(context)
152
158
  unless context.valid?
153
159
  @config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
154
160
  return nil
@@ -188,10 +194,11 @@ module LaunchDarkly
188
194
  # @param default the default value of the flag; this is used if there is an error
189
195
  # condition making it impossible to find or evaluate the flag
190
196
  #
191
- # @return the variation for the provided context, or the default value if there's an an error
197
+ # @return the variation for the provided context, or the default value if there's an error
192
198
  #
193
199
  def variation(key, context, default)
194
- evaluate_internal(key, context, default, false).value
200
+ detail, _, _, = variation_with_flag(key, context, default)
201
+ detail.value
195
202
  end
196
203
 
197
204
  #
@@ -218,7 +225,43 @@ module LaunchDarkly
218
225
  # @return [EvaluationDetail] an object describing the result
219
226
  #
220
227
  def variation_detail(key, context, default)
221
- evaluate_internal(key, context, default, true)
228
+ detail, _, _ = evaluate_internal(key, context, default, true)
229
+ detail
230
+ end
231
+
232
+ #
233
+ # This method returns the migration stage of the migration feature flag for the given evaluation context.
234
+ #
235
+ # This method returns the default stage if there is an error or the flag does not exist. If the default stage is not
236
+ # a valid stage, then a default stage of 'off' will be used instead.
237
+ #
238
+ # @param key [String]
239
+ # @param context [LDContext]
240
+ # @param default_stage [Symbol]
241
+ #
242
+ # @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
243
+ #
244
+ def migration_variation(key, context, default_stage)
245
+ unless Migrations::VALID_STAGES.include? default_stage
246
+ @config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
247
+ default_stage = Migrations::STAGE_OFF
248
+ end
249
+
250
+ context = Impl::Context::make_context(context)
251
+ detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
252
+
253
+ stage = detail.value
254
+ stage = stage.to_sym if stage.respond_to? :to_sym
255
+
256
+ if Migrations::VALID_STAGES.include?(stage)
257
+ tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
258
+ return stage, tracker
259
+ end
260
+
261
+ detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
262
+ tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
263
+
264
+ [default_stage, tracker]
222
265
  end
223
266
 
224
267
  #
@@ -281,6 +324,32 @@ module LaunchDarkly
281
324
  @event_processor.record_custom_event(context, event_name, data, metric_value)
282
325
  end
283
326
 
327
+ #
328
+ # Tracks the results of a migrations operation. This event includes measurements which can be used to enhance the
329
+ # observability of a migration within the LaunchDarkly UI.
330
+ #
331
+ # This event should be generated through {Interfaces::Migrations::OpTracker}. If you are using the
332
+ # {Interfaces::Migrations::Migrator} to handle migrations, this event will be created and emitted
333
+ # automatically.
334
+ #
335
+ # @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
336
+ #
337
+ def track_migration_op(tracker)
338
+ unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
339
+ @config.logger.error { "invalid op tracker received in track_migration_op" }
340
+ return
341
+ end
342
+
343
+ event = tracker.build
344
+ if event.is_a? String
345
+ @config.logger.error { "[LDClient] Error occurred generating migration op event; #{event}" }
346
+ return
347
+ end
348
+
349
+
350
+ @event_processor.record_migration_op_event(event)
351
+ end
352
+
284
353
  #
285
354
  # Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
286
355
  # including the flag values and also metadata that can be used on the front end. This method does not
@@ -430,24 +499,41 @@ module LaunchDarkly
430
499
  end
431
500
  end
432
501
 
502
+ #
503
+ # @param key [String]
433
504
  # @param context [Hash, LDContext]
434
- # @return [EvaluationDetail]
505
+ # @param default [Object]
506
+ #
507
+ # @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
508
+ #
509
+ def variation_with_flag(key, context, default)
510
+ evaluate_internal(key, context, default, false)
511
+ end
512
+
513
+ #
514
+ # @param key [String]
515
+ # @param context [Hash, LDContext]
516
+ # @param default [Object]
517
+ # @param with_reasons [Boolean]
518
+ #
519
+ # @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
520
+ #
435
521
  def evaluate_internal(key, context, default, with_reasons)
436
522
  if @config.offline?
437
- return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
523
+ return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default), nil, nil
438
524
  end
439
525
 
440
526
  if context.nil?
441
527
  @config.logger.error { "[LDClient] Must specify context" }
442
528
  detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
443
- return detail
529
+ return detail, nil, "no context provided"
444
530
  end
445
531
 
446
532
  context = Impl::Context::make_context(context)
447
533
  unless context.valid?
448
534
  @config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
449
535
  detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
450
- return detail
536
+ return detail, nil, context.error
451
537
  end
452
538
 
453
539
  unless initialized?
@@ -457,7 +543,7 @@ module LaunchDarkly
457
543
  @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
458
544
  detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
459
545
  record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
460
- return detail
546
+ return detail, nil, "client not initialized"
461
547
  end
462
548
  end
463
549
 
@@ -471,7 +557,7 @@ module LaunchDarkly
471
557
  @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
472
558
  detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
473
559
  record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
474
- return detail
560
+ return detail, nil, "feature flag not found"
475
561
  end
476
562
 
477
563
  begin
@@ -486,12 +572,12 @@ module LaunchDarkly
486
572
  detail = EvaluationDetail.new(default, nil, detail.reason)
487
573
  end
488
574
  record_flag_eval(feature, context, detail, default, with_reasons)
489
- detail
575
+ [detail, feature, nil]
490
576
  rescue => exn
491
577
  Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
492
578
  detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
493
579
  record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
494
- detail
580
+ [detail, feature, exn.to_s]
495
581
  end
496
582
  end
497
583
 
@@ -507,7 +593,9 @@ module LaunchDarkly
507
593
  default,
508
594
  add_experiment_data || flag[:trackEvents] || false,
509
595
  flag[:debugEventsUntilDate],
510
- nil
596
+ nil,
597
+ flag[:samplingRatio],
598
+ !!flag[:excludeFromSummaries]
511
599
  )
512
600
  end
513
601
 
@@ -523,13 +611,15 @@ module LaunchDarkly
523
611
  nil,
524
612
  add_experiment_data || prereq_flag[:trackEvents] || false,
525
613
  prereq_flag[:debugEventsUntilDate],
526
- prereq_of_flag[:key]
614
+ prereq_of_flag[:key],
615
+ prereq_flag[:samplingRatio],
616
+ !!prereq_flag[:excludeFromSummaries]
527
617
  )
528
618
  end
529
619
 
530
620
  private def record_flag_eval_error(flag, context, default, reason, with_reasons)
531
621
  @event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
532
- flag[:trackEvents], flag[:debugEventsUntilDate], nil)
622
+ flag[:trackEvents], flag[:debugEventsUntilDate], nil, flag[:samplingRatio], !!flag[:excludeFromSummaries])
533
623
  end
534
624
 
535
625
  #
@@ -541,7 +631,7 @@ module LaunchDarkly
541
631
  #
542
632
  private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
543
633
  @event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
544
- false, nil, nil)
634
+ false, nil, nil, 1, false)
545
635
  end
546
636
 
547
637
  private def experiment?(flag, reason)
@@ -0,0 +1,230 @@
1
+ require 'ldclient-rb/impl/migrations/migrator'
2
+
3
+ module LaunchDarkly
4
+ #
5
+ # Namespace for feature-flag based technology migration support.
6
+ #
7
+ module Migrations
8
+ # Symbol representing the old origin, or the old technology source you are migrating away from.
9
+ ORIGIN_OLD = :old
10
+ # Symbol representing the new origin, or the new technology source you are migrating towards.
11
+ ORIGIN_NEW = :new
12
+
13
+ # Symbol defining a read-related operation
14
+ OP_READ = :read
15
+ # Symbol defining a write-related operation
16
+ OP_WRITE = :write
17
+
18
+ STAGE_OFF = :off
19
+ STAGE_DUALWRITE = :dualwrite
20
+ STAGE_SHADOW = :shadow
21
+ STAGE_LIVE = :live
22
+ STAGE_RAMPDOWN = :rampdown
23
+ STAGE_COMPLETE = :complete
24
+
25
+ VALID_OPERATIONS = [
26
+ OP_READ,
27
+ OP_WRITE,
28
+ ]
29
+
30
+ VALID_ORIGINS = [
31
+ ORIGIN_OLD,
32
+ ORIGIN_NEW,
33
+ ]
34
+
35
+ VALID_STAGES = [
36
+ STAGE_OFF,
37
+ STAGE_DUALWRITE,
38
+ STAGE_SHADOW,
39
+ STAGE_LIVE,
40
+ STAGE_RAMPDOWN,
41
+ STAGE_COMPLETE,
42
+ ]
43
+
44
+ #
45
+ # The OperationResult wraps the {LaunchDarkly::Result} class to tie an operation origin to a result.
46
+ #
47
+ class OperationResult
48
+ extend Forwardable
49
+ def_delegators :@result, :value, :error, :exception, :success?
50
+
51
+ #
52
+ # @param origin [Symbol]
53
+ # @param result [LaunchDarkly::Result]
54
+ #
55
+ def initialize(origin, result)
56
+ @origin = origin
57
+ @result = result
58
+ end
59
+
60
+ #
61
+ # @return [Symbol] The origin this result is associated with.
62
+ #
63
+ attr_reader :origin
64
+ end
65
+
66
+ #
67
+ # A write result contains the operation results against both the authoritative and non-authoritative origins.
68
+ #
69
+ # Authoritative writes are always executed first. In the event of a failure, the non-authoritative write will not
70
+ # be executed, resulting in a nil value in the final WriteResult.
71
+ #
72
+ class WriteResult
73
+ #
74
+ # @param authoritative [OperationResult]
75
+ # @param nonauthoritative [OperationResult, nil]
76
+ #
77
+ def initialize(authoritative, nonauthoritative = nil)
78
+ @authoritative = authoritative
79
+ @nonauthoritative = nonauthoritative
80
+ end
81
+
82
+ #
83
+ # Returns the operation result for the authoritative origin.
84
+ #
85
+ # @return [OperationResult]
86
+ #
87
+ attr_reader :authoritative
88
+
89
+ #
90
+ # Returns the operation result for the non-authoritative origin.
91
+ #
92
+ # This result might be nil as the non-authoritative write does not execute in every stage, and will not execute
93
+ # if the authoritative write failed.
94
+ #
95
+ # @return [OperationResult, nil]
96
+ #
97
+ attr_reader :nonauthoritative
98
+ end
99
+
100
+
101
+ #
102
+ # The migration builder is used to configure and construct an instance of a
103
+ # {LaunchDarkly::Interfaces::Migrations::Migrator}. This migrator can be used to perform LaunchDarkly assisted
104
+ # technology migrations through the use of migration-based feature flags.
105
+ #
106
+ class MigratorBuilder
107
+ EXECUTION_SERIAL = :serial
108
+ EXECUTION_RANDOM = :random
109
+ EXECUTION_PARALLEL = :parallel
110
+
111
+ VALID_EXECUTION_ORDERS = [EXECUTION_SERIAL, EXECUTION_RANDOM, EXECUTION_PARALLEL]
112
+ private_constant :VALID_EXECUTION_ORDERS
113
+
114
+ #
115
+ # @param client [LaunchDarkly::LDClient]
116
+ #
117
+ def initialize(client)
118
+ @client = client
119
+
120
+ # Default settings as required by the spec
121
+ @read_execution_order = EXECUTION_PARALLEL
122
+ @measure_latency = true
123
+ @measure_errors = true
124
+
125
+ @read_config = nil # @type [LaunchDarkly::Impl::Migrations::MigrationConfig, nil]
126
+ @write_config = nil # @type [LaunchDarkly::Impl::Migrations::MigrationConfig, nil]
127
+ end
128
+
129
+ #
130
+ # The read execution order influences the parallelism and execution order for read operations involving multiple
131
+ # origins.
132
+ #
133
+ # @param order [Symbol]
134
+ #
135
+ def read_execution_order(order)
136
+ return unless VALID_EXECUTION_ORDERS.include? order
137
+
138
+ @read_execution_order = order
139
+ end
140
+
141
+ #
142
+ # Enable or disable latency tracking for migration operations. This latency information can be sent upstream to
143
+ # LaunchDarkly to enhance migration visibility.
144
+ #
145
+ # @param enabled [Boolean]
146
+ #
147
+ def track_latency(enabled)
148
+ @measure_latency = !!enabled
149
+ end
150
+
151
+ #
152
+ # Enable or disable error tracking for migration operations. This error information can be sent upstream to
153
+ # LaunchDarkly to enhance migration visibility.
154
+ #
155
+ # @param enabled [Boolean]
156
+ #
157
+ def track_errors(enabled)
158
+ @measure_errors = !!enabled
159
+ end
160
+
161
+ #
162
+ # Read can be used to configure the migration-read behavior of the resulting
163
+ # {LaunchDarkly::Interfaces::Migrations::Migrator} instance.
164
+ #
165
+ # Users are required to provide two different read methods -- one to read from the old migration origin, and one
166
+ # to read from the new origin. Additionally, customers can opt-in to consistency tracking by providing a
167
+ # comparison function.
168
+ #
169
+ # Depending on the migration stage, one or both of these read methods may be called.
170
+ #
171
+ # The read methods should accept a single nullable parameter. This parameter is a payload passed through the
172
+ # {LaunchDarkly::Interfaces::Migrations::Migrator#read} method. This method should return a {LaunchDarkly::Result}
173
+ # instance.
174
+ #
175
+ # The consistency method should accept 2 parameters of any type. These parameters are the results of executing the
176
+ # read operation against the old and new origins. If both operations were successful, the consistency method will
177
+ # be invoked. This method should return true if the two parameters are equal, or false otherwise.
178
+ #
179
+ # @param old_read [#call]
180
+ # @param new_read [#call]
181
+ # @param comparison [#call, nil]
182
+ #
183
+ def read(old_read, new_read, comparison = nil)
184
+ return unless old_read.respond_to?(:call) && old_read.arity == 1
185
+ return unless new_read.respond_to?(:call) && new_read.arity == 1
186
+ return unless comparison.nil? || (comparison.respond_to?(:call) && comparison.arity == 2)
187
+
188
+ @read_config = LaunchDarkly::Impl::Migrations::MigrationConfig.new(old_read, new_read, comparison)
189
+ end
190
+
191
+ #
192
+ # Write can be used to configure the migration-write behavior of the resulting
193
+ # {LaunchDarkly::Interfaces::Migrations::Migrator} instance.
194
+ #
195
+ # Users are required to provide two different write methods -- one to write to the old migration origin, and one
196
+ # to write to the new origin.
197
+ #
198
+ # Depending on the migration stage, one or both of these write methods may be called.
199
+ #
200
+ # The write methods should accept a single nullable parameter. This parameter is a payload passed through the
201
+ # {LaunchDarkly::Interfaces::Migrations::Migrator#write} method. This method should return a {LaunchDarkly::Result}
202
+ # instance.
203
+ #
204
+ # @param old_write [#call]
205
+ # @param new_write [#call]
206
+ #
207
+ def write(old_write, new_write)
208
+ return unless old_write.respond_to?(:call) && old_write.arity == 1
209
+ return unless new_write.respond_to?(:call) && new_write.arity == 1
210
+
211
+ @write_config = LaunchDarkly::Impl::Migrations::MigrationConfig.new(old_write, new_write, nil)
212
+ end
213
+
214
+ #
215
+ # Build constructs a {LaunchDarkly::Interfaces::Migrations::Migrator} instance to support migration-based reads
216
+ # and writes. A string describing any failure conditions will be returned if the build fails.
217
+ #
218
+ # @return [LaunchDarkly::Interfaces::Migrations::Migrator, string]
219
+ #
220
+ def build
221
+ return "client not provided" if @client.nil?
222
+ return "read configuration not provided" if @read_config.nil?
223
+ return "write configuration not provided" if @write_config.nil?
224
+
225
+ LaunchDarkly::Impl::Migrations::Migrator.new(@client, @read_execution_order, @read_config, @write_config, @measure_latency, @measure_errors)
226
+ end
227
+ end
228
+
229
+ end
230
+ end
@@ -2,6 +2,69 @@ require "uri"
2
2
  require "http"
3
3
 
4
4
  module LaunchDarkly
5
+ #
6
+ # A Result is used to reflect the outcome of any operation.
7
+ #
8
+ # Results can either be considered a success or a failure.
9
+ #
10
+ # In the event of success, the Result will contain an option, nullable value to hold any success value back to the
11
+ # calling function.
12
+ #
13
+ # If the operation fails, the Result will contain an error describing the value.
14
+ #
15
+ class Result
16
+ #
17
+ # Create a successful result with the provided value.
18
+ #
19
+ # @param value [Object, nil]
20
+ # @return [Result]
21
+ #
22
+ def self.success(value)
23
+ Result.new(value)
24
+ end
25
+
26
+ #
27
+ # Create a failed result with the provided error description.
28
+ #
29
+ # @param error [String]
30
+ # @param exception [Exception, nil]
31
+ # @return [Result]
32
+ #
33
+ def self.fail(error, exception = nil)
34
+ Result.new(nil, error, exception)
35
+ end
36
+
37
+ #
38
+ # Was this result successful or did it encounter an error?
39
+ #
40
+ # @return [Boolean]
41
+ #
42
+ def success?
43
+ @error.nil?
44
+ end
45
+
46
+ #
47
+ # @return [Object, nil] The value returned from the operation if it was successful; nil otherwise.
48
+ #
49
+ attr_reader :value
50
+
51
+ #
52
+ # @return [String, nil] An error description of the failure; nil otherwise
53
+ #
54
+ attr_reader :error
55
+
56
+ #
57
+ # @return [Exception, nil] An optional exception which caused the failure
58
+ #
59
+ attr_reader :exception
60
+
61
+ private def initialize(value, error = nil, exception = nil)
62
+ @value = value
63
+ @error = error
64
+ @exception = exception
65
+ end
66
+ end
67
+
5
68
  # @private
6
69
  module Util
7
70
  #
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "7.3.0"
2
+ VERSION = "8.0.0"
3
3
  end
data/lib/ldclient-rb.rb CHANGED
@@ -9,6 +9,7 @@ require "ldclient-rb/version"
9
9
  require "ldclient-rb/interfaces"
10
10
  require "ldclient-rb/util"
11
11
  require "ldclient-rb/flags_state"
12
+ require "ldclient-rb/migrations"
12
13
  require "ldclient-rb/ldclient"
13
14
  require "ldclient-rb/cache_store"
14
15
  require "ldclient-rb/expiring_cache"