Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/ldclient-rb/impl/data_system/polling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ def self.polling_payload_to_changeset(data)
builder.start(server_intent.payload.code)

when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT
next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data])

begin
put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(event[:data])
rescue ArgumentError => e
Expand All @@ -506,6 +508,8 @@ def self.polling_payload_to_changeset(data)
builder.add_put(put.kind, put.key, put.version, put.object)

when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT
next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data])

begin
delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(event[:data])
rescue ArgumentError => e
Expand Down
21 changes: 21 additions & 0 deletions lib/ldclient-rb/impl/data_system/protocolv2.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'json'
require 'ldclient-rb/interfaces/data_system'

module LaunchDarkly
module Impl
Expand All @@ -9,6 +10,26 @@ module ProtocolV2
# LaunchDarkly data system version 2 (FDv2).
#

SUPPORTED_KINDS = [
LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
].freeze

#
# Returns true if put-object or delete-object data names a kind this SDK
# does not support. Such objects are ignored so that payloads carrying
# newer kinds can still be applied.
#
# @param data [Hash] The hash representation of the event data
# @return [Boolean]
#
def self.unsupported_kind?(data)
return false unless data.is_a?(Hash)

kind = data[:kind]
!kind.nil? && !SUPPORTED_KINDS.include?(kind)
end

#
# DeleteObject specifies the deletion of a particular object.
#
Expand Down
14 changes: 10 additions & 4 deletions lib/ldclient-rb/impl/data_system/streaming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,19 @@ def stop
nil

when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT
put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(JSON.parse(message.data, symbolize_names: true))
change_set_builder.add_put(put.kind, put.key, put.version, put.object)
data = JSON.parse(message.data, symbolize_names: true)
unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data)
put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(data)
change_set_builder.add_put(put.kind, put.key, put.version, put.object)
end
nil

when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT
delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(JSON.parse(message.data, symbolize_names: true))
change_set_builder.add_delete(delete_object.kind, delete_object.key, delete_object.version)
data = JSON.parse(message.data, symbolize_names: true)
unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data)
delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(data)
change_set_builder.add_delete(delete_object.kind, delete_object.key, delete_object.version)
end
nil

when LaunchDarkly::Interfaces::DataSystem::EventName::GOODBYE
Expand Down
22 changes: 22 additions & 0 deletions spec/impl/data_system/polling_payload_parsing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ module DataSystem
expect(result.value.selector.version).to eq(461)
end

it "ignores put and delete objects of unknown kinds" do
payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "put-object","data": {"key":"sampleflag","kind":"flag","version":461,"object":{"key":"sampleflag","version":112}}},{"event": "put-object","data": {"key":"sampleskill","kind":"some-future-kind","version":461,"object":{"foo":1}}},{"event": "delete-object","data": {"key":"sampleskill","kind":"some-future-kind","version":461}},{"event": "delete-object","data": {"key":"samplesegment","kind":"segment","version":461}},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength

# Parse a payload that mixes known and unknown object kinds.
result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true))

# The payload completes with only the known objects.
expect(result.success?).to eq(true)
expect(result.value.intent_code).to eq(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL)
expect(result.value.changes.length).to eq(2)

expect(result.value.changes[0].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT)
expect(result.value.changes[0].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG)
expect(result.value.changes[0].key).to eq(:sampleflag)

expect(result.value.changes[1].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE)
expect(result.value.changes[1].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT)
expect(result.value.changes[1].key).to eq(:samplesegment)

expect(result.value.selector.state).to eq("(p:5A46PZ79FQ9D08YYKT79DECDNV:461)")
end

it "handles invalid put object" do
payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "put-object","data": {}},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength
result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true))
Expand Down
66 changes: 66 additions & 0 deletions spec/impl/data_system/streaming_synchronizer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,72 @@ def initialize(type, data = nil)
expect(update.change_set.changes[0].version).to eq(101)
end

it "ignores put and delete objects of unknown kinds" do
server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new(
payload: LaunchDarkly::Interfaces::DataSystem::Payload.new(
id: "id",
target: 300,
code: LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL,
reason: "cant-catchup"
)
)
known_put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.new(
version: 100,
kind: LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG,
key: "flagkey",
object: { key: "flagkey" }
)
unknown_put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.new(
version: 100,
kind: "some-future-kind",
key: "otherkey",
object: { foo: 1 }
)
unknown_delete = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.new(
version: 101,
kind: "some-future-kind",
key: "otherkey"
)
known_delete = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.new(
version: 101,
kind: LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT,
key: "segmentkey"
)
selector = LaunchDarkly::Interfaces::DataSystem::Selector.new(state: "p:SOMETHING:300", version: 300)

expect(logger).not_to receive(:warn)
expect(logger).not_to receive(:error)

# Process a payload that mixes known and unknown object kinds
synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::SERVER_INTENT, JSON.generate(server_intent.to_h)),
change_set_builder, envid)
[known_put, unknown_put].each do |put|
result = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT, JSON.generate(put.to_h)),
change_set_builder, envid)
expect(result).to be_nil
end
[unknown_delete, known_delete].each do |delete_object|
result = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT, JSON.generate(delete_object.to_h)),
change_set_builder, envid)
expect(result).to be_nil
end
update = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::PAYLOAD_TRANSFERRED, JSON.generate(selector.to_h)),
change_set_builder, envid)

# The payload completes as valid with only the known objects
expect(update).not_to be_nil
expect(update.state).to eq(LaunchDarkly::Interfaces::DataSource::Status::VALID)
expect(update.error).to be_nil
expect(update.change_set).not_to be_nil
expect(update.change_set.changes.length).to eq(2)
expect(update.change_set.changes[0].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT)
expect(update.change_set.changes[0].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG)
expect(update.change_set.changes[0].key).to eq(:flagkey)
expect(update.change_set.changes[1].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE)
expect(update.change_set.changes[1].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT)
expect(update.change_set.changes[1].key).to eq(:segmentkey)
end

it "swallows goodbye events" do
server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new(
payload: LaunchDarkly::Interfaces::DataSystem::Payload.new(
Expand Down
Loading