Skip to content

Commit 319d5d9

Browse files
hkotianclaude
andcommitted
refactor: use SDK subsystems types in FDv2 polling
Replace locally-defined FDv2 wire-format types with the equivalent types from go-server-sdk/v7/subsystems: RawEvent, PollingPayload, PutObject, ServerIntent, Selector, and the IntentTransferFull/IntentNone/FlagKind constants. The three reason strings (up-to-date, cant-catchup, payload-missing) have no SDK equivalents and remain as local constants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a271490 commit 319d5d9

2 files changed

Lines changed: 62 additions & 101 deletions

File tree

internal/dev_server/sdk/fdv2.go

Lines changed: 36 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,16 @@ import (
66
"strconv"
77
"strings"
88

9+
"github.com/launchdarkly/go-server-sdk/v7/subsystems"
910
"github.com/launchdarkly/ldcli/internal/dev_server/model"
1011
)
1112

1213
const (
13-
fdv2EventServerIntent = "server-intent"
14-
fdv2EventPutObject = "put-object"
15-
fdv2EventPayloadTransferred = "payload-transferred"
16-
17-
fdv2IntentXferFull = "xfer-full"
18-
fdv2IntentNone = "none"
19-
2014
fdv2ReasonUpToDate = "up-to-date"
2115
fdv2ReasonCantCatchup = "cant-catchup"
2216
fdv2ReasonPayloadMissing = "payload-missing"
2317
)
2418

25-
// fdv2RawEvent matches the wire format the SDK deserializes from the /sdk/poll response.
26-
// The SDK's RawEvent uses json:"event" (not json:"name") as of v7.13+.
27-
type fdv2RawEvent struct {
28-
Event string `json:"event"`
29-
Data json.RawMessage `json:"data"`
30-
}
31-
32-
type fdv2Payload struct {
33-
ID string `json:"id"`
34-
Target int `json:"target"`
35-
IntentCode string `json:"intentCode"`
36-
Reason string `json:"reason"`
37-
}
38-
39-
type fdv2ServerIntentData struct {
40-
Payloads []fdv2Payload `json:"payloads"`
41-
}
42-
43-
type fdv2PutObjectData struct {
44-
Version int `json:"version"`
45-
Kind string `json:"kind"`
46-
Key string `json:"key"`
47-
Object json.RawMessage `json:"object"`
48-
}
49-
50-
type fdv2PayloadTransferredData struct {
51-
State string `json:"state"`
52-
Version int `json:"version"`
53-
}
54-
55-
type fdv2PollResponse struct {
56-
Events []fdv2RawEvent `json:"events"`
57-
}
58-
5919
// parseBasisVersion extracts the payload version from a basis state string of the
6020
// form "(p:<payloadId>:<version>)". Returns 0 if the string is absent or unparseable.
6121
func parseBasisVersion(basis string) int {
@@ -80,85 +40,86 @@ func parseBasisVersion(basis string) int {
8040
// currentVersion is the project's current PayloadVersion.
8141
// flags is the current flag state with overrides applied.
8242
// basisVersion is parsed from the SDK's ?basis query param (0 = no basis provided).
83-
func buildPollResponse(payloadID string, currentVersion int, flags model.FlagsState, basisVersion int) (fdv2PollResponse, error) {
43+
//
44+
// Delta transfers are not supported: stale clients always receive a full payload.
45+
// Tracking the change history required for deltas is overkill for a local dev server.
46+
func buildPollResponse(payloadID string, currentVersion int, flags model.FlagsState, basisVersion int) (subsystems.PollingPayload, error) {
8447
switch {
8548
case basisVersion == 0:
8649
return buildFullTransferResponse(payloadID, currentVersion, flags, fdv2ReasonPayloadMissing)
8750
case basisVersion >= currentVersion:
88-
event, err := makeServerIntentEvent(payloadID, currentVersion, fdv2IntentNone, fdv2ReasonUpToDate)
51+
event, err := makeServerIntentEvent(payloadID, currentVersion, subsystems.IntentNone, fdv2ReasonUpToDate)
8952
if err != nil {
90-
return fdv2PollResponse{}, err
53+
return subsystems.PollingPayload{}, err
9154
}
92-
return fdv2PollResponse{Events: []fdv2RawEvent{event}}, nil
55+
return subsystems.PollingPayload{Events: []subsystems.RawEvent{event}}, nil
9356
default:
9457
// Stale: we don't store history so we can't compute a delta — send the full payload.
9558
return buildFullTransferResponse(payloadID, currentVersion, flags, fdv2ReasonCantCatchup)
9659
}
9760
}
9861

99-
func buildFullTransferResponse(payloadID string, version int, flags model.FlagsState, reason string) (fdv2PollResponse, error) {
100-
intentEvent, err := makeServerIntentEvent(payloadID, version, fdv2IntentXferFull, reason)
62+
func buildFullTransferResponse(payloadID string, version int, flags model.FlagsState, reason string) (subsystems.PollingPayload, error) {
63+
intentEvent, err := makeServerIntentEvent(payloadID, version, subsystems.IntentTransferFull, reason)
10164
if err != nil {
102-
return fdv2PollResponse{}, err
65+
return subsystems.PollingPayload{}, err
10366
}
104-
events := []fdv2RawEvent{intentEvent}
67+
events := []subsystems.RawEvent{intentEvent}
10568

10669
for key, flagState := range flags {
10770
event, err := makePutObjectEvent(version, key, flagState)
10871
if err != nil {
109-
return fdv2PollResponse{}, err
72+
return subsystems.PollingPayload{}, err
11073
}
11174
events = append(events, event)
11275
}
11376

11477
transferredEvent, err := makePayloadTransferredEvent(payloadID, version)
11578
if err != nil {
116-
return fdv2PollResponse{}, err
79+
return subsystems.PollingPayload{}, err
11780
}
11881
events = append(events, transferredEvent)
11982

120-
return fdv2PollResponse{Events: events}, nil
83+
return subsystems.PollingPayload{Events: events}, nil
12184
}
12285

123-
func makeServerIntentEvent(payloadID string, target int, intentCode, reason string) (fdv2RawEvent, error) {
124-
data, err := json.Marshal(fdv2ServerIntentData{
125-
Payloads: []fdv2Payload{{
126-
ID: payloadID,
127-
Target: target,
128-
IntentCode: intentCode,
129-
Reason: reason,
130-
}},
86+
func makeServerIntentEvent(payloadID string, target int, intentCode subsystems.IntentCode, reason string) (subsystems.RawEvent, error) {
87+
data, err := json.Marshal(subsystems.ServerIntent{
88+
Payload: subsystems.Payload{
89+
ID: payloadID,
90+
Target: target,
91+
Code: intentCode,
92+
Reason: reason,
93+
},
13194
})
13295
if err != nil {
133-
return fdv2RawEvent{}, err
96+
return subsystems.RawEvent{}, err
13497
}
135-
return fdv2RawEvent{Event: fdv2EventServerIntent, Data: data}, nil
98+
return subsystems.RawEvent{Name: subsystems.EventServerIntent, Data: data}, nil
13699
}
137100

138-
func makePutObjectEvent(version int, key string, flagState model.FlagState) (fdv2RawEvent, error) {
101+
func makePutObjectEvent(version int, key string, flagState model.FlagState) (subsystems.RawEvent, error) {
139102
object, err := json.Marshal(serverFlagFromFlagState(key, flagState))
140103
if err != nil {
141-
return fdv2RawEvent{}, err
104+
return subsystems.RawEvent{}, err
142105
}
143-
data, err := json.Marshal(fdv2PutObjectData{
106+
data, err := json.Marshal(subsystems.PutObject{
144107
Version: version,
145-
Kind: "flag",
108+
Kind: subsystems.FlagKind,
146109
Key: key,
147110
Object: object,
148111
})
149112
if err != nil {
150-
return fdv2RawEvent{}, err
113+
return subsystems.RawEvent{}, err
151114
}
152-
return fdv2RawEvent{Event: fdv2EventPutObject, Data: data}, nil
115+
return subsystems.RawEvent{Name: subsystems.EventPutObject, Data: data}, nil
153116
}
154117

155-
func makePayloadTransferredEvent(payloadID string, version int) (fdv2RawEvent, error) {
156-
data, err := json.Marshal(fdv2PayloadTransferredData{
157-
State: fmt.Sprintf("(p:%s:%d)", payloadID, version),
158-
Version: version,
159-
})
118+
func makePayloadTransferredEvent(payloadID string, version int) (subsystems.RawEvent, error) {
119+
selector := subsystems.NewSelector(fmt.Sprintf("(p:%s:%d)", payloadID, version), version)
120+
data, err := json.Marshal(selector)
160121
if err != nil {
161-
return fdv2RawEvent{}, err
122+
return subsystems.RawEvent{}, err
162123
}
163-
return fdv2RawEvent{Event: fdv2EventPayloadTransferred, Data: data}, nil
124+
return subsystems.RawEvent{Name: subsystems.EventPayloadTransferred, Data: data}, nil
164125
}

internal/dev_server/sdk/fdv2_test.go

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/gorilla/mux"
1212
"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
1313
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
14+
"github.com/launchdarkly/go-server-sdk/v7/subsystems"
1415
"github.com/launchdarkly/ldcli/internal/dev_server/model"
1516
"github.com/launchdarkly/ldcli/internal/dev_server/model/mocks"
1617
"github.com/stretchr/testify/assert"
@@ -53,7 +54,7 @@ func TestBuildPollResponse(t *testing.T) {
5354

5455
require.GreaterOrEqual(t, len(resp.Events), 3) // server-intent + put-objects + payload-transferred
5556

56-
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, fdv2IntentXferFull, fdv2ReasonPayloadMissing)
57+
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, subsystems.IntentTransferFull, fdv2ReasonPayloadMissing)
5758
assertPayloadTransferredEvent(t, resp.Events[len(resp.Events)-1], payloadID, currentVersion)
5859
})
5960

@@ -62,23 +63,23 @@ func TestBuildPollResponse(t *testing.T) {
6263
require.NoError(t, err)
6364

6465
require.Len(t, resp.Events, 1)
65-
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, fdv2IntentNone, fdv2ReasonUpToDate)
66+
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, subsystems.IntentNone, fdv2ReasonUpToDate)
6667
})
6768

6869
t.Run("basis ahead of current version sends none with up-to-date", func(t *testing.T) {
6970
resp, err := buildPollResponse(payloadID, currentVersion, flags, currentVersion+10)
7071
require.NoError(t, err)
7172

7273
require.Len(t, resp.Events, 1)
73-
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, fdv2IntentNone, fdv2ReasonUpToDate)
74+
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, subsystems.IntentNone, fdv2ReasonUpToDate)
7475
})
7576

7677
t.Run("stale basis sends xfer-full with cant-catchup", func(t *testing.T) {
7778
resp, err := buildPollResponse(payloadID, currentVersion, flags, currentVersion-1)
7879
require.NoError(t, err)
7980

8081
require.GreaterOrEqual(t, len(resp.Events), 3)
81-
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, fdv2IntentXferFull, fdv2ReasonCantCatchup)
82+
assertServerIntentEvent(t, resp.Events[0], payloadID, currentVersion, subsystems.IntentTransferFull, fdv2ReasonCantCatchup)
8283
assertPayloadTransferredEvent(t, resp.Events[len(resp.Events)-1], payloadID, currentVersion)
8384
})
8485

@@ -94,12 +95,12 @@ func TestBuildPollResponse(t *testing.T) {
9495
assert.Len(t, resp.Events, 4)
9596
putKeys := make(map[string]bool)
9697
for _, event := range resp.Events {
97-
if event.Event == fdv2EventPutObject {
98-
var put fdv2PutObjectData
98+
if event.Name == subsystems.EventPutObject {
99+
var put subsystems.PutObject
99100
require.NoError(t, json.Unmarshal(event.Data, &put))
100101
putKeys[put.Key] = true
101102
assert.Equal(t, currentVersion, put.Version)
102-
assert.Equal(t, "flag", put.Kind)
103+
assert.Equal(t, subsystems.FlagKind, put.Kind)
103104
}
104105
}
105106
assert.True(t, putKeys["flag-a"])
@@ -141,10 +142,10 @@ func TestPollV2Handler(t *testing.T) {
141142
require.Equal(t, http.StatusOK, rec.Code)
142143
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
143144

144-
var resp fdv2PollResponse
145+
var resp subsystems.PollingPayload
145146
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
146147
require.GreaterOrEqual(t, len(resp.Events), 3)
147-
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, fdv2IntentXferFull, fdv2ReasonPayloadMissing)
148+
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, subsystems.IntentTransferFull, fdv2ReasonPayloadMissing)
148149
assertPayloadTransferredEvent(t, resp.Events[len(resp.Events)-1], exampleProjectKey, 3)
149150
})
150151

@@ -160,10 +161,10 @@ func TestPollV2Handler(t *testing.T) {
160161

161162
require.Equal(t, http.StatusOK, rec.Code)
162163

163-
var resp fdv2PollResponse
164+
var resp subsystems.PollingPayload
164165
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
165166
require.Len(t, resp.Events, 1)
166-
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, fdv2IntentNone, fdv2ReasonUpToDate)
167+
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, subsystems.IntentNone, fdv2ReasonUpToDate)
167168
})
168169

169170
t.Run("stale basis returns full payload with cant-catchup", func(t *testing.T) {
@@ -178,10 +179,10 @@ func TestPollV2Handler(t *testing.T) {
178179

179180
require.Equal(t, http.StatusOK, rec.Code)
180181

181-
var resp fdv2PollResponse
182+
var resp subsystems.PollingPayload
182183
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
183184
require.GreaterOrEqual(t, len(resp.Events), 3)
184-
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, fdv2IntentXferFull, fdv2ReasonCantCatchup)
185+
assertServerIntentEvent(t, resp.Events[0], exampleProjectKey, 3, subsystems.IntentTransferFull, fdv2ReasonCantCatchup)
185186
assertPayloadTransferredEvent(t, resp.Events[len(resp.Events)-1], exampleProjectKey, 3)
186187
})
187188

@@ -198,24 +199,23 @@ func TestPollV2Handler(t *testing.T) {
198199
}
199200

200201
// assertServerIntentEvent unmarshals a server-intent event and checks its fields.
201-
func assertServerIntentEvent(t *testing.T, event fdv2RawEvent, payloadID string, target int, intentCode, reason string) {
202+
func assertServerIntentEvent(t *testing.T, event subsystems.RawEvent, payloadID string, target int, intentCode subsystems.IntentCode, reason string) {
202203
t.Helper()
203-
assert.Equal(t, fdv2EventServerIntent, event.Event)
204-
var data fdv2ServerIntentData
204+
assert.Equal(t, subsystems.EventServerIntent, event.Name)
205+
var data subsystems.ServerIntent
205206
require.NoError(t, json.Unmarshal(event.Data, &data))
206-
require.Len(t, data.Payloads, 1)
207-
assert.Equal(t, payloadID, data.Payloads[0].ID)
208-
assert.Equal(t, target, data.Payloads[0].Target)
209-
assert.Equal(t, intentCode, data.Payloads[0].IntentCode)
210-
assert.Equal(t, reason, data.Payloads[0].Reason)
207+
assert.Equal(t, payloadID, data.Payload.ID)
208+
assert.Equal(t, target, data.Payload.Target)
209+
assert.Equal(t, intentCode, data.Payload.Code)
210+
assert.Equal(t, reason, data.Payload.Reason)
211211
}
212212

213213
// assertPayloadTransferredEvent unmarshals a payload-transferred event and checks its fields.
214-
func assertPayloadTransferredEvent(t *testing.T, event fdv2RawEvent, payloadID string, version int) {
214+
func assertPayloadTransferredEvent(t *testing.T, event subsystems.RawEvent, payloadID string, version int) {
215215
t.Helper()
216-
assert.Equal(t, fdv2EventPayloadTransferred, event.Event)
217-
var data fdv2PayloadTransferredData
216+
assert.Equal(t, subsystems.EventPayloadTransferred, event.Name)
217+
var data subsystems.Selector
218218
require.NoError(t, json.Unmarshal(event.Data, &data))
219-
assert.Equal(t, version, data.Version)
220-
assert.Equal(t, fmt.Sprintf("(p:%s:%d)", payloadID, version), data.State)
219+
assert.Equal(t, version, data.Version())
220+
assert.Equal(t, fmt.Sprintf("(p:%s:%d)", payloadID, version), data.State())
221221
}

0 commit comments

Comments
 (0)