launchdarkly-server-sdk 8.6.0 → 8.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a732ff00a5e21bfc907dff44adc5bd3f1651bd80c2483273a1a9730f443448f
4
- data.tar.gz: 1ca26bf847c2387ed585e07b18c6aa42a78496110a7bf9643751a0e2b04d2908
3
+ metadata.gz: d13d9414f9f1c60e2af1e2505cc40005da11facb98f362f6ce7c5575f92a9596
4
+ data.tar.gz: 94b1b3fd7e9e6f3c685f0b42c2fd798c72445df71d6e436426a89341b864bbeb
5
5
  SHA512:
6
- metadata.gz: 901e6f7562f0861916bf06fd07088561713579633cc5e052ae5cf86c6acdbdd0497b55e273c2f92244a5653d0fa806c9776f92a853bad22232634489c630f85d
7
- data.tar.gz: a325418b21c256a8b2d846d187e8bc91ba1e323955f460a418732b77f30354b64fc2dbfdadd04c4be4a41217779a4f217d522799ace4ce46a3ebc5019baf060c
6
+ metadata.gz: eafd096ae3a4257855b762e2a267560fcec2b0e96c976fff4a6aa69e113dfe178888637ea27deb963d70df2dd9933503304c010f9e96e17c2ff68b6697d976f1
7
+ data.tar.gz: 1ec423bf28d9bf8b6f15f0a0deb9e6b24b175fdb0f9ebc1b25987d4335ceaedfd91532539aaaf1cea9f85c954739707b971642619b40fa9cbce66f3168ed527d
@@ -65,6 +65,7 @@ module LaunchDarkly
65
65
  @all_attributes_private = opts[:all_attributes_private] || false
66
66
  @private_attributes = opts[:private_attributes] || []
67
67
  @send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
68
+ @compress_events = opts.has_key?(:compress_events) ? opts[:compress_events] : Config.default_compress_events
68
69
  @context_keys_capacity = opts[:context_keys_capacity] || Config.default_context_keys_capacity
69
70
  @context_keys_flush_interval = opts[:context_keys_flush_interval] || Config.default_context_keys_flush_interval
70
71
  @data_source = opts[:data_source]
@@ -76,7 +77,7 @@ module LaunchDarkly
76
77
  @socket_factory = opts[:socket_factory]
77
78
  @big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
78
79
  @application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
79
- @payload_filter_key = opts[:payload_filter_key]
80
+ @payload_filter_key = LaunchDarkly::Impl::Util.validate_payload_filter_key(opts[:payload_filter_key] , @logger)
80
81
  @hooks = (opts[:hooks] || []).keep_if { |hook| hook.is_a? Interfaces::Hooks::Hook }
81
82
  @omit_anonymous_contexts = opts.has_key?(:omit_anonymous_contexts) && opts[:omit_anonymous_contexts]
82
83
  @data_source_update_sink = nil
@@ -254,6 +255,17 @@ module LaunchDarkly
254
255
  #
255
256
  attr_reader :send_events
256
257
 
258
+ #
259
+ # Should the event payload sent to LaunchDarkly use gzip compression. By default this is false to prevent backward
260
+ # breaking compatibility issues with older versions of the relay proxy.
261
+ #
262
+ # Customers not using the relay proxy are strongly encouraged to enable this feature to reduce egress bandwidth
263
+ # cost.
264
+ #
265
+ # @return [Boolean]
266
+ #
267
+ attr_reader :compress_events
268
+
257
269
  #
258
270
  # The number of context keys that the event processor can remember at any one time. This reduces the
259
271
  # amount of duplicate context details sent in analytics events.
@@ -539,6 +551,14 @@ module LaunchDarkly
539
551
  true
540
552
  end
541
553
 
554
+ #
555
+ # The default value for {#compress_events}.
556
+ # @return [Boolean] false
557
+ #
558
+ def self.default_compress_events
559
+ false
560
+ end
561
+
542
562
  #
543
563
  # The default value for {#context_keys_capacity}.
544
564
  # @return [Integer] 1000
@@ -513,13 +513,13 @@ module LaunchDarkly
513
513
  evaluation: {
514
514
  key: event.key,
515
515
  value: event.evaluation.value,
516
+ reason: event.evaluation.reason,
516
517
  },
517
518
  }
518
519
 
519
520
  out[:evaluation][:version] = event.version unless event.version.nil?
520
521
  out[:evaluation][:default] = event.default unless event.default.nil?
521
522
  out[:evaluation][:variation] = event.evaluation.variation_index unless event.evaluation.variation_index.nil?
522
- out[:evaluation][:reason] = event.evaluation.reason unless event.evaluation.reason.nil?
523
523
  out[:samplingRatio] = event.sampling_ratio unless event.sampling_ratio.nil? || event.sampling_ratio == 1
524
524
 
525
525
  measurements = []
@@ -38,6 +38,7 @@ module LaunchDarkly
38
38
  meta[:version] = flag_state[:version]
39
39
  end
40
40
 
41
+ meta[:prerequisites] = flag_state[:prerequisites] unless flag_state[:prerequisites].nil? || flag_state[:prerequisites].empty?
41
42
  meta[:variation] = flag_state[:variation] unless flag_state[:variation].nil?
42
43
  meta[:trackEvents] = true if flag_state[:trackEvents]
43
44
  meta[:trackReason] = true if flag_state[:trackReason]
@@ -4,6 +4,56 @@ require "ldclient-rb/interfaces"
4
4
  module LaunchDarkly
5
5
  module Impl
6
6
  module DataStore
7
+
8
+ class DataKind
9
+ FEATURES = "features".freeze
10
+ SEGMENTS = "segments".freeze
11
+
12
+ FEATURE_PREREQ_FN = lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }.freeze
13
+
14
+ attr_reader :namespace
15
+ attr_reader :priority
16
+
17
+ #
18
+ # @param namespace [String]
19
+ # @param priority [Integer]
20
+ #
21
+ def initialize(namespace:, priority:)
22
+ @namespace = namespace
23
+ @priority = priority
24
+ end
25
+
26
+ #
27
+ # Maintain the same behavior when these data kinds were standard ruby hashes.
28
+ #
29
+ # @param key [Symbol]
30
+ # @return [Object]
31
+ #
32
+ def [](key)
33
+ return priority if key == :priority
34
+ return namespace if key == :namespace
35
+ return get_dependency_keys_fn() if key == :get_dependency_keys
36
+ nil
37
+ end
38
+
39
+ #
40
+ # Retrieve the dependency keys for a particular data kind. Right now, this is only defined for flags.
41
+ #
42
+ def get_dependency_keys_fn()
43
+ return nil unless @namespace == FEATURES
44
+
45
+ FEATURE_PREREQ_FN
46
+ end
47
+
48
+ def eql?(other)
49
+ other.is_a?(DataKind) && namespace == other.namespace && priority == other.priority
50
+ end
51
+
52
+ def hash
53
+ [namespace, priority].hash
54
+ end
55
+ end
56
+
7
57
  class StatusProvider
8
58
  include LaunchDarkly::Interfaces::DataStore::StatusProvider
9
59
 
@@ -32,8 +32,16 @@ module LaunchDarkly
32
32
  def initialize(original_flag)
33
33
  @prereq_stack = EvaluatorStack.new(original_flag.key)
34
34
  @segment_stack = EvaluatorStack.new(nil)
35
+ @prerequisites = []
36
+ @depth = 0
35
37
  end
36
38
 
39
+ def record_evaluated_prereq_key(key)
40
+ @prerequisites.push(key) if @depth.zero?
41
+ end
42
+
43
+ attr_accessor :depth
44
+ attr_reader :prerequisites
37
45
  attr_reader :prereq_stack
38
46
  attr_reader :segment_stack
39
47
  end
@@ -135,7 +143,8 @@ module LaunchDarkly
135
143
  #
136
144
  # @param flag [LaunchDarkly::Impl::Model::FeatureFlag] the flag
137
145
  # @param context [LaunchDarkly::LDContext] the evaluation context
138
- # @return [EvalResult] the evaluation result
146
+ # @return [Array<EvalResult, EvaluatorState>] the evaluation result and a state object that may be used for
147
+ # inspecting the evaluation process
139
148
  def evaluate(flag, context)
140
149
  state = EvaluatorState.new(flag)
141
150
 
@@ -145,11 +154,11 @@ module LaunchDarkly
145
154
  rescue EvaluationException => exn
146
155
  LaunchDarkly::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
147
156
  result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(exn.error_kind))
148
- return result
157
+ return result, state
149
158
  rescue => exn
150
159
  LaunchDarkly::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
151
160
  result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
152
- return result
161
+ return result, state
153
162
  end
154
163
 
155
164
  unless result.big_segments_status.nil?
@@ -159,7 +168,7 @@ module LaunchDarkly
159
168
  detail.reason.with_big_segments_status(result.big_segments_status))
160
169
  end
161
170
  result.detail = detail
162
- result
171
+ [result, state]
163
172
  end
164
173
 
165
174
  # @param segment [LaunchDarkly::Impl::Model::Segment]
@@ -223,13 +232,16 @@ module LaunchDarkly
223
232
  )
224
233
  end
225
234
 
235
+ state.record_evaluated_prereq_key(prereq_key)
226
236
  prereq_flag = @get_flag.call(prereq_key)
227
237
 
228
238
  if prereq_flag.nil?
229
239
  @logger.error { "[LDClient] Could not retrieve prerequisite flag \"#{prereq_key}\" when evaluating \"#{flag.key}\"" }
230
240
  prereq_ok = false
231
241
  else
242
+ state.depth += 1
232
243
  prereq_res = eval_internal(prereq_flag, context, eval_result, state)
244
+ state.depth -= 1
233
245
  # Note that if the prerequisite flag is off, we don't consider it a match no matter what its
234
246
  # off variation was. But we still need to evaluate it in order to generate an event.
235
247
  if !prereq_flag.on || prereq_res.variation_index != prerequisite.variation
@@ -2,6 +2,8 @@ require "ldclient-rb/impl/unbounded_pool"
2
2
 
3
3
  require "securerandom"
4
4
  require "http"
5
+ require "stringio"
6
+ require "zlib"
5
7
 
6
8
  module LaunchDarkly
7
9
  module Impl
@@ -42,14 +44,24 @@ module LaunchDarkly
42
44
  @logger.debug { "[LDClient] sending #{description}: #{event_data}" }
43
45
  headers = {}
44
46
  headers["content-type"] = "application/json"
47
+ headers["content-encoding"] = "gzip" if @config.compress_events
45
48
  Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| headers[k] = v }
46
49
  unless is_diagnostic
47
50
  headers["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
48
51
  headers["X-LaunchDarkly-Payload-ID"] = payload_id
49
52
  end
53
+
54
+ body = event_data
55
+ if @config.compress_events
56
+ gzip = Zlib::GzipWriter.new(StringIO.new)
57
+ gzip << event_data
58
+
59
+ body = gzip.close.string
60
+ end
61
+
50
62
  response = http_client.request("POST", uri, {
51
63
  headers: headers,
52
- body: event_data,
64
+ body: body,
53
65
  })
54
66
  rescue StandardError => exn
55
67
  @logger.warn { "[LDClient] Error sending events: #{exn.inspect}." }
@@ -75,6 +75,21 @@ module LaunchDarkly
75
75
  version: validate_application_value(app[:version], :version, logger),
76
76
  }
77
77
  end
78
+
79
+ #
80
+ # @param value [String, nil]
81
+ # @param logger [Logger]
82
+ # @return [String, nil]
83
+ #
84
+ def self.validate_payload_filter_key(value, logger)
85
+ return nil if value.nil?
86
+ return value if value.is_a?(String) && /^[a-zA-Z0-9][._\-a-zA-Z0-9]*$/.match?(value)
87
+
88
+ logger.warn {
89
+ "Invalid payload filter configured, full environment will be fetched. Ensure the filter key is not empty and was copied correctly from LaunchDarkly settings."
90
+ }
91
+ nil
92
+ end
78
93
  end
79
94
  end
80
95
  end
@@ -11,17 +11,10 @@ module LaunchDarkly
11
11
  # to ensure data consistency during non-atomic updates.
12
12
 
13
13
  # @private
14
- FEATURES = {
15
- namespace: "features",
16
- priority: 1, # that is, features should be stored after segments
17
- get_dependency_keys: lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } },
18
- }.freeze
14
+ FEATURES = Impl::DataStore::DataKind.new(namespace: "features", priority: 1).freeze
19
15
 
20
16
  # @private
21
- SEGMENTS = {
22
- namespace: "segments",
23
- priority: 0,
24
- }.freeze
17
+ SEGMENTS = Impl::DataStore::DataKind.new(namespace: "segments", priority: 0).freeze
25
18
 
26
19
  # @private
27
20
  ALL_KINDS = [FEATURES, SEGMENTS].freeze
@@ -546,7 +546,8 @@ module LaunchDarkly
546
546
  next
547
547
  end
548
548
  begin
549
- detail = @evaluator.evaluate(f, context).detail
549
+ (eval_result, eval_state) = @evaluator.evaluate(f, context)
550
+ detail = eval_result.detail
550
551
  rescue => exn
551
552
  detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
552
553
  Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
@@ -558,6 +559,7 @@ module LaunchDarkly
558
559
  value: detail.value,
559
560
  variation: detail.variation_index,
560
561
  reason: detail.reason,
562
+ prerequisites: eval_state.prerequisites,
561
563
  version: f[:version],
562
564
  trackEvents: f[:trackEvents] || requires_experiment_data,
563
565
  trackReason: requires_experiment_data,
@@ -705,7 +707,7 @@ module LaunchDarkly
705
707
  end
706
708
 
707
709
  begin
708
- res = @evaluator.evaluate(feature, context)
710
+ (res, _) = @evaluator.evaluate(feature, context)
709
711
  unless res.prereq_evals.nil?
710
712
  res.prereq_evals.each do |prereq_eval|
711
713
  record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag, context, prereq_eval.detail, with_reasons)
@@ -77,11 +77,6 @@ module LaunchDarkly
77
77
  def self.add_payload_filter_key(uri, config)
78
78
  return uri if config.payload_filter_key.nil?
79
79
 
80
- unless config.payload_filter_key.is_a?(String) && !config.payload_filter_key.empty?
81
- config.logger.warn { "[LDClient] Filter key must be a non-empty string. No filtering will be applied." }
82
- return uri
83
- end
84
-
85
80
  begin
86
81
  parsed = URI.parse(uri)
87
82
  new_query_params = URI.decode_www_form(String(parsed.query)) << ["filter", config.payload_filter_key]
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "8.6.0" # x-release-please-version
2
+ VERSION = "8.8.2" # x-release-please-version
3
3
  end
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.6.0
4
+ version: 8.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-26 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -24,6 +24,26 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.57'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rexml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.3'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 3.3.7
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '3.3'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.3.7
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: bundler
29
49
  requirement: !ruby/object:Gem::Requirement
@@ -248,6 +268,20 @@ dependencies:
248
268
  - - "~>"
249
269
  - !ruby/object:Gem::Version
250
270
  version: 0.1.2
271
+ - !ruby/object:Gem::Dependency
272
+ name: zlib
273
+ requirement: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - "~>"
276
+ - !ruby/object:Gem::Version
277
+ version: '3.1'
278
+ type: :runtime
279
+ prerelease: false
280
+ version_requirements: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - "~>"
283
+ - !ruby/object:Gem::Version
284
+ version: '3.1'
251
285
  - !ruby/object:Gem::Dependency
252
286
  name: json
253
287
  requirement: !ruby/object:Gem::Requirement
@@ -376,7 +410,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
376
410
  - !ruby/object:Gem::Version
377
411
  version: '0'
378
412
  requirements: []
379
- rubygems_version: 3.5.11
413
+ rubygems_version: 3.5.22
380
414
  signing_key:
381
415
  specification_version: 4
382
416
  summary: LaunchDarkly SDK for Ruby