launchdarkly-server-sdk 7.3.2 → 8.1.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,
@@ -149,7 +155,7 @@ module LaunchDarkly
149
155
  # @return [String, nil] a hash string or nil if the provided context was invalid
150
156
  #
151
157
  def secure_mode_hash(context)
152
- context = Impl::Context::make_context(context)
158
+ context = Impl::Context.make_context(context)
153
159
  unless context.valid?
154
160
  @config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
155
161
  return nil
@@ -189,10 +195,11 @@ module LaunchDarkly
189
195
  # @param default the default value of the flag; this is used if there is an error
190
196
  # condition making it impossible to find or evaluate the flag
191
197
  #
192
- # @return the variation for the provided context, or the default value if there's an an error
198
+ # @return the variation for the provided context, or the default value if there's an error
193
199
  #
194
200
  def variation(key, context, default)
195
- evaluate_internal(key, context, default, false).value
201
+ detail, _, _, = variation_with_flag(key, context, default)
202
+ detail.value
196
203
  end
197
204
 
198
205
  #
@@ -219,7 +226,43 @@ module LaunchDarkly
219
226
  # @return [EvaluationDetail] an object describing the result
220
227
  #
221
228
  def variation_detail(key, context, default)
222
- evaluate_internal(key, context, default, true)
229
+ detail, _, _ = evaluate_internal(key, context, default, true)
230
+ detail
231
+ end
232
+
233
+ #
234
+ # This method returns the migration stage of the migration feature flag for the given evaluation context.
235
+ #
236
+ # This method returns the default stage if there is an error or the flag does not exist. If the default stage is not
237
+ # a valid stage, then a default stage of 'off' will be used instead.
238
+ #
239
+ # @param key [String]
240
+ # @param context [LDContext]
241
+ # @param default_stage [Symbol]
242
+ #
243
+ # @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
244
+ #
245
+ def migration_variation(key, context, default_stage)
246
+ unless Migrations::VALID_STAGES.include? default_stage
247
+ @config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
248
+ default_stage = Migrations::STAGE_OFF
249
+ end
250
+
251
+ context = Impl::Context::make_context(context)
252
+ detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
253
+
254
+ stage = detail.value
255
+ stage = stage.to_sym if stage.respond_to? :to_sym
256
+
257
+ if Migrations::VALID_STAGES.include?(stage)
258
+ tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
259
+ return stage, tracker
260
+ end
261
+
262
+ detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
263
+ tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
264
+
265
+ [default_stage, tracker]
223
266
  end
224
267
 
225
268
  #
@@ -282,6 +325,32 @@ module LaunchDarkly
282
325
  @event_processor.record_custom_event(context, event_name, data, metric_value)
283
326
  end
284
327
 
328
+ #
329
+ # Tracks the results of a migrations operation. This event includes measurements which can be used to enhance the
330
+ # observability of a migration within the LaunchDarkly UI.
331
+ #
332
+ # This event should be generated through {Interfaces::Migrations::OpTracker}. If you are using the
333
+ # {Interfaces::Migrations::Migrator} to handle migrations, this event will be created and emitted
334
+ # automatically.
335
+ #
336
+ # @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
337
+ #
338
+ def track_migration_op(tracker)
339
+ unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
340
+ @config.logger.error { "invalid op tracker received in track_migration_op" }
341
+ return
342
+ end
343
+
344
+ event = tracker.build
345
+ if event.is_a? String
346
+ @config.logger.error { "[LDClient] Error occurred generating migration op event; #{event}" }
347
+ return
348
+ end
349
+
350
+
351
+ @event_processor.record_migration_op_event(event)
352
+ end
353
+
285
354
  #
286
355
  # Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
287
356
  # including the flag values and also metadata that can be used on the front end. This method does not
@@ -431,24 +500,41 @@ module LaunchDarkly
431
500
  end
432
501
  end
433
502
 
503
+ #
504
+ # @param key [String]
434
505
  # @param context [Hash, LDContext]
435
- # @return [EvaluationDetail]
506
+ # @param default [Object]
507
+ #
508
+ # @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
509
+ #
510
+ def variation_with_flag(key, context, default)
511
+ evaluate_internal(key, context, default, false)
512
+ end
513
+
514
+ #
515
+ # @param key [String]
516
+ # @param context [Hash, LDContext]
517
+ # @param default [Object]
518
+ # @param with_reasons [Boolean]
519
+ #
520
+ # @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
521
+ #
436
522
  def evaluate_internal(key, context, default, with_reasons)
437
523
  if @config.offline?
438
- return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
524
+ return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default), nil, nil
439
525
  end
440
526
 
441
527
  if context.nil?
442
528
  @config.logger.error { "[LDClient] Must specify context" }
443
529
  detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
444
- return detail
530
+ return detail, nil, "no context provided"
445
531
  end
446
532
 
447
533
  context = Impl::Context::make_context(context)
448
534
  unless context.valid?
449
535
  @config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
450
536
  detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
451
- return detail
537
+ return detail, nil, context.error
452
538
  end
453
539
 
454
540
  unless initialized?
@@ -458,7 +544,7 @@ module LaunchDarkly
458
544
  @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
459
545
  detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
460
546
  record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
461
- return detail
547
+ return detail, nil, "client not initialized"
462
548
  end
463
549
  end
464
550
 
@@ -472,7 +558,7 @@ module LaunchDarkly
472
558
  @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
473
559
  detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
474
560
  record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
475
- return detail
561
+ return detail, nil, "feature flag not found"
476
562
  end
477
563
 
478
564
  begin
@@ -487,12 +573,12 @@ module LaunchDarkly
487
573
  detail = EvaluationDetail.new(default, nil, detail.reason)
488
574
  end
489
575
  record_flag_eval(feature, context, detail, default, with_reasons)
490
- detail
576
+ [detail, feature, nil]
491
577
  rescue => exn
492
578
  Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
493
579
  detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
494
580
  record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
495
- detail
581
+ [detail, feature, exn.to_s]
496
582
  end
497
583
  end
498
584
 
@@ -508,7 +594,9 @@ module LaunchDarkly
508
594
  default,
509
595
  add_experiment_data || flag[:trackEvents] || false,
510
596
  flag[:debugEventsUntilDate],
511
- nil
597
+ nil,
598
+ flag[:samplingRatio],
599
+ !!flag[:excludeFromSummaries]
512
600
  )
513
601
  end
514
602
 
@@ -524,13 +612,15 @@ module LaunchDarkly
524
612
  nil,
525
613
  add_experiment_data || prereq_flag[:trackEvents] || false,
526
614
  prereq_flag[:debugEventsUntilDate],
527
- prereq_of_flag[:key]
615
+ prereq_of_flag[:key],
616
+ prereq_flag[:samplingRatio],
617
+ !!prereq_flag[:excludeFromSummaries]
528
618
  )
529
619
  end
530
620
 
531
621
  private def record_flag_eval_error(flag, context, default, reason, with_reasons)
532
622
  @event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
533
- flag[:trackEvents], flag[:debugEventsUntilDate], nil)
623
+ flag[:trackEvents], flag[:debugEventsUntilDate], nil, flag[:samplingRatio], !!flag[:excludeFromSummaries])
534
624
  end
535
625
 
536
626
  #
@@ -542,7 +632,7 @@ module LaunchDarkly
542
632
  #
543
633
  private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
544
634
  @event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
545
- false, nil, nil)
635
+ false, nil, nil, 1, false)
546
636
  end
547
637
 
548
638
  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
@@ -109,6 +109,8 @@ module LaunchDarkly
109
109
  end
110
110
  private_class_method :new
111
111
 
112
+ protected attr_reader :components
113
+
112
114
  #
113
115
  # Creates a Reference from a string. For the supported syntax and examples,
114
116
  # see comments on the Reference type.
@@ -227,6 +229,15 @@ module LaunchDarkly
227
229
  @components[index]
228
230
  end
229
231
 
232
+ def ==(other)
233
+ self.error == other.error && self.components == other.components
234
+ end
235
+ alias eql? ==
236
+
237
+ def hash
238
+ ([error] + components).hash
239
+ end
240
+
230
241
  #
231
242
  # Performs unescaping of attribute reference path components:
232
243
  #
@@ -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.2" # x-release-please-version
2
+ VERSION = "8.1.0" # x-release-please-version
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"