launchdarkly-server-sdk 8.3.1 → 8.4.0
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 +4 -4
- data/lib/ldclient-rb/config.rb +13 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/interfaces.rb +85 -0
- data/lib/ldclient-rb/ldclient.rb +154 -16
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6067b2e1ab38ffb622a683a63206d485bd13744f361d99f89de467b5f1bc0c9e
|
4
|
+
data.tar.gz: 28999c77f6007effa021c4d88a99d3b6ad7ddd951d0e93449ee25276b311290b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f4e045335066941a471cb09a183867318256c093449992b966724b7b5a0f12e8caae2b73ec4beda03c8732130e0891827209ddc22f42bb7de50267135ee3ed6
|
7
|
+
data.tar.gz: e949b6e9d82371ef4a9d6e0d412b6979dc0e1963336fde18c15b6bffea1b023d0ab885cc12fc8a73522cbc59a1174b5ca8c60bbe84fada1aaf6671acbaf8e9b2
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -43,6 +43,7 @@ module LaunchDarkly
|
|
43
43
|
# @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
|
44
44
|
# @option opts [Hash] :application See {#application}
|
45
45
|
# @option opts [String] :payload_filter_key See {#payload_filter_key}
|
46
|
+
# @option hooks [Array<Interfaces::Hooks::Hook]
|
46
47
|
#
|
47
48
|
def initialize(opts = {})
|
48
49
|
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
|
@@ -75,6 +76,7 @@ module LaunchDarkly
|
|
75
76
|
@big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
|
76
77
|
@application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
|
77
78
|
@payload_filter_key = opts[:payload_filter_key]
|
79
|
+
@hooks = (opts[:hooks] || []).keep_if { |hook| hook.is_a? Interfaces::Hooks::Hook }
|
78
80
|
@data_source_update_sink = nil
|
79
81
|
end
|
80
82
|
|
@@ -372,6 +374,17 @@ module LaunchDarkly
|
|
372
374
|
#
|
373
375
|
attr_reader :socket_factory
|
374
376
|
|
377
|
+
#
|
378
|
+
# Initial set of hooks for the client.
|
379
|
+
#
|
380
|
+
# Hooks provide entrypoints which allow for observation of SDK functions.
|
381
|
+
#
|
382
|
+
# LaunchDarkly provides integration packages, and most applications will not
|
383
|
+
# need to implement their own hooks. Refer to the `launchdarkly-server-sdk-otel` gem
|
384
|
+
# for instrumentation.
|
385
|
+
#
|
386
|
+
attr_reader :hooks
|
387
|
+
|
375
388
|
#
|
376
389
|
# The default LaunchDarkly client configuration. This configuration sets
|
377
390
|
# reasonable defaults for most users.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module LaunchDarkly
|
2
|
+
module Impl
|
3
|
+
#
|
4
|
+
# Simple helper class for returning formatted data.
|
5
|
+
#
|
6
|
+
# The variation methods make use of the new hook support. Those methods all need to return an evaluation detail, and
|
7
|
+
# some other unstructured bit of data.
|
8
|
+
#
|
9
|
+
class EvaluationWithHookResult
|
10
|
+
#
|
11
|
+
# Return the evaluation detail that was generated as part of the evaluation.
|
12
|
+
#
|
13
|
+
# @return [LaunchDarkly::EvaluationDetail]
|
14
|
+
#
|
15
|
+
attr_reader :evaluation_detail
|
16
|
+
|
17
|
+
#
|
18
|
+
# All purpose container for additional return values from the wrapping method
|
19
|
+
#
|
20
|
+
# @return [any]
|
21
|
+
#
|
22
|
+
attr_reader :results
|
23
|
+
|
24
|
+
#
|
25
|
+
# @param evaluation_detail [LaunchDarkly::EvaluationDetail]
|
26
|
+
# @param results [any]
|
27
|
+
#
|
28
|
+
def initialize(evaluation_detail, results = nil)
|
29
|
+
@evaluation_detail = evaluation_detail
|
30
|
+
@results = results
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -885,5 +885,90 @@ module LaunchDarkly
|
|
885
885
|
end
|
886
886
|
end
|
887
887
|
end
|
888
|
+
|
889
|
+
module Hooks
|
890
|
+
#
|
891
|
+
# Mixin for extending SDK functionality via hooks.
|
892
|
+
#
|
893
|
+
# All provided hook implementations **MUST** include this mixin. Hooks without this mixin will be ignored.
|
894
|
+
#
|
895
|
+
# This mixin includes default implementations for all hook handlers. This allows LaunchDarkly to expand the list
|
896
|
+
# of hook handlers without breaking customer integrations.
|
897
|
+
#
|
898
|
+
module Hook
|
899
|
+
#
|
900
|
+
# Get metadata about the hook implementation.
|
901
|
+
#
|
902
|
+
# @return [Metadata]
|
903
|
+
#
|
904
|
+
def metadata
|
905
|
+
Metadata.new('UNDEFINED')
|
906
|
+
end
|
907
|
+
|
908
|
+
#
|
909
|
+
# The before method is called during the execution of a variation method before the flag value has been
|
910
|
+
# determined. The method is executed synchronously.
|
911
|
+
#
|
912
|
+
# @param evaluation_series_context [EvaluationSeriesContext] Contains information about the evaluation being
|
913
|
+
# performed. This is not mutable.
|
914
|
+
# @param data [Hash] A record associated with each stage of hook invocations. Each stage is called with the data
|
915
|
+
# of the previous stage for a series. The input record should not be modified.
|
916
|
+
# @return [Hash] Data to use when executing the next state of the hook in the evaluation series.
|
917
|
+
#
|
918
|
+
def before_evaluation(evaluation_series_context, data)
|
919
|
+
data
|
920
|
+
end
|
921
|
+
|
922
|
+
#
|
923
|
+
# The after method is called during the execution of the variation method after the flag value has been
|
924
|
+
# determined. The method is executed synchronously.
|
925
|
+
#
|
926
|
+
# @param evaluation_series_context [EvaluationSeriesContext] Contains read-only information about the evaluation
|
927
|
+
# being performed.
|
928
|
+
# @param data [Hash] A record associated with each stage of hook invocations. Each stage is called with the data
|
929
|
+
# of the previous stage for a series.
|
930
|
+
# @param detail [LaunchDarkly::EvaluationDetail] The result of the evaluation. This value should not be
|
931
|
+
# modified.
|
932
|
+
# @return [Hash] Data to use when executing the next state of the hook in the evaluation series.
|
933
|
+
#
|
934
|
+
def after_evaluation(evaluation_series_context, data, detail)
|
935
|
+
data
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
#
|
940
|
+
# Metadata data class used for annotating hook implementations.
|
941
|
+
#
|
942
|
+
class Metadata
|
943
|
+
attr_reader :name
|
944
|
+
|
945
|
+
def initialize(name)
|
946
|
+
@name = name
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
#
|
951
|
+
# Contextual information that will be provided to handlers during evaluation series.
|
952
|
+
#
|
953
|
+
class EvaluationSeriesContext
|
954
|
+
attr_reader :key
|
955
|
+
attr_reader :context
|
956
|
+
attr_reader :default_value
|
957
|
+
attr_reader :method
|
958
|
+
|
959
|
+
#
|
960
|
+
# @param key [String]
|
961
|
+
# @param context [LaunchDarkly::LDContext]
|
962
|
+
# @param default_value [any]
|
963
|
+
# @param method [Symbol]
|
964
|
+
#
|
965
|
+
def initialize(key, context, default_value, method)
|
966
|
+
@key = key
|
967
|
+
@context = context
|
968
|
+
@default_value = default_value
|
969
|
+
@method = method
|
970
|
+
end
|
971
|
+
end
|
972
|
+
end
|
888
973
|
end
|
889
974
|
end
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -4,9 +4,11 @@ require "ldclient-rb/impl/data_source"
|
|
4
4
|
require "ldclient-rb/impl/data_store"
|
5
5
|
require "ldclient-rb/impl/diagnostic_events"
|
6
6
|
require "ldclient-rb/impl/evaluator"
|
7
|
+
require "ldclient-rb/impl/evaluation_with_hook_result"
|
7
8
|
require "ldclient-rb/impl/flag_tracker"
|
8
9
|
require "ldclient-rb/impl/store_client_wrapper"
|
9
10
|
require "ldclient-rb/impl/migrations/tracker"
|
11
|
+
require "concurrent"
|
10
12
|
require "concurrent/atomics"
|
11
13
|
require "digest/sha1"
|
12
14
|
require "forwardable"
|
@@ -54,6 +56,7 @@ module LaunchDarkly
|
|
54
56
|
end
|
55
57
|
|
56
58
|
@sdk_key = sdk_key
|
59
|
+
@hooks = Concurrent::Array.new(config.hooks)
|
57
60
|
|
58
61
|
@shared_executor = Concurrent::SingleThreadExecutor.new
|
59
62
|
|
@@ -131,6 +134,23 @@ module LaunchDarkly
|
|
131
134
|
end
|
132
135
|
end
|
133
136
|
|
137
|
+
#
|
138
|
+
# Add a hook to the client. In order to register a hook before the client starts, please use the `hooks` property of
|
139
|
+
# {#LDConfig}.
|
140
|
+
#
|
141
|
+
# Hooks provide entrypoints which allow for observation of SDK functions.
|
142
|
+
#
|
143
|
+
# @param hook [Interfaces::Hooks::Hook]
|
144
|
+
#
|
145
|
+
def add_hook(hook)
|
146
|
+
unless hook.is_a?(Interfaces::Hooks::Hook)
|
147
|
+
@config.logger.error { "[LDClient] Attempted to add a hook that does not include the LaunchDarkly::Intefaces::Hooks::Hook mixin. Ignoring." }
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
@hooks.push(hook)
|
152
|
+
end
|
153
|
+
|
134
154
|
#
|
135
155
|
# Tells the client that all pending analytics events should be delivered as soon as possible.
|
136
156
|
#
|
@@ -198,8 +218,13 @@ module LaunchDarkly
|
|
198
218
|
# @return the variation for the provided context, or the default value if there's an error
|
199
219
|
#
|
200
220
|
def variation(key, context, default)
|
201
|
-
|
202
|
-
|
221
|
+
context = Impl::Context::make_context(context)
|
222
|
+
result = evaluate_with_hooks(key, context, default, :variation) do
|
223
|
+
detail, _, _ = variation_with_flag(key, context, default)
|
224
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail)
|
225
|
+
end
|
226
|
+
|
227
|
+
result.evaluation_detail.value
|
203
228
|
end
|
204
229
|
|
205
230
|
#
|
@@ -226,8 +251,118 @@ module LaunchDarkly
|
|
226
251
|
# @return [EvaluationDetail] an object describing the result
|
227
252
|
#
|
228
253
|
def variation_detail(key, context, default)
|
229
|
-
|
230
|
-
|
254
|
+
context = Impl::Context::make_context(context)
|
255
|
+
result = evaluate_with_hooks(key, context, default, :variation_detail) do
|
256
|
+
detail, _, _ = evaluate_internal(key, context, default, true)
|
257
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail)
|
258
|
+
end
|
259
|
+
|
260
|
+
result.evaluation_detail
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# evaluate_with_hook will run the provided block, wrapping it with evaluation hook support.
|
265
|
+
#
|
266
|
+
# Example:
|
267
|
+
#
|
268
|
+
# ```ruby
|
269
|
+
# evaluate_with_hooks(key, context, default, method) do
|
270
|
+
# puts 'This is being wrapped with evaluation hooks'
|
271
|
+
# end
|
272
|
+
# ```
|
273
|
+
#
|
274
|
+
# @param key [String]
|
275
|
+
# @param context [LDContext]
|
276
|
+
# @param default [any]
|
277
|
+
# @param method [Symbol]
|
278
|
+
# @param &block [#call] Implicit passed block
|
279
|
+
#
|
280
|
+
# @return [LaunchDarkly::Impl::EvaluationWithHookResult]
|
281
|
+
#
|
282
|
+
private def evaluate_with_hooks(key, context, default, method)
|
283
|
+
return yield if @hooks.empty?
|
284
|
+
|
285
|
+
hooks, evaluation_series_context = prepare_hooks(key, context, default, method)
|
286
|
+
hook_data = execute_before_evaluation(hooks, evaluation_series_context)
|
287
|
+
evaluation_result = yield
|
288
|
+
execute_after_evaluation(hooks, evaluation_series_context, hook_data, evaluation_result.evaluation_detail)
|
289
|
+
|
290
|
+
evaluation_result
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Execute the :before_evaluation stage of the evaluation series.
|
295
|
+
#
|
296
|
+
# This method will return the results of each hook, indexed into an array in the same order as the hooks. If a hook
|
297
|
+
# raised an uncaught exception, the value will be nil.
|
298
|
+
#
|
299
|
+
# @param hooks [Array<Interfaces::Hooks::Hook>]
|
300
|
+
# @param evaluation_series_context [EvaluationSeriesContext]
|
301
|
+
#
|
302
|
+
# @return [Array<any>]
|
303
|
+
#
|
304
|
+
private def execute_before_evaluation(hooks, evaluation_series_context)
|
305
|
+
hooks.map do |hook|
|
306
|
+
try_execute_stage(:before_evaluation, hook.metadata.name) do
|
307
|
+
hook.before_evaluation(evaluation_series_context, {})
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Execute the :after_evaluation stage of the evaluation series.
|
314
|
+
#
|
315
|
+
# This method will return the results of each hook, indexed into an array in the same order as the hooks. If a hook
|
316
|
+
# raised an uncaught exception, the value will be nil.
|
317
|
+
#
|
318
|
+
# @param hooks [Array<Interfaces::Hooks::Hook>]
|
319
|
+
# @param evaluation_series_context [EvaluationSeriesContext]
|
320
|
+
# @param hook_data [Array<any>]
|
321
|
+
# @param evaluation_detail [EvaluationDetail]
|
322
|
+
#
|
323
|
+
# @return [Array<any>]
|
324
|
+
#
|
325
|
+
private def execute_after_evaluation(hooks, evaluation_series_context, hook_data, evaluation_detail)
|
326
|
+
hooks.zip(hook_data).reverse.map do |(hook, data)|
|
327
|
+
try_execute_stage(:after_evaluation, hook.metadata.name) do
|
328
|
+
hook.after_evaluation(evaluation_series_context, data, evaluation_detail)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
# Try to execute the provided block. If execution raises an exception, catch and log it, then move on with
|
335
|
+
# execution.
|
336
|
+
#
|
337
|
+
# @return [any]
|
338
|
+
#
|
339
|
+
private def try_execute_stage(method, hook_name)
|
340
|
+
begin
|
341
|
+
yield
|
342
|
+
rescue => e
|
343
|
+
@config.logger.error { "[LDClient] An error occurred in #{method} of the hook #{hook_name}: #{e}" }
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
#
|
349
|
+
# Return a copy of the existing hooks and a few instance of the EvaluationSeriesContext used for the evaluation series.
|
350
|
+
#
|
351
|
+
# @param key [String]
|
352
|
+
# @param context [LDContext]
|
353
|
+
# @param default [any]
|
354
|
+
# @param method [Symbol]
|
355
|
+
# @return [Array[Array<Interfaces::Hooks::Hook>, Interfaces::Hooks::EvaluationSeriesContext]]
|
356
|
+
#
|
357
|
+
private def prepare_hooks(key, context, default, method)
|
358
|
+
# Copy the hooks to use a consistent set during the evaluation series.
|
359
|
+
#
|
360
|
+
# Hooks can be added and we want to ensure all correct stages for a given hook execute. For example, we do not
|
361
|
+
# want to trigger the after_evaluation method without also triggering the before_evaluation method.
|
362
|
+
hooks = @hooks.dup
|
363
|
+
evaluation_series_context = Interfaces::Hooks::EvaluationSeriesContext.new(key, context, default, method)
|
364
|
+
|
365
|
+
[hooks, evaluation_series_context]
|
231
366
|
end
|
232
367
|
|
233
368
|
#
|
@@ -249,20 +384,24 @@ module LaunchDarkly
|
|
249
384
|
end
|
250
385
|
|
251
386
|
context = Impl::Context::make_context(context)
|
252
|
-
|
387
|
+
result = evaluate_with_hooks(key, context, default_stage, :migration_variation) do
|
388
|
+
detail, flag, _ = variation_with_flag(key, context, default_stage.to_s)
|
253
389
|
|
254
|
-
|
255
|
-
|
390
|
+
stage = detail.value
|
391
|
+
stage = stage.to_sym if stage.respond_to? :to_sym
|
256
392
|
|
257
|
-
|
393
|
+
if Migrations::VALID_STAGES.include?(stage)
|
394
|
+
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
395
|
+
next LaunchDarkly::Impl::EvaluationWithHookResult.new(detail, {stage: stage, tracker: tracker})
|
396
|
+
end
|
397
|
+
|
398
|
+
detail = LaunchDarkly::Impl::Evaluator.error_result(LaunchDarkly::EvaluationReason::ERROR_WRONG_TYPE, default_stage.to_s)
|
258
399
|
tracker = Impl::Migrations::OpTracker.new(@config.logger, key, flag, context, detail, default_stage)
|
259
|
-
return stage, tracker
|
260
|
-
end
|
261
400
|
|
262
|
-
|
263
|
-
|
401
|
+
LaunchDarkly::Impl::EvaluationWithHookResult.new(detail, {stage: default_stage, tracker: tracker})
|
402
|
+
end
|
264
403
|
|
265
|
-
[
|
404
|
+
[result.results[:stage], result.results[:tracker]]
|
266
405
|
end
|
267
406
|
|
268
407
|
#
|
@@ -502,7 +641,7 @@ module LaunchDarkly
|
|
502
641
|
|
503
642
|
#
|
504
643
|
# @param key [String]
|
505
|
-
# @param context [
|
644
|
+
# @param context [LDContext]
|
506
645
|
# @param default [Object]
|
507
646
|
#
|
508
647
|
# @return [Array<EvaluationDetail, [LaunchDarkly::Impl::Model::FeatureFlag, nil], [String, nil]>]
|
@@ -513,7 +652,7 @@ module LaunchDarkly
|
|
513
652
|
|
514
653
|
#
|
515
654
|
# @param key [String]
|
516
|
-
# @param context [
|
655
|
+
# @param context [LDContext]
|
517
656
|
# @param default [Object]
|
518
657
|
# @param with_reasons [Boolean]
|
519
658
|
#
|
@@ -530,7 +669,6 @@ module LaunchDarkly
|
|
530
669
|
return detail, nil, "no context provided"
|
531
670
|
end
|
532
671
|
|
533
|
-
context = Impl::Context::make_context(context)
|
534
672
|
unless context.valid?
|
535
673
|
@config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" }
|
536
674
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
data/lib/ldclient-rb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: launchdarkly-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.
|
4
|
+
version: 8.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-dynamodb
|
@@ -295,6 +295,7 @@ files:
|
|
295
295
|
- lib/ldclient-rb/impl/data_store.rb
|
296
296
|
- lib/ldclient-rb/impl/dependency_tracker.rb
|
297
297
|
- lib/ldclient-rb/impl/diagnostic_events.rb
|
298
|
+
- lib/ldclient-rb/impl/evaluation_with_hook_result.rb
|
298
299
|
- lib/ldclient-rb/impl/evaluator.rb
|
299
300
|
- lib/ldclient-rb/impl/evaluator_bucketing.rb
|
300
301
|
- lib/ldclient-rb/impl/evaluator_helpers.rb
|