launchdarkly-server-sdk 8.11.3 → 8.12.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.
@@ -14,6 +14,30 @@ require "ldclient-rb/interfaces/data_system"
14
14
  module LaunchDarkly
15
15
  module Impl
16
16
  module DataSystem
17
+ #
18
+ # Represents the possible outcomes from consuming synchronizer results.
19
+ #
20
+ # Used by {FDv2#consume_synchronizer_results} to indicate what action the
21
+ # synchronizer loop should take next.
22
+ #
23
+ module SyncResult
24
+ # Temporarily move to the next synchronizer in the list.
25
+ # The current synchronizer remains available for future recovery.
26
+ FALLBACK = :fallback
27
+
28
+ # Return to the first synchronizer in the list.
29
+ # Used when recovery conditions are met on a fallback synchronizer.
30
+ RECOVER = :recover
31
+
32
+ # Permanently remove the current synchronizer from the list.
33
+ # Used for unrecoverable failures (OFF state, exceptions).
34
+ REMOVE = :remove
35
+
36
+ # Switch to the FDv1 protocol fallback.
37
+ # Replaces the synchronizer list with the FDv1 fallback synchronizer.
38
+ FDV1 = :fdv1
39
+ end
40
+
17
41
  # FDv2 is an implementation of the DataSystem interface that uses the Flag Delivery V2 protocol
18
42
  # for obtaining and keeping data up-to-date. Additionally, it operates with an optional persistent
19
43
  # store in read-only or read/write mode.
@@ -30,8 +54,7 @@ module LaunchDarkly
30
54
  @config = config
31
55
  @data_system_config = data_system_config
32
56
  @logger = config.logger
33
- @primary_synchronizer_builder = data_system_config.primary_synchronizer
34
- @secondary_synchronizer_builder = data_system_config.secondary_synchronizer
57
+ @synchronizer_builders = data_system_config.synchronizers || []
35
58
  @fdv1_fallback_synchronizer_builder = data_system_config.fdv1_fallback_synchronizer
36
59
  @disabled = @config.offline?
37
60
 
@@ -86,7 +109,7 @@ module LaunchDarkly
86
109
 
87
110
  # Track configuration
88
111
  @configured_with_data_sources = (@data_system_config.initializers && !@data_system_config.initializers.empty?) ||
89
- !@data_system_config.primary_synchronizer.nil?
112
+ !@synchronizer_builders.empty?
90
113
  end
91
114
 
92
115
  # (see DataSystem#start)
@@ -214,7 +237,7 @@ module LaunchDarkly
214
237
  return if @stop_event.set?
215
238
 
216
239
  begin
217
- initializer = initializer_builder.call(@sdk_key, @config)
240
+ initializer = initializer_builder.build(@sdk_key, @config)
218
241
  @logger.info { "[LDClient] Attempting to initialize via #{initializer.name}" }
219
242
 
220
243
  basis_result = initializer.fetch(@store)
@@ -246,8 +269,8 @@ module LaunchDarkly
246
269
  # @return [void]
247
270
  #
248
271
  def run_synchronizers
249
- # If no primary synchronizer configured, just set ready and return
250
- if @primary_synchronizer_builder.nil?
272
+ # If no synchronizers configured, just set ready and return
273
+ if @synchronizer_builders.empty?
251
274
  @ready_event.set
252
275
  return
253
276
  end
@@ -259,79 +282,64 @@ module LaunchDarkly
259
282
  end
260
283
 
261
284
  #
262
- # Synchronizer loop that manages primary/secondary/fallback synchronizers.
285
+ # Synchronizer loop that manages synchronizers and fallbacks.
263
286
  #
264
287
  # @return [void]
265
288
  #
266
289
  def synchronizer_loop
290
+ # Track the current index in the synchronizer array
291
+ current_index = 0
292
+
267
293
  begin
268
- while !@stop_event.set? && @primary_synchronizer_builder
269
- # Try primary synchronizer
294
+ while !@stop_event.set? && current_index < @synchronizer_builders.length
295
+ synchronizer_builder = @synchronizer_builders[current_index]
296
+ is_primary = current_index == 0
297
+
270
298
  begin
271
299
  @lock.synchronize do
272
- primary_sync = @primary_synchronizer_builder.call(@sdk_key, @config)
273
- if primary_sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator
274
- primary_sync.set_diagnostic_accumulator(@diagnostic_accumulator)
300
+ sync = synchronizer_builder.build(@sdk_key, @config)
301
+ if sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator
302
+ sync.set_diagnostic_accumulator(@diagnostic_accumulator)
275
303
  end
276
- @active_synchronizer = primary_sync
304
+ @active_synchronizer = sync
277
305
  end
278
306
 
279
- @logger.info { "[LDClient] Primary synchronizer #{@active_synchronizer.name} is starting" }
307
+ @logger.info { "[LDClient] Synchronizer[#{current_index}] #{@active_synchronizer.name} is starting" }
280
308
 
281
- remove_sync, fallback_v1 = consume_synchronizer_results(
282
- @active_synchronizer,
283
- method(:fallback_condition)
284
- )
309
+ sync_result = consume_synchronizer_results(@active_synchronizer, check_recovery: !is_primary)
285
310
 
286
- if remove_sync
287
- @primary_synchronizer_builder = fallback_v1 ? @fdv1_fallback_synchronizer_builder : @secondary_synchronizer_builder
288
- @secondary_synchronizer_builder = nil
311
+ break if @stop_event.set?
289
312
 
290
- if @primary_synchronizer_builder.nil?
291
- @logger.warn { "[LDClient] No more synchronizers available" }
292
- @data_source_status_provider.update_status(
293
- LaunchDarkly::Interfaces::DataSource::Status::OFF,
294
- @data_source_status_provider.status.last_error
295
- )
296
- break
313
+ case sync_result
314
+ when SyncResult::FDV1
315
+ if @fdv1_fallback_synchronizer_builder
316
+ @synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
317
+ current_index = 0
318
+ next
297
319
  end
320
+ # No FDv1 fallback configured, treat as regular fallback
321
+ current_index += 1
322
+ when SyncResult::RECOVER
323
+ @logger.info { "[LDClient] Recovery condition met, returning to primary synchronizer" }
324
+ current_index = 0
325
+ when SyncResult::REMOVE
326
+ @logger.info { "[LDClient] Removing synchronizer from list due to permanent failure" }
327
+ @synchronizer_builders.delete_at(current_index)
298
328
  else
299
329
  @logger.info { "[LDClient] Fallback condition met" }
330
+ current_index += 1
300
331
  end
301
332
 
302
- break if @stop_event.set?
303
-
304
- next if @secondary_synchronizer_builder.nil?
305
-
306
- @lock.synchronize do
307
- secondary_sync = @secondary_synchronizer_builder.call(@sdk_key, @config)
308
- if secondary_sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator
309
- secondary_sync.set_diagnostic_accumulator(@diagnostic_accumulator)
310
- end
311
- @logger.info { "[LDClient] Secondary synchronizer #{secondary_sync.name} is starting" }
312
- @active_synchronizer = secondary_sync
313
- end
333
+ current_index = 0 if current_index >= @synchronizer_builders.length
314
334
 
315
- remove_sync, fallback_v1 = consume_synchronizer_results(
316
- @active_synchronizer,
317
- method(:recovery_condition)
318
- )
319
-
320
- if remove_sync
321
- @secondary_synchronizer_builder = nil
322
- @primary_synchronizer_builder = @fdv1_fallback_synchronizer_builder if fallback_v1
323
-
324
- if @primary_synchronizer_builder.nil?
325
- @logger.warn { "[LDClient] No more synchronizers available" }
326
- @data_source_status_provider.update_status(
327
- LaunchDarkly::Interfaces::DataSource::Status::OFF,
328
- @data_source_status_provider.status.last_error
329
- )
330
- break
331
- end
335
+ if @synchronizer_builders.length == 0
336
+ @logger.warn { "[LDClient] No more synchronizers available" }
337
+ @data_source_status_provider.update_status(
338
+ LaunchDarkly::Interfaces::DataSource::Status::OFF,
339
+ @data_source_status_provider.status.last_error
340
+ )
341
+ break
332
342
  end
333
-
334
- @logger.info { "[LDClient] Recovery condition met, returning to primary synchronizer" }
335
343
  rescue => e
336
344
  @logger.error { "[LDClient] Failed to build synchronizer: #{e.message}" }
337
345
  break
@@ -353,10 +361,10 @@ module LaunchDarkly
353
361
  # Consume results from a synchronizer until a condition is met or it fails.
354
362
  #
355
363
  # @param synchronizer [Object] The synchronizer
356
- # @param condition_func [Proc] Function to check if condition is met
357
- # @return [Array(Boolean, Boolean)] [should_remove_sync, fallback_to_fdv1]
364
+ # @param check_recovery [Boolean] Whether to check recovery condition (healthy too long)
365
+ # @return [Symbol] One of {SyncResult::FALLBACK}, {SyncResult::RECOVER}, {SyncResult::REMOVE}, or {SyncResult::FDV1}
358
366
  #
359
- def consume_synchronizer_results(synchronizer, condition_func)
367
+ def consume_synchronizer_results(synchronizer, check_recovery: false)
360
368
  action_queue = Queue.new
361
369
  timer = LaunchDarkly::Impl::RepeatingTask.new(10, 10, -> { action_queue.push("check") }, @logger, "FDv2-sync-cond-timer")
362
370
 
@@ -384,13 +392,14 @@ module LaunchDarkly
384
392
  if update == "check"
385
393
  # Check condition periodically
386
394
  current_status = @data_source_status_provider.status
387
- return [false, false] if condition_func.call(current_status)
395
+ return SyncResult::RECOVER if check_recovery && recovery_condition(current_status)
396
+ return SyncResult::FALLBACK if fallback_condition(current_status)
388
397
  end
389
398
  next
390
399
  end
391
400
 
392
401
  @logger.info { "[LDClient] Synchronizer #{synchronizer.name} update: #{update.state}" }
393
- return [false, false] if @stop_event.set?
402
+ return SyncResult::FALLBACK if @stop_event.set?
394
403
 
395
404
  # Handle the update
396
405
  @store.apply(update.change_set, true) if update.change_set
@@ -401,26 +410,24 @@ module LaunchDarkly
401
410
  # Update status
402
411
  @data_source_status_provider.update_status(update.state, update.error)
403
412
 
404
- # Check if we should revert to FDv1 immediately
405
- return [true, true] if update.revert_to_fdv1
413
+ return SyncResult::FDV1 if update.revert_to_fdv1
406
414
 
407
- # Check for OFF state indicating permanent failure
408
- return [true, false] if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
415
+ return SyncResult::REMOVE if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
409
416
  end
410
417
  rescue => e
411
418
  @logger.error { "[LDClient] Error consuming synchronizer results: #{e.message}" }
412
- return [true, false]
419
+ return SyncResult::REMOVE
413
420
  ensure
414
421
  synchronizer.stop
415
422
  timer.stop
416
423
  sync_reader.join(0.5) if sync_reader.alive?
417
424
  end
418
425
 
419
- [true, false]
426
+ SyncResult::REMOVE
420
427
  end
421
428
 
422
429
  #
423
- # Determine if we should fallback to secondary synchronizer.
430
+ # Determine if we should fallback to the next synchronizer.
424
431
  #
425
432
  # @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
426
433
  # @return [Boolean] true if fallback condition is met
@@ -435,20 +442,14 @@ module LaunchDarkly
435
442
  end
436
443
 
437
444
  #
438
- # Determine if we should try to recover to primary synchronizer.
445
+ # Determine if we should recover to the primary synchronizer.
439
446
  #
440
447
  # @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
441
- # @return [Boolean] true if recovery condition is met
448
+ # @return [Boolean] true if recovery condition is met (healthy for too long)
442
449
  #
443
450
  def recovery_condition(status)
444
- interrupted_at_runtime = status.state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED &&
445
- Time.now - status.state_since > 60 # 1 minute
446
- healthy_for_too_long = status.state == LaunchDarkly::Interfaces::DataSource::Status::VALID &&
451
+ status.state == LaunchDarkly::Interfaces::DataSource::Status::VALID &&
447
452
  Time.now - status.state_since > 300 # 5 minutes
448
- cannot_initialize = status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING &&
449
- Time.now - status.state_since > 10 # 10 seconds
450
-
451
- interrupted_at_runtime || healthy_for_too_long || cannot_initialize
452
453
  end
453
454
 
454
455
  #
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+ module DataSystem
6
+ #
7
+ # HttpConfigOptions contains HTTP connection configuration settings.
8
+ # This class is created by data source builders and passed to v2 Requesters/DataSources.
9
+ #
10
+ class HttpConfigOptions
11
+ # Generic HTTP defaults - base URIs live in the respective builders
12
+ DEFAULT_READ_TIMEOUT = 10
13
+ DEFAULT_CONNECT_TIMEOUT = 2
14
+
15
+ attr_reader :base_uri, :socket_factory, :read_timeout, :connect_timeout
16
+
17
+ #
18
+ # @param base_uri [String] The base URI for HTTP requests
19
+ # @param socket_factory [Object, nil] Optional socket factory for custom connections
20
+ # @param read_timeout [Float, nil] Read timeout in seconds (defaults to DEFAULT_READ_TIMEOUT)
21
+ # @param connect_timeout [Float, nil] Connect timeout in seconds (defaults to DEFAULT_CONNECT_TIMEOUT)
22
+ #
23
+ def initialize(base_uri:, socket_factory: nil, read_timeout: nil, connect_timeout: nil)
24
+ @base_uri = base_uri
25
+ @socket_factory = socket_factory
26
+ @read_timeout = read_timeout || DEFAULT_READ_TIMEOUT
27
+ @connect_timeout = connect_timeout || DEFAULT_CONNECT_TIMEOUT
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -4,6 +4,7 @@ require "ldclient-rb/interfaces"
4
4
  require "ldclient-rb/interfaces/data_system"
5
5
  require "ldclient-rb/impl/data_system"
6
6
  require "ldclient-rb/impl/data_system/protocolv2"
7
+ require "ldclient-rb/impl/data_system/data_source_builder_common"
7
8
  require "ldclient-rb/impl/data_source/requestor"
8
9
  require "ldclient-rb/impl/util"
9
10
  require "concurrent"
@@ -17,8 +18,8 @@ module LaunchDarkly
17
18
  FDV2_POLLING_ENDPOINT = "/sdk/poll"
18
19
  FDV1_POLLING_ENDPOINT = "/sdk/latest-all"
19
20
 
20
- LD_ENVID_HEADER = "x-launchdarkly-env-id"
21
- LD_FD_FALLBACK_HEADER = "x-launchdarkly-fd-fallback"
21
+ LD_ENVID_HEADER = "X-LD-EnvID"
22
+ LD_FD_FALLBACK_HEADER = "X-LD-FD-Fallback"
22
23
 
23
24
  #
24
25
  # Requester protocol for polling data source
@@ -93,8 +94,6 @@ module LaunchDarkly
93
94
  #
94
95
  def sync(ss)
95
96
  @logger.info { "[LDClient] Starting PollingDataSourceV2 synchronizer" }
96
- @stop.reset
97
- @interrupt_event.reset
98
97
 
99
98
  until @stop.set?
100
99
  result = @requester.fetch(ss.selector)
@@ -251,14 +250,15 @@ module LaunchDarkly
251
250
 
252
251
  #
253
252
  # @param sdk_key [String]
254
- # @param config [LaunchDarkly::Config]
253
+ # @param http_config [HttpConfigOptions] HTTP connection settings
254
+ # @param config [LaunchDarkly::Config] Used for global header settings
255
255
  #
256
- def initialize(sdk_key, config)
256
+ def initialize(sdk_key, http_config, config)
257
257
  @etag = nil
258
258
  @config = config
259
259
  @sdk_key = sdk_key
260
- @poll_uri = config.base_uri + FDV2_POLLING_ENDPOINT
261
- @http_client = Impl::Util.new_http_client(config.base_uri, config)
260
+ @poll_uri = http_config.base_uri + FDV2_POLLING_ENDPOINT
261
+ @http_client = Impl::Util.new_http_client(http_config)
262
262
  .use(:auto_inflate)
263
263
  .headers("Accept-Encoding" => "gzip")
264
264
  end
@@ -342,14 +342,15 @@ module LaunchDarkly
342
342
 
343
343
  #
344
344
  # @param sdk_key [String]
345
- # @param config [LaunchDarkly::Config]
345
+ # @param http_config [HttpConfigOptions] HTTP connection settings
346
+ # @param config [LaunchDarkly::Config] Used for global header settings and payload_filter_key
346
347
  #
347
- def initialize(sdk_key, config)
348
+ def initialize(sdk_key, http_config, config)
348
349
  @etag = nil
349
350
  @config = config
350
351
  @sdk_key = sdk_key
351
- @poll_uri = config.base_uri + FDV1_POLLING_ENDPOINT
352
- @http_client = Impl::Util.new_http_client(config.base_uri, config)
352
+ @poll_uri = http_config.base_uri + FDV1_POLLING_ENDPOINT
353
+ @http_client = Impl::Util.new_http_client(http_config)
353
354
  .use(:auto_inflate)
354
355
  .headers("Accept-Encoding" => "gzip")
355
356
  end
@@ -440,7 +441,7 @@ module LaunchDarkly
440
441
 
441
442
  next unless event[:event]
442
443
 
443
- case event[:event]
444
+ case event[:event].to_sym
444
445
  when LaunchDarkly::Interfaces::DataSystem::EventName::SERVER_INTENT
445
446
  begin
446
447
  server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.from_h(event[:data])
@@ -518,7 +519,7 @@ module LaunchDarkly
518
519
  version = flag_or_segment[:version]
519
520
  return LaunchDarkly::Result.fail("Invalid format: #{key} does not have a version set") if version.nil?
520
521
 
521
- builder.add_put(kind, key.to_s, version, flag_or_segment)
522
+ builder.add_put(kind, key, version, flag_or_segment)
522
523
  end
523
524
  end
524
525
 
@@ -529,14 +530,24 @@ module LaunchDarkly
529
530
  # Builder for a PollingDataSource.
530
531
  #
531
532
  class PollingDataSourceBuilder
533
+ include DataSourceBuilderCommon
534
+
535
+ DEFAULT_BASE_URI = "https://sdk.launchdarkly.com"
536
+ DEFAULT_POLL_INTERVAL = 30
537
+
538
+ def initialize
539
+ @requester = nil
540
+ end
541
+
532
542
  #
533
- # @param sdk_key [String]
534
- # @param config [LaunchDarkly::Config]
543
+ # Sets the polling interval in seconds.
535
544
  #
536
- def initialize(sdk_key, config)
537
- @sdk_key = sdk_key
538
- @config = config
539
- @requester = nil
545
+ # @param secs [Float] Polling interval in seconds
546
+ # @return [PollingDataSourceBuilder]
547
+ #
548
+ def poll_interval(secs)
549
+ @poll_interval = secs
550
+ self
540
551
  end
541
552
 
542
553
  #
@@ -553,11 +564,14 @@ module LaunchDarkly
553
564
  #
554
565
  # Builds the PollingDataSource with the configured parameters.
555
566
  #
567
+ # @param sdk_key [String]
568
+ # @param config [LaunchDarkly::Config]
556
569
  # @return [PollingDataSource]
557
570
  #
558
- def build
559
- requester = @requester || HTTPPollingRequester.new(@sdk_key, @config)
560
- PollingDataSource.new(@config.poll_interval, requester, @config.logger)
571
+ def build(sdk_key, config)
572
+ http_opts = build_http_config
573
+ requester = @requester || HTTPPollingRequester.new(sdk_key, http_opts, config)
574
+ PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger)
561
575
  end
562
576
  end
563
577
 
@@ -565,14 +579,24 @@ module LaunchDarkly
565
579
  # Builder for an FDv1 PollingDataSource.
566
580
  #
567
581
  class FDv1PollingDataSourceBuilder
582
+ include DataSourceBuilderCommon
583
+
584
+ DEFAULT_BASE_URI = "https://sdk.launchdarkly.com"
585
+ DEFAULT_POLL_INTERVAL = 30
586
+
587
+ def initialize
588
+ @requester = nil
589
+ end
590
+
568
591
  #
569
- # @param sdk_key [String]
570
- # @param config [LaunchDarkly::Config]
592
+ # Sets the polling interval in seconds.
571
593
  #
572
- def initialize(sdk_key, config)
573
- @sdk_key = sdk_key
574
- @config = config
575
- @requester = nil
594
+ # @param secs [Float] Polling interval in seconds
595
+ # @return [FDv1PollingDataSourceBuilder]
596
+ #
597
+ def poll_interval(secs)
598
+ @poll_interval = secs
599
+ self
576
600
  end
577
601
 
578
602
  #
@@ -589,11 +613,14 @@ module LaunchDarkly
589
613
  #
590
614
  # Builds the PollingDataSource with the configured parameters.
591
615
  #
616
+ # @param sdk_key [String]
617
+ # @param config [LaunchDarkly::Config]
592
618
  # @return [PollingDataSource]
593
619
  #
594
- def build
595
- requester = @requester || HTTPFDv1PollingRequester.new(@sdk_key, @config)
596
- PollingDataSource.new(@config.poll_interval, requester, @config.logger)
620
+ def build(sdk_key, config)
621
+ http_opts = build_http_config
622
+ requester = @requester || HTTPFDv1PollingRequester.new(sdk_key, http_opts, config)
623
+ PollingDataSource.new(@poll_interval || DEFAULT_POLL_INTERVAL, requester, config.logger)
597
624
  end
598
625
  end
599
626
  end
@@ -22,13 +22,13 @@ module LaunchDarkly
22
22
  # @return [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
23
23
  attr_reader :kind
24
24
 
25
- # @return [String] The key
25
+ # @return [Symbol] The key
26
26
  attr_reader :key
27
27
 
28
28
  #
29
29
  # @param version [Integer] The version
30
30
  # @param kind [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
31
- # @param key [String] The key
31
+ # @param key [Symbol] The key
32
32
  #
33
33
  def initialize(version:, kind:, key:)
34
34
  @version = version
@@ -39,7 +39,7 @@ module LaunchDarkly
39
39
  #
40
40
  # Returns the event name.
41
41
  #
42
- # @return [String]
42
+ # @return [Symbol]
43
43
  #
44
44
  def name
45
45
  LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT
@@ -72,7 +72,7 @@ module LaunchDarkly
72
72
 
73
73
  raise ArgumentError, "Missing required fields in DeleteObject" if version.nil? || kind.nil? || key.nil?
74
74
 
75
- new(version: version, kind: kind, key: key)
75
+ new(version: version, kind: kind, key: key.to_sym)
76
76
  end
77
77
  end
78
78
 
@@ -89,7 +89,7 @@ module LaunchDarkly
89
89
  # @return [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
90
90
  attr_reader :kind
91
91
 
92
- # @return [String] The key
92
+ # @return [Symbol] The key
93
93
  attr_reader :key
94
94
 
95
95
  # @return [Hash] The object data
@@ -98,7 +98,7 @@ module LaunchDarkly
98
98
  #
99
99
  # @param version [Integer] The version
100
100
  # @param kind [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
101
- # @param key [String] The key
101
+ # @param key [Symbol] The key
102
102
  # @param object [Hash] The object data
103
103
  #
104
104
  def initialize(version:, kind:, key:, object:)
@@ -111,7 +111,7 @@ module LaunchDarkly
111
111
  #
112
112
  # Returns the event name.
113
113
  #
114
- # @return [String]
114
+ # @return [Symbol]
115
115
  #
116
116
  def name
117
117
  LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT
@@ -146,7 +146,7 @@ module LaunchDarkly
146
146
 
147
147
  raise ArgumentError, "Missing required fields in PutObject" if version.nil? || kind.nil? || key.nil? || object_data.nil?
148
148
 
149
- new(version: version, kind: kind, key: key, object: object_data)
149
+ new(version: version, kind: kind, key: key.to_sym, object: object_data)
150
150
  end
151
151
  end
152
152