Commit f453061e authored by Dominik Charousset's avatar Dominik Charousset

Support variant fields in the json_writer

parent 2ede8ae0
...@@ -35,10 +35,12 @@ public: ...@@ -35,10 +35,12 @@ public:
// -- constants -------------------------------------------------------------- // -- constants --------------------------------------------------------------
/// The value returned for `skip_empty_fields()` for a default-constructed /// The default value for `skip_empty_fields()`.
/// JSON writer.
static constexpr bool skip_empty_fields_default = true; static constexpr bool skip_empty_fields_default = true;
/// The value value for `field_type_suffix()`.
static constexpr string_view field_type_suffix_default = "-type";
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
json_writer(); json_writer();
...@@ -88,6 +90,18 @@ public: ...@@ -88,6 +90,18 @@ public:
skip_empty_fields_ = value; skip_empty_fields_ = value;
} }
/// Returns the suffix for generating type annotation fields for variant
/// fields. For example, CAF inserts field called "@foo${field_type_suffix}"
/// for a variant field called "foo".
[[nodiscard]] string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Removes all characters from the buffer and restores the writer to its /// Removes all characters from the buffer and restores the writer to its
...@@ -249,6 +263,8 @@ private: ...@@ -249,6 +263,8 @@ private:
// Configures whether we omit empty fields entirely (true) or render empty // Configures whether we omit empty fields entirely (true) or render empty
// fields as `$field: null` (false). // fields as `$field: null` (false).
bool skip_empty_fields_ = skip_empty_fields_default; bool skip_empty_fields_ = skip_empty_fields_default;
string_view field_type_suffix_ = field_type_suffix_default;
}; };
} // namespace caf } // namespace caf
...@@ -79,13 +79,21 @@ void json_writer::reset() { ...@@ -79,13 +79,21 @@ void json_writer::reset() {
// -- overrides ---------------------------------------------------------------- // -- overrides ----------------------------------------------------------------
bool json_writer::begin_object(type_id_t, string_view name) { bool json_writer::begin_object(type_id_t id, string_view name) {
auto add_type_annotation = [this, name] { auto add_type_annotation = [this, id, name] {
CAF_ASSERT(top() == type::key); CAF_ASSERT(top() == type::key);
add(R"_("@type": )_"); add(R"_("@type": )_");
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
detail::print_escaped(buf_, name); if (auto tname = query_type_name(id); !tname.empty()) {
add('"');
add(tname);
add('"');
} else {
add('"');
add(name);
add('"');
}
pop(); pop();
return true; return true;
}; };
...@@ -105,8 +113,9 @@ bool json_writer::end_object() { ...@@ -105,8 +113,9 @@ bool json_writer::end_object() {
bool json_writer::begin_field(string_view name) { bool json_writer::begin_field(string_view name) {
if (begin_key_value_pair()) { if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key); CAF_ASSERT(top() == type::key);
detail::print_escaped(buf_, name); add('"');
add(": "); add(name);
add("\": ");
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
return true; return true;
...@@ -131,8 +140,9 @@ bool json_writer::begin_field(string_view name, bool is_present) { ...@@ -131,8 +140,9 @@ bool json_writer::begin_field(string_view name, bool is_present) {
} }
} else if (begin_key_value_pair()) { } else if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key); CAF_ASSERT(top() == type::key);
detail::print_escaped(buf_, name); add('"');
add(": "); add(name);
add("\": ");
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
if (!is_present) { if (!is_present) {
...@@ -145,13 +155,41 @@ bool json_writer::begin_field(string_view name, bool is_present) { ...@@ -145,13 +155,41 @@ bool json_writer::begin_field(string_view name, bool is_present) {
} }
} }
bool json_writer::begin_field(string_view name, span<const type_id_t>, size_t) { bool json_writer::begin_field(string_view name, span<const type_id_t> types,
return begin_field(name); size_t index) {
if (index >= types.size()) {
emplace_error(sec::runtime_error, "index >= types.size()");
return false;
}
if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
add("\"@");
add(name);
add(field_type_suffix_);
add("\": ");
pop();
CAF_ASSERT(top() == type::element);
pop();
if (auto tname = query_type_name(types[index]); !tname.empty()) {
add('"');
add(tname);
add('"');
} else {
emplace_error(sec::runtime_error, "query_type_name failed");
return false;
}
return end_key_value_pair() && begin_field(name);
} else {
return false;
}
} }
bool json_writer::begin_field(string_view name, bool is_present, bool json_writer::begin_field(string_view name, bool is_present,
span<const type_id_t>, size_t) { span<const type_id_t> types, size_t index) {
return begin_field(name, is_present); if (is_present)
return begin_field(name, types, index);
else
return begin_field(name, is_present);
} }
bool json_writer::end_field() { bool json_writer::end_field() {
......
...@@ -298,8 +298,8 @@ bool inspect(Inspector& f, dummy_enum& x) { ...@@ -298,8 +298,8 @@ bool inspect(Inspector& f, dummy_enum& x) {
} }
struct point { struct point {
int x; int32_t x;
int y; int32_t y;
}; };
[[maybe_unused]] constexpr bool operator==(point a, point b) noexcept { [[maybe_unused]] constexpr bool operator==(point a, point b) noexcept {
...@@ -336,6 +336,48 @@ bool inspect(Inspector& f, rectangle& x) { ...@@ -336,6 +336,48 @@ bool inspect(Inspector& f, rectangle& x) {
return !(x == y); return !(x == y);
} }
struct circle {
point center;
int32_t radius;
};
template <class Inspector>
bool inspect(Inspector& f, circle& x) {
return f.object(x).fields(f.field("center", x.center),
f.field("radius", x.radius));
}
[[maybe_unused]] constexpr bool operator==(const circle& x,
const circle& y) noexcept {
return x.center == y.center && x.radius == y.radius;
}
[[maybe_unused]] constexpr bool operator!=(const circle& x,
const circle& y) noexcept {
return !(x == y);
}
struct widget {
std::string color;
caf::variant<rectangle, circle> shape;
};
template <class Inspector>
bool inspect(Inspector& f, widget& x) {
return f.object(x).fields(f.field("color", x.color),
f.field("shape", x.shape));
}
[[maybe_unused]] inline bool operator==(const widget& x,
const widget& y) noexcept {
return x.color == y.color && x.shape == y.shape;
}
[[maybe_unused]] inline bool operator!=(const widget& x,
const widget& y) noexcept {
return !(x == y);
}
struct dummy_user { struct dummy_user {
std::string name; std::string name;
caf::optional<std::string> nickname; caf::optional<std::string> nickname;
...@@ -378,6 +420,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) ...@@ -378,6 +420,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((caf::stream<int32_t>) ) ADD_TYPE_ID((caf::stream<int32_t>) )
ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) ) ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) )
ADD_TYPE_ID((caf::stream<std::string>) ) ADD_TYPE_ID((caf::stream<std::string>) )
ADD_TYPE_ID((circle))
ADD_TYPE_ID((dummy_enum)) ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class)) ADD_TYPE_ID((dummy_enum_class))
ADD_TYPE_ID((dummy_struct)) ADD_TYPE_ID((dummy_struct))
...@@ -410,6 +453,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) ...@@ -410,6 +453,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((test_array)) ADD_TYPE_ID((test_array))
ADD_TYPE_ID((test_empty_non_pod)) ADD_TYPE_ID((test_empty_non_pod))
ADD_TYPE_ID((test_enum)) ADD_TYPE_ID((test_enum))
ADD_TYPE_ID((widget))
ADD_ATOM(abc_atom) ADD_ATOM(abc_atom)
ADD_ATOM(get_state_atom) ADD_ATOM(get_state_atom)
......
...@@ -224,4 +224,76 @@ SCENARIO("the JSON writer omits or nulls missing values") { ...@@ -224,4 +224,76 @@ SCENARIO("the JSON writer omits or nulls missing values") {
} }
} }
SCENARIO("the JSON writer annotates variant fields") {
GIVEN("a widget object with rectangle shape") {
widget x;
x.color = "red";
x.shape = rectangle{{10, 10}, {20, 20}};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON is a single line containing '@shape-type = rectangle'") {
std::string out = R"({"@type": "widget", )"
R"("color": "red", )"
R"("@shape-type": "rectangle", )"
R"("shape": )"
R"({"top-left": {"x": 10, "y": 10}, )"
R"("bottom-right": {"x": 20, "y": 20}}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON is multiple lines containing '@shape-type = rectangle'") {
std::string out = R"({
"@type": "widget",
"color": "red",
"@shape-type": "rectangle",
"shape": {
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
}
})";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
GIVEN("a widget object with circle shape") {
widget x;
x.color = "red";
x.shape = circle{{15, 15}, 5};
WHEN("converting it to JSON with indentation factor 0") {
THEN("the JSON is a single line containing '@shape-type = circle'") {
std::string out = R"({"@type": "widget", )"
R"("color": "red", )"
R"("@shape-type": "circle", )"
R"("shape": )"
R"({"center": {"x": 15, "y": 15}, )"
R"("radius": 5}})";
CHECK_EQ(to_json_string(x, 0), out);
}
}
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON is multiple lines containing '@shape-type = circle'") {
std::string out = R"({
"@type": "widget",
"color": "red",
"@shape-type": "circle",
"shape": {
"center": {
"x": 15,
"y": 15
},
"radius": 5
}
})";
CHECK_EQ(to_json_string(x, 2), out);
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment