Skip to content

Commit d182f7e

Browse files
authored
chore: Add FDv2 Protocol Handling (#184)
This implements a protocol handling state machine for FDv2. The state machine handles each event type for a connection and returns an action the caller should take. The expectation is that streaming/polling create a new protocol handler per connection. As events are received they are pushed into the the protocol handler. For each event the protocol handler will return an action to take. An action could be emitting a changeset, preparing for a disconnect, or logging an informative message. Currently the protocol handler doesn't directly include any event or logging capability and delegates all responsibility to the caller. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds an FDv2 protocol state machine that processes events and emits changesets, replaces the polling event wrapper with a unified event type, introduces intent code handling, and changes PutObject to store raw JSON strings with comprehensive tests. > > - **FDv2 Protocol/State Machine**: > - Add `FDv2ProtocolHandler` with actions (`Changeset`, `Error`, `Goodbye`, `None`, `InternalError`) and error types; handles `server-intent`, `put-object`, `delete-object`, `payload-transferred`, `goodbye`, `heartbeat` and emits `FDv2ChangeSet`. > - New data models: `FDv2Change`, `FDv2ChangeSet` (Full/Partial/None), `FDv2Selector`, and `FDv2EventTypes`. > - **Event/Payload Model**: > - Replace `FDv2PollEvent` with unified `FDv2Event` (type + raw JSON) and `FDv2PollEventConverter`. > - Add `IntentCode` enum + converter; update `ServerIntent`/`ServerIntentPayload` to use it. > - Change `PutObject.Object` from `JsonElement` to raw JSON `string`; update read/write to preserve raw payload. > - **Tests**: > - Add `Internal/FDv2DataSources/FDv2ProtocolHandlerTest` covering state transitions, errors, resets, and changeset emission. > - Update `Internal/FDv2Payloads/FDv2PayloadsTest` for new event wrapper, `IntentCode`, and `PutObject` serialization. > - **Project**: > - Add test folder include `Internal/FDv2DataSources/` in `LaunchDarkly.ServerSdk.Tests.csproj`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9c4653b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 10b2f4a commit d182f7e

File tree

12 files changed

+2102
-197
lines changed

12 files changed

+2102
-197
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Text.Json;
4+
5+
namespace LaunchDarkly.Sdk.Server.Internal.FDv2DataSources
6+
{
7+
/// <summary>
8+
/// Represents the type of change operation.
9+
/// </summary>
10+
internal enum FDv2ChangeType
11+
{
12+
/// <summary>
13+
/// Indicates an upsert operation (insert or update).
14+
/// </summary>
15+
Put,
16+
17+
/// <summary>
18+
/// Indicates a delete operation.
19+
/// </summary>
20+
Delete
21+
}
22+
23+
internal enum FDv2ChangeSetType
24+
{
25+
/// <summary>
26+
/// Changeset represent a full payload to use as a basis.
27+
/// </summary>
28+
Full,
29+
30+
/// <summary>
31+
/// Changeset represents a partial payload to be applied to a basis.
32+
/// </summary>
33+
Partial,
34+
35+
/// <summary>
36+
/// A changeset which indicates that no changes should be made.
37+
/// </summary>
38+
None,
39+
}
40+
41+
/// <summary>
42+
/// Represents a single change to a data object.
43+
/// </summary>
44+
internal sealed class FDv2Change
45+
{
46+
/// <summary>
47+
/// The type of change operation.
48+
/// </summary>
49+
public FDv2ChangeType Type { get; }
50+
51+
/// <summary>
52+
/// The kind of object being changed ("flag" or "segment").
53+
/// <para>
54+
/// This field is required and will never be null.
55+
/// </para>
56+
/// </summary>
57+
public string Kind { get; }
58+
59+
/// <summary>
60+
/// The key identifying the object.
61+
/// <para>
62+
/// This field is required and will never be null.
63+
/// </para>
64+
/// </summary>
65+
public string Key { get; }
66+
67+
/// <summary>
68+
/// The version of the change.
69+
/// </summary>
70+
public int Version { get; }
71+
72+
/// <summary>
73+
/// The raw JSON string representing the object data (only present for Put operations).
74+
/// </summary>
75+
public string Object { get; }
76+
77+
/// <summary>
78+
/// Constructs a new Change.
79+
/// </summary>
80+
/// <param name="type">The type of change operation.</param>
81+
/// <param name="kind">The kind of object being changed.</param>
82+
/// <param name="key">The key identifying the object.</param>
83+
/// <param name="version">The version of the change.</param>
84+
/// <param name="obj">The raw JSON string representing the object data (required for Put operations).</param>
85+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="kind"/> or <paramref name="key"/> is null.</exception>
86+
public FDv2Change(FDv2ChangeType type, string kind, string key, int version, string obj = null)
87+
{
88+
Type = type;
89+
Kind = kind ?? throw new ArgumentNullException(nameof(kind));
90+
Key = key ?? throw new ArgumentNullException(nameof(key));
91+
Version = version;
92+
Object = obj;
93+
}
94+
}
95+
96+
/// <summary>
97+
/// Represents a collection of changes with metadata about the intent and version.
98+
/// </summary>
99+
internal sealed class FDv2ChangeSet
100+
{
101+
/// <summary>
102+
/// The intent code indicating how the server intends to transfer data.
103+
/// </summary>
104+
public FDv2ChangeSetType Type { get; }
105+
106+
/// <summary>
107+
/// The list of changes in this changeset.
108+
/// <para>
109+
/// Null if there are no changes to apply.
110+
/// </para>
111+
/// </summary>
112+
public ImmutableList<FDv2Change> Changes { get; }
113+
114+
/// <summary>
115+
/// The selector (version identifier) for this changeset.
116+
/// </summary>
117+
public FDv2Selector FDv2Selector { get; }
118+
119+
/// <summary>
120+
/// Constructs a new ChangeSet.
121+
/// </summary>
122+
/// <param name="type">The type of the changeset.</param>
123+
/// <param name="changes">The list of changes.</param>
124+
/// <param name="fDv2Selector">The selector.</param>
125+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="changes"/> is null.</exception>
126+
public FDv2ChangeSet(FDv2ChangeSetType type, ImmutableList<FDv2Change> changes, FDv2Selector fDv2Selector)
127+
{
128+
Type = type;
129+
Changes = changes ?? throw new ArgumentNullException(nameof(changes));
130+
FDv2Selector = fDv2Selector;
131+
}
132+
133+
/// <summary>
134+
/// An empty changeset that indicates no changes are required.
135+
/// </summary>
136+
public static FDv2ChangeSet None { get; } =
137+
new FDv2ChangeSet(FDv2ChangeSetType.None, ImmutableList<FDv2Change>.Empty, FDv2Selector.Empty);
138+
}
139+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace LaunchDarkly.Sdk.Server.Internal.FDv2DataSources
2+
{
3+
/// <summary>
4+
/// Types of events that FDv2 can receive.
5+
/// </summary>
6+
internal static class FDv2EventTypes
7+
{
8+
public const string ServerIntent = "server-intent";
9+
public const string PutObject = "put-object";
10+
public const string DeleteObject = "delete-object";
11+
public const string Error = "error";
12+
public const string Goodbye = "goodbye";
13+
public const string HeartBeat = "heartbeat";
14+
public const string PayloadTransferred = "payload-transferred";
15+
}
16+
}

0 commit comments

Comments
 (0)