Skip to content

Commit 1bb6805

Browse files
committed
feat: Add FDv2 wire format types and JSON deserializers
1 parent 5292cb7 commit 1bb6805

6 files changed

Lines changed: 750 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <launchdarkly/data_model/flag.hpp>
4+
#include <launchdarkly/data_model/item_descriptor.hpp>
5+
#include <launchdarkly/data_model/segment.hpp>
6+
#include <launchdarkly/data_model/selector.hpp>
7+
8+
#include <string>
9+
#include <variant>
10+
#include <vector>
11+
12+
namespace launchdarkly::data_model {
13+
14+
struct FDv2Change {
15+
std::string key;
16+
std::variant<ItemDescriptor<Flag>, ItemDescriptor<Segment>> object;
17+
};
18+
19+
struct FDv2ChangeSet {
20+
enum class Type {
21+
kFull = 0,
22+
kPartial = 1,
23+
kNone = 2,
24+
};
25+
26+
Type type;
27+
std::vector<FDv2Change> changes;
28+
Selector selector;
29+
};
30+
31+
} // namespace launchdarkly::data_model
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <optional>
5+
#include <string>
6+
7+
namespace launchdarkly::data_model {
8+
9+
/**
10+
* Identifies a specific version of data in the LaunchDarkly backend, used to
11+
* request incremental updates from a known point. <p> A selector is either
12+
* empty or contains a version number and state string that
13+
* were provided by a LaunchDarkly data source. Empty selectors signal that the
14+
* client has no existing data and requires a full payload. <p> <strong>For SDK
15+
* consumers implementing custom data sources:</strong> you should always use
16+
* std::nullopt when constructing a ChangeSet. Non-empty selectors are
17+
* set by LaunchDarkly's own data sources based on state received from the
18+
* LaunchDarkly backend, and are not meaningful when constructed externally.
19+
*/
20+
struct Selector {
21+
struct State {
22+
std::int64_t version;
23+
std::string state;
24+
};
25+
26+
std::optional<State> value;
27+
};
28+
29+
} // namespace launchdarkly::data_model
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#pragma once
2+
3+
#include <launchdarkly/detail/serialization/json_errors.hpp>
4+
5+
#include <boost/json/value.hpp>
6+
#include <tl/expected.hpp>
7+
8+
#include <cstdint>
9+
#include <optional>
10+
#include <string>
11+
#include <vector>
12+
13+
namespace launchdarkly {
14+
15+
enum class IntentCode { kNone, kTransferFull, kTransferChanges, kUnknown };
16+
17+
struct ServerIntentPayload {
18+
std::string id;
19+
std::int64_t target;
20+
IntentCode intent_code;
21+
std::optional<std::string> reason;
22+
};
23+
24+
struct ServerIntent {
25+
std::vector<ServerIntentPayload> payloads;
26+
};
27+
28+
struct PutObject {
29+
std::int64_t version;
30+
std::string kind;
31+
std::string key;
32+
boost::json::value object;
33+
};
34+
35+
struct DeleteObject {
36+
std::int64_t version;
37+
std::string kind;
38+
std::string key;
39+
};
40+
41+
struct PayloadTransferred {
42+
std::string state;
43+
std::int64_t version;
44+
};
45+
46+
struct Goodbye {
47+
std::optional<std::string> reason;
48+
};
49+
50+
struct FDv2Error {
51+
std::optional<std::string> id;
52+
std::string reason;
53+
};
54+
55+
tl::expected<std::optional<IntentCode>, JsonError> tag_invoke(
56+
boost::json::value_to_tag<
57+
tl::expected<std::optional<IntentCode>, JsonError>> const& unused,
58+
boost::json::value const& json_value);
59+
60+
tl::expected<std::optional<ServerIntentPayload>, JsonError> tag_invoke(
61+
boost::json::value_to_tag<
62+
tl::expected<std::optional<ServerIntentPayload>, JsonError>> const&
63+
unused,
64+
boost::json::value const& json_value);
65+
66+
tl::expected<std::optional<ServerIntent>, JsonError> tag_invoke(
67+
boost::json::value_to_tag<
68+
tl::expected<std::optional<ServerIntent>, JsonError>> const& unused,
69+
boost::json::value const& json_value);
70+
71+
tl::expected<std::optional<PutObject>, JsonError> tag_invoke(
72+
boost::json::value_to_tag<
73+
tl::expected<std::optional<PutObject>, JsonError>> const& unused,
74+
boost::json::value const& json_value);
75+
76+
tl::expected<std::optional<DeleteObject>, JsonError> tag_invoke(
77+
boost::json::value_to_tag<
78+
tl::expected<std::optional<DeleteObject>, JsonError>> const& unused,
79+
boost::json::value const& json_value);
80+
81+
tl::expected<std::optional<PayloadTransferred>, JsonError> tag_invoke(
82+
boost::json::value_to_tag<
83+
tl::expected<std::optional<PayloadTransferred>, JsonError>> const&
84+
unused,
85+
boost::json::value const& json_value);
86+
87+
tl::expected<std::optional<Goodbye>, JsonError> tag_invoke(
88+
boost::json::value_to_tag<
89+
tl::expected<std::optional<Goodbye>, JsonError>> const& unused,
90+
boost::json::value const& json_value);
91+
92+
tl::expected<std::optional<FDv2Error>, JsonError> tag_invoke(
93+
boost::json::value_to_tag<
94+
tl::expected<std::optional<FDv2Error>, JsonError>> const& unused,
95+
boost::json::value const& json_value);
96+
97+
} // namespace launchdarkly

libs/internal/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ set(INTERNAL_SOURCES
3434
serialization/json_evaluation_reason.cpp
3535
serialization/value_mapping.cpp
3636
serialization/json_evaluation_result.cpp
37+
serialization/json_fdv2_events.cpp
3738
serialization/json_sdk_data_set.cpp
3839
serialization/json_segment.cpp
3940
serialization/json_primitives.cpp
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include <boost/core/ignore_unused.hpp>
2+
#include <boost/json.hpp>
3+
#include <launchdarkly/serialization/json_fdv2_events.hpp>
4+
#include <launchdarkly/serialization/value_mapping.hpp>
5+
#include <tl/expected.hpp>
6+
7+
namespace launchdarkly {
8+
9+
tl::expected<std::optional<IntentCode>, JsonError> tag_invoke(
10+
boost::json::value_to_tag<
11+
tl::expected<std::optional<IntentCode>, JsonError>> const& unused,
12+
boost::json::value const& json_value) {
13+
boost::ignore_unused(unused);
14+
15+
REQUIRE_STRING(json_value);
16+
17+
auto const& str = json_value.as_string();
18+
if (str == "none") {
19+
return IntentCode::kNone;
20+
} else if (str == "xfer-full") {
21+
return IntentCode::kTransferFull;
22+
} else if (str == "xfer-changes") {
23+
return IntentCode::kTransferChanges;
24+
} else {
25+
return IntentCode::kUnknown;
26+
}
27+
}
28+
29+
tl::expected<std::optional<ServerIntentPayload>, JsonError> tag_invoke(
30+
boost::json::value_to_tag<
31+
tl::expected<std::optional<ServerIntentPayload>, JsonError>> const&
32+
unused,
33+
boost::json::value const& json_value) {
34+
boost::ignore_unused(unused);
35+
36+
REQUIRE_OBJECT(json_value);
37+
auto const& obj = json_value.as_object();
38+
39+
ServerIntentPayload payload{};
40+
41+
PARSE_REQUIRED_FIELD(payload.id, obj, "id");
42+
PARSE_REQUIRED_FIELD(payload.target, obj, "target");
43+
PARSE_REQUIRED_FIELD(payload.intent_code, obj, "intentCode");
44+
PARSE_CONDITIONAL_FIELD(payload.reason, obj, "reason");
45+
46+
return payload;
47+
}
48+
49+
tl::expected<std::optional<ServerIntent>, JsonError> tag_invoke(
50+
boost::json::value_to_tag<
51+
tl::expected<std::optional<ServerIntent>, JsonError>> const& unused,
52+
boost::json::value const& json_value) {
53+
boost::ignore_unused(unused);
54+
55+
REQUIRE_OBJECT(json_value);
56+
auto const& obj = json_value.as_object();
57+
58+
ServerIntent intent{};
59+
60+
PARSE_REQUIRED_FIELD(intent.payloads, obj, "payloads");
61+
62+
return intent;
63+
}
64+
65+
tl::expected<std::optional<PutObject>, JsonError> tag_invoke(
66+
boost::json::value_to_tag<
67+
tl::expected<std::optional<PutObject>, JsonError>> const& unused,
68+
boost::json::value const& json_value) {
69+
boost::ignore_unused(unused);
70+
71+
REQUIRE_OBJECT(json_value);
72+
auto const& obj = json_value.as_object();
73+
74+
PutObject put{};
75+
76+
PARSE_REQUIRED_FIELD(put.version, obj, "version");
77+
PARSE_REQUIRED_FIELD(put.kind, obj, "kind");
78+
PARSE_REQUIRED_FIELD(put.key, obj, "key");
79+
80+
auto const& it = obj.find("object");
81+
if (it == obj.end()) {
82+
return tl::make_unexpected(JsonError::kSchemaFailure);
83+
}
84+
put.object = it->value();
85+
86+
return put;
87+
}
88+
89+
tl::expected<std::optional<DeleteObject>, JsonError> tag_invoke(
90+
boost::json::value_to_tag<
91+
tl::expected<std::optional<DeleteObject>, JsonError>> const& unused,
92+
boost::json::value const& json_value) {
93+
boost::ignore_unused(unused);
94+
95+
REQUIRE_OBJECT(json_value);
96+
auto const& obj = json_value.as_object();
97+
98+
DeleteObject del{};
99+
100+
PARSE_REQUIRED_FIELD(del.version, obj, "version");
101+
PARSE_REQUIRED_FIELD(del.kind, obj, "kind");
102+
PARSE_REQUIRED_FIELD(del.key, obj, "key");
103+
104+
return del;
105+
}
106+
107+
tl::expected<std::optional<PayloadTransferred>, JsonError> tag_invoke(
108+
boost::json::value_to_tag<
109+
tl::expected<std::optional<PayloadTransferred>, JsonError>> const&
110+
unused,
111+
boost::json::value const& json_value) {
112+
boost::ignore_unused(unused);
113+
114+
REQUIRE_OBJECT(json_value);
115+
auto const& obj = json_value.as_object();
116+
117+
PayloadTransferred transferred{};
118+
119+
PARSE_REQUIRED_FIELD(transferred.state, obj, "state");
120+
PARSE_REQUIRED_FIELD(transferred.version, obj, "version");
121+
122+
return transferred;
123+
}
124+
125+
tl::expected<std::optional<Goodbye>, JsonError> tag_invoke(
126+
boost::json::value_to_tag<
127+
tl::expected<std::optional<Goodbye>, JsonError>> const& unused,
128+
boost::json::value const& json_value) {
129+
boost::ignore_unused(unused);
130+
131+
REQUIRE_OBJECT(json_value);
132+
auto const& obj = json_value.as_object();
133+
134+
Goodbye goodbye{};
135+
136+
PARSE_CONDITIONAL_FIELD(goodbye.reason, obj, "reason");
137+
138+
return goodbye;
139+
}
140+
141+
tl::expected<std::optional<FDv2Error>, JsonError> tag_invoke(
142+
boost::json::value_to_tag<
143+
tl::expected<std::optional<FDv2Error>, JsonError>> const& unused,
144+
boost::json::value const& json_value) {
145+
boost::ignore_unused(unused);
146+
147+
REQUIRE_OBJECT(json_value);
148+
auto const& obj = json_value.as_object();
149+
150+
FDv2Error error{};
151+
152+
PARSE_REQUIRED_FIELD(error.reason, obj, "reason");
153+
PARSE_CONDITIONAL_FIELD(error.id, obj, "id");
154+
155+
return error;
156+
}
157+
158+
} // namespace launchdarkly

0 commit comments

Comments
 (0)