Skip to content

feat: Add FDv2 wire format and protocol handler#177

Open
beekld wants to merge 1 commit into
mainfrom
bklimt/SDK-2588/fdv2-wire-types
Open

feat: Add FDv2 wire format and protocol handler#177
beekld wants to merge 1 commit into
mainfrom
bklimt/SDK-2588/fdv2-wire-types

Conversation

@beekld

@beekld beekld commented Jun 23, 2026

Copy link
Copy Markdown

Summary

  • Introduces the fdv2 module:
    • wire-format types
    • assembled ChangeSet / Selector types
    • protocol-handler state machine that folds SSE events into change sets
  • Object bodies stay as raw serde_json::Value; typed deserialization will live in a later translator.
  • Matches the C++ SDK on permissive-parsing choices:
    • Selector keeps the deprecated version field alongside state
    • Goodbye and FDv2Error use optional fields to tolerate permissively-parsed wire data
  • Module is annotated #[allow(dead_code)] for now: types are covered by unit tests but not yet reachable from the production data path.

Note

Low Risk
New code is module-scoped, dead-code gated, and covered by unit tests; it does not alter the live flag data path until integrated.

Overview
Adds a new fdv2 module (registered in lib.rs behind #[allow(dead_code)]) that is not yet hooked into streaming/polling.

wire.rs introduces serde models for FDv2 SSE payloads (server-intent, put-object, delete-object, payload-transferred, error, goodbye) plus assembled ChangeSet / Selector types. Put bodies stay as raw serde_json::Value; parsing is permissive (unknown intent codes, optional fields, default version on payload-transferred) to align with the C++ SDK.

protocol.rs implements FDv2ProtocolHandler, a small state machine that folds those events into full/partial/none change sets, handles errors and goodbye (goodbye always resets even if JSON is partial), and allows further partial cycles after the first payload-transferred without a new intent. Unit tests cover the main flows and edge cases.

Reviewed by Cursor Bugbot for commit dfa473b. Bugbot is set up for automated code reviews on this repo. Configure here.

@beekld beekld marked this pull request as ready for review June 25, 2026 17:59
@beekld beekld requested a review from a team as a code owner June 25, 2026 17:59
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ServerIntentPayload {
pub(super) intent_code: IntentCode,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are missing "reason" here.

pub(super) version: u64,
pub(super) kind: String,
pub(super) key: String,
pub(super) object: serde_json::Value,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we think about using serde's raw value? Might save some cycles if we find we need to discard part of the stream.

pub(super) struct PayloadTransferred {
pub(super) state: String,
#[serde(default)]
pub(super) version: u64,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this. We were never meant to process that version.

https://github.com/launchdarkly/sdk-specs/pull/169

#[derive(Debug, PartialEq, Eq)]
pub(super) enum Selector {
Empty,
Set { state: String, version: u64 },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Means you also won't need this I don't believe.

Comment on lines +74 to +79
Put {
kind: String,
key: String,
version: u64,
object: serde_json::Value,
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do Put(PutObject) and Delete(DeleteObject)?

}

#[test]
fn put_object_keeps_object_as_raw_json() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ensuring it is still raw bytes, just that it could parse an arbitrary object type. If we go with the raw value, this test will have to change a bit.

}

#[test]
fn payload_transferred_defaults_version_when_absent() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need this test after we remove the version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants