From 8333d7020b2f6981f58920ce17519d878088c95e Mon Sep 17 00:00:00 2001 From: Lixin Wei Date: Mon, 19 Oct 2026 10:26:19 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Cap=E2=80=99n=20Proto=20maps=20with=20sca?= =?UTF-8?q?lar=20and=20enum=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rfl/capnproto/schema/CapnProtoTypes.hpp | 10 +++ src/rfl/capnproto/to_schema.cpp | 46 +++++++++-- tests/capnproto/test_map_value_types.cpp | 80 +++++++++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/include/rfl/capnproto/schema/CapnProtoTypes.hpp b/include/rfl/capnproto/schema/CapnProtoTypes.hpp index 6f50f4e61..ef8ac44a7 100644 --- a/include/rfl/capnproto/schema/CapnProtoTypes.hpp +++ b/include/rfl/capnproto/schema/CapnProtoTypes.hpp @@ -10,6 +10,7 @@ namespace rfl::capnproto::schema { struct CapnProtoTypes { bool has_maps_ = false; + std::map scalar_maps_; std::map structs_; std::map enums_; std::map tuples_; @@ -40,6 +41,15 @@ std::ostream& operator<<(std::ostream& _os, const CapnProtoTypes& _cnp_types) { if (_cnp_types.has_maps_) { _os << MAP_DEFINITION << std::endl << std::endl; } + for (const auto& [name, value_type] : _cnp_types.scalar_maps_) { + _os << "struct " << name << " {\n" + << " entries @0 :List(Entry);\n" + << " struct Entry {\n" + << " key @0 :Text;\n" + << " value @1 :" << value_type << ";\n" + << " }\n" + << "}\n\n"; + } _os << _cnp_types.structs_ << _cnp_types.enums_ << _cnp_types.tuples_ << _cnp_types.unions_; return _os; diff --git a/src/rfl/capnproto/to_schema.cpp b/src/rfl/capnproto/to_schema.cpp index 27ee2f75c..6d166e286 100644 --- a/src/rfl/capnproto/to_schema.cpp +++ b/src/rfl/capnproto/to_schema.cpp @@ -139,6 +139,45 @@ schema::Type reference_to_capnproto_schema_type( } } +schema::Type string_map_to_capnproto_schema_type( + const parsing::schema::Type::StringMap& _map, + const std::map& _definitions, + schema::CapnProtoTypes* _cnp_types) { + auto value_type = type_to_capnproto_schema_type( + *_map.value_type_, _definitions, Parent::is_not_a_struct, _cnp_types); + const bool is_scalar = value_type.reflection().visit([&](const auto& _value) { + using T = std::remove_cvref_t; + using Type = schema::Type; + if constexpr (std::is_same_v) { + const auto it = _definitions.find(_value.type_name); + return _cnp_types->enums_.contains(_value.type_name) || + (it != _definitions.end() && + it->second.variant_.visit([](const auto& _definition) { + return std::is_same_v< + std::remove_cvref_t, + parsing::schema::Type::Literal>; + })); + } else { + return !std::is_same_v && !std::is_same_v && + !std::is_same_v && !std::is_same_v && + !std::is_same_v && + !std::is_same_v; + } + }); + if (is_scalar) { + // Cap'n Proto generic parameters must be pointer types. Keep scalar values + // directly in a concrete Entry struct instead of instantiating Map(Value). + const auto name = std::string("ScalarMap") + + std::to_string(_cnp_types->scalar_maps_.size() + 1); + _cnp_types->scalar_maps_.emplace(name, std::move(value_type)); + return schema::Type{.value = schema::Type::Reference{name}}; + } + _cnp_types->has_maps_ = true; + return schema::Type{ + .value = schema::Type::Map{ + .type = Ref::make(std::move(value_type))}}; +} + schema::Type type_to_capnproto_schema_type( const parsing::schema::Type& _type, const std::map& _definitions, @@ -223,12 +262,7 @@ schema::Type type_to_capnproto_schema_type( _cnp_types); } else if constexpr (std::is_same()) { - _cnp_types->has_maps_ = true; - return schema::Type{ - .value = schema::Type::Map{ - .type = Ref::make(type_to_capnproto_schema_type( - *_t.value_type_, _definitions, Parent::is_not_a_struct, - _cnp_types))}}; + return string_map_to_capnproto_schema_type(_t, _definitions, _cnp_types); } else if constexpr (std::is_same()) { return type_to_capnproto_schema_type( diff --git a/tests/capnproto/test_map_value_types.cpp b/tests/capnproto/test_map_value_types.cpp index f53c375ff..9950b1204 100644 --- a/tests/capnproto/test_map_value_types.cpp +++ b/tests/capnproto/test_map_value_types.cpp @@ -1,6 +1,11 @@ +#include +#include #include +#include #include #include +#include +#include #include "write_and_read.hpp" @@ -22,4 +27,79 @@ TEST(capnproto, test_map_with_string_values) { write_and_read(homer); } + +template +struct MapValues { + Map values; +}; + +template +void check_map_values(const Map& _values) { + using T = MapValues; + const auto serialized = rfl::capnproto::write(T{.values = _values}); + const auto restored = rfl::capnproto::read(serialized); + ASSERT_TRUE(restored) << restored.error().what(); + EXPECT_EQ(restored.value().values, _values); +} + +TEST(capnproto, test_map_with_integer_values) { + check_map_values(std::map{}); + check_map_values(std::map{ + {"negative", -42}, {"zero", 0}, {"positive", 42}}); + check_map_values(std::map{ + {"zero", 0}, {"maximum", std::numeric_limits::max()}}); + check_map_values(std::unordered_map{}); + check_map_values(std::unordered_map{ + {"minimum", std::numeric_limits::min()}, + {"large", std::int64_t{1} << 40}, + {"maximum", std::numeric_limits::max()}}); + check_map_values(std::map{ + {"zero", 0}, {"maximum", std::numeric_limits::max()}}); +} + +TEST(capnproto, test_map_with_bool_and_floating_point_values) { + check_map_values( + std::map{{"false", false}, {"true", true}}); + check_map_values( + std::map{{"negative", -1.25f}, {"positive", 2.5f}}); + check_map_values(std::unordered_map{{"negative", -1.25}, + {"positive", 2.5}}); +} + +enum class Color { red, green, blue }; + +TEST(capnproto, test_map_with_enum_values) { + check_map_values(std::map{{"first", Color::red}, + {"second", Color::blue}}); + using Literal = rfl::Literal<"red", "green", "blue">; + check_map_values(std::unordered_map{ + {"first", Literal::make<"red">()}, {"second", Literal::make<"blue">()}}); +} + +struct MixedMaps { + std::map integers; + std::map booleans; + std::map colors; + std::map strings; + std::map> lists; + std::map> optionals; + std::map> nested; + + bool operator==(const MixedMaps&) const = default; +}; + +TEST(capnproto, test_mixed_and_nested_map_value_types) { + const auto original = + MixedMaps{.integers = {{"negative", -42}}, + .booleans = {{"true", true}}, + .colors = {{"red", Color::red}}, + .strings = {{"first", "hello"}}, + .lists = {{"first", {1, 2, 3}}}, + .optionals = {{"present", 42}, {"absent", std::nullopt}}, + .nested = {{"first", {{"second", 42}}}}}; + const auto serialized = rfl::capnproto::write(original); + const auto restored = rfl::capnproto::read(serialized); + ASSERT_TRUE(restored) << restored.error().what(); + EXPECT_EQ(restored.value(), original); +} } // namespace test_map_value_types