diff --git a/modules/serdes/CMakeLists.txt b/modules/serdes/CMakeLists.txt index bfa5e2e0..373a1f58 100644 --- a/modules/serdes/CMakeLists.txt +++ b/modules/serdes/CMakeLists.txt @@ -16,24 +16,24 @@ find_package(range-v3 REQUIRED) # library sources set(SOURCES - src/serdes.cpp - src/type_info.cpp - src/dynamic_deserializer.cpp src/protobuf/buffers.cpp src/protobuf/dynamic_deserializer.cpp - src/protobuf/buffers.cpp - src/protobuf/protobuf.cpp + src/protobuf/enums.cpp src/protobuf/protobuf_internal.cpp + src/protobuf/protobuf.cpp + src/dynamic_deserializer.cpp + src/serdes.cpp + src/type_info.cpp README.md - include/hephaestus/serdes/dynamic_deserializer.h - include/hephaestus/serdes/serdes.h - include/hephaestus/serdes/type_info.h include/hephaestus/serdes/protobuf/buffers.h include/hephaestus/serdes/protobuf/concepts.h - include/hephaestus/serdes/protobuf/buffers.h include/hephaestus/serdes/protobuf/dynamic_deserializer.h - include/hephaestus/serdes/protobuf/protobuf.h + include/hephaestus/serdes/protobuf/enums.h include/hephaestus/serdes/protobuf/protobuf_internal.h + include/hephaestus/serdes/protobuf/protobuf.h + include/hephaestus/serdes/dynamic_deserializer.h + include/hephaestus/serdes/serdes.h + include/hephaestus/serdes/type_info.h ) # library target diff --git a/modules/serdes/include/hephaestus/serdes/protobuf/enums.h b/modules/serdes/include/hephaestus/serdes/protobuf/enums.h new file mode 100644 index 00000000..74075573 --- /dev/null +++ b/modules/serdes/include/hephaestus/serdes/protobuf/enums.h @@ -0,0 +1,117 @@ +//================================================================================================= +// Copyright (C) 2023-2024 HEPHAESTUS Contributors +//================================================================================================= + +#pragma once + +#include +#include +#include + +#include +#include + +#include "hephaestus/utils/concepts.h" +#include "hephaestus/utils/exception.h" +#include "hephaestus/utils/format/format.h" +#include "hephaestus/utils/string/string_literal.h" +#include "hephaestus/utils/string/string_utils.h" + +namespace heph::serdes::protobuf { + +template +[[nodiscard]] auto toProtoEnum(const T& enum_value) -> ProtoT; + +template +auto fromProto(const ProtoT& proto_enum_value, T& enum_value) -> void; + +//================================================================================================= +// Implementation +//================================================================================================= + +namespace internal { +template +[[nodiscard]] auto getProtoPrefix() -> std::string { + const auto enum_type_name = magic_enum::enum_type_name(); + + // Underscores indicate nested proto enums, no underscore indicates a top-level proto enum. + if (const auto underscore_pos = std::find(enum_type_name.begin(), enum_type_name.end(), '_'); + underscore_pos == enum_type_name.end()) { + // Top-level enums use the enum name as a prefix: ENUM_NAME_ENUM_VALUE. + return utils::string::toScreamingSnakeCase(enum_type_name); + } + + // Nested enums have a prefix, and enum values are separated by an underscore: + // ClassName_EnumName_ENUM_VALUE. + return fmt::format("{}", enum_type_name); +} + +/// Convert between enums and their protobuf counterparts. The following convention is used: +/// enum class FooExternalEnum : { BAR1, BAR2 }; +/// struct Foo { +/// enum class InternalEnum : { BAR1, BAR2 }; +/// }; +/// will have a protobuf counterpart +/// enum FooExternalEnum : { FOO_EXTERNAL_ENUM_BAR1, FOO_EXTERNAL_ENUM_BAR2 }; +/// enum Foo_InternalEnum : { Foo_InternalEnum_BAR1, Foo_InternalEnum_BAR2 }; +template +[[nodiscard]] auto getAsProtoEnum(T e) -> ProtoT { + const auto proto_enum_name = fmt::format("{}_{}", getProtoPrefix(), magic_enum::enum_name(e)); + const auto proto_enum = magic_enum::enum_cast(proto_enum_name); + + heph::throwExceptionIf( + !proto_enum.has_value(), + fmt::format("The proto enum does not contain the requested key {}. Proto enum keys are\n{}", + proto_enum_name, utils::format::toString(magic_enum::enum_names()))); + + return proto_enum.value(); // NOLINT(bugprone-unchecked-optional-access) +} + +template +[[nodiscard]] auto createEnumLookupTable() -> std::unordered_map { + std::unordered_map lookup_table; + + for (const auto& e : magic_enum::enum_values()) { + lookup_table[e] = getAsProtoEnum(e); + } + + return lookup_table; +} + +/// @brief Create inverse lookup table. Unique values are guaranteed by the enum layout. +template +[[nodiscard]] auto createInverseLookupTable(const std::unordered_map& enum_to_proto_enum) + -> std::unordered_map { + std::unordered_map proto_enum_to_enum; + + for (const auto& kvp : enum_to_proto_enum) { + proto_enum_to_enum.insert({ kvp.second, kvp.first }); + } + + return proto_enum_to_enum; +} +} // namespace internal + +template +auto toProtoEnum(const T& enum_value) -> ProtoT { + static const auto enum_to_proto_enum = internal::createEnumLookupTable(); + const auto it = enum_to_proto_enum.find(enum_value); + throwExceptionIf( + it == enum_to_proto_enum.end(), + fmt::format("Enum {} not found in the lookup table", utils::format::toString(enum_value))); + return it->second; +} + +template +auto fromProto(const ProtoT& proto_enum_value, T& enum_value) -> void { + static const auto proto_enum_value_to_enum = + internal::createInverseLookupTable(internal::createEnumLookupTable()); + const auto it = proto_enum_value_to_enum.find(proto_enum_value); + throwExceptionIf( + it == proto_enum_value_to_enum.end(), + fmt::format("Enum {} not found in the lookup table", utils::format::toString(proto_enum_value))); + enum_value = it->second; + ; +} + +} // namespace heph::serdes::protobuf diff --git a/modules/serdes/src/protobuf/enums.cpp b/modules/serdes/src/protobuf/enums.cpp new file mode 100644 index 00000000..b4707283 --- /dev/null +++ b/modules/serdes/src/protobuf/enums.cpp @@ -0,0 +1,5 @@ +//================================================================================================= +// Copyright (C) 2023-2024 HEPHAESTUS Contributors +//================================================================================================= + +#include "hephaestus/serdes/protobuf/enums.h" // NOLINT(misc-include-cleaner) diff --git a/modules/types/include/hephaestus/types/dummy_type.h b/modules/types/include/hephaestus/types/dummy_type.h index 1dda9dda..c0c0985f 100644 --- a/modules/types/include/hephaestus/types/dummy_type.h +++ b/modules/types/include/hephaestus/types/dummy_type.h @@ -42,11 +42,13 @@ struct DummyPrimitivesType { auto operator<<(std::ostream& os, const DummyPrimitivesType& dummy_primitives_type) -> std::ostream&; +enum class ExternalDummyEnum : int8_t { A, B, C, D, E, F, G }; + /// @brief Collection of non-primitive types for testing purposes. /// NOTE: the data needs to be Protobuf serializable /// NOTE: missing generic non-primitive types can be added to increase the test coverage struct DummyType { - enum class DummyEnum : int8_t { A, B, C, D, E, F, G }; + enum class InternalDummyEnum : int8_t { ALPHA }; [[nodiscard]] auto operator==(const DummyType&) const -> bool = default; @@ -54,7 +56,8 @@ struct DummyType { DummyPrimitivesType dummy_primitives_type{}; - DummyEnum dummy_enum{}; + InternalDummyEnum internal_dummy_enum{}; + ExternalDummyEnum external_dummy_enum{}; std::string dummy_string{}; std::vector dummy_vector{}; diff --git a/modules/types/src/dummy_type.cpp b/modules/types/src/dummy_type.cpp index 3cee4f5b..19351e17 100644 --- a/modules/types/src/dummy_type.cpp +++ b/modules/types/src/dummy_type.cpp @@ -8,8 +8,6 @@ #include #include -#include - #include "hephaestus/random/random_object_creator.h" #include "hephaestus/utils/format/format.h" @@ -48,7 +46,8 @@ auto operator<<(std::ostream& os, const DummyPrimitivesType& dummy_primitives_ty auto DummyType::random(std::mt19937_64& mt) -> DummyType { return { .dummy_primitives_type = random::random(mt), - .dummy_enum = random::random(mt), + .internal_dummy_enum = random::random(mt), + .external_dummy_enum = random::random(mt), .dummy_string = random::random(mt), .dummy_vector = random::random(mt) }; } @@ -56,7 +55,8 @@ auto DummyType::random(std::mt19937_64& mt) -> DummyType { auto operator<<(std::ostream& os, const DummyType& dummy_type) -> std::ostream& { return os << "DummyType{\n" << " dummy_primitives_type={" << dummy_type.dummy_primitives_type << "}\n" - << " dummy_enum=" << magic_enum::enum_name(dummy_type.dummy_enum) << "\n" + << " internal_dummy_enum=" << utils::format::toString(dummy_type.internal_dummy_enum) << "\n" + << " external_dummy_enum=" << utils::format::toString(dummy_type.external_dummy_enum) << "\n" << " dummy_string=" << dummy_type.dummy_string << "\n" << " dummy_vector=" << utils::format::toString(dummy_type.dummy_vector) << "\n" << "}"; diff --git a/modules/types_proto/proto/hephaestus/types/proto/dummy_type.proto b/modules/types_proto/proto/hephaestus/types/proto/dummy_type.proto index 7cac38cf..dfae36d9 100644 --- a/modules/types_proto/proto/hephaestus/types/proto/dummy_type.proto +++ b/modules/types_proto/proto/hephaestus/types/proto/dummy_type.proto @@ -6,14 +6,14 @@ syntax = "proto3"; package heph.types.proto; -enum DummyTypeDummyEnum { - A = 0; - B = 1; - C = 2; - D = 3; - E = 4; - F = 5; - G = 6; +enum DummyTypeExternalDummyEnum { + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_A = 0; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_B = 1; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_C = 2; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_D = 3; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_E = 4; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_F = 5; + DUMMY_TYPE_EXTERNAL_DUMMY_ENUM_G = 6; } message DummyPrimitivesType { @@ -34,10 +34,15 @@ message DummyPrimitivesType { } message DummyType { + enum InternalDummyEnum { + ALPHA = 0; + } + DummyPrimitivesType dummy_primitives_type = 1; - DummyTypeDummyEnum dummy_enum = 2; + InternalDummyEnum internal_dummy_enum = 2; + DummyTypeExternalDummyEnum external_dummy_enum = 3; - string dummy_string = 3; - repeated int32 dummy_vector = 4; + string dummy_string = 4; + repeated int32 dummy_vector = 5; } diff --git a/modules/types_proto/src/dummy_type.cpp b/modules/types_proto/src/dummy_type.cpp index 74385fe6..3aadcbbd 100644 --- a/modules/types_proto/src/dummy_type.cpp +++ b/modules/types_proto/src/dummy_type.cpp @@ -7,6 +7,7 @@ #include #include +#include "hephaestus/serdes/protobuf/enums.h" #include "hephaestus/types/dummy_type.h" #include "hephaestus/types/proto/dummy_type.pb.h" @@ -35,51 +36,6 @@ auto fromProto(const google::protobuf::RepeatedField& proto_repeated_fie } } -//================================================================================================= -// DummyTypeDummyEnum -//================================================================================================= -auto getAsProto(const DummyType::DummyEnum& dummy_enum) -> proto::DummyTypeDummyEnum { - switch (dummy_enum) { - case DummyType::DummyEnum::A: - return proto::DummyTypeDummyEnum::A; - case DummyType::DummyEnum::B: - return proto::DummyTypeDummyEnum::B; - case DummyType::DummyEnum::C: - return proto::DummyTypeDummyEnum::C; - case DummyType::DummyEnum::D: - return proto::DummyTypeDummyEnum::D; - case DummyType::DummyEnum::E: - return proto::DummyTypeDummyEnum::E; - case DummyType::DummyEnum::F: - return proto::DummyTypeDummyEnum::F; - case DummyType::DummyEnum::G: - return proto::DummyTypeDummyEnum::G; - default: // A default case is needed to suppress warnings about unhandled enum values. - return {}; - } -} - -auto getFromProto(const proto::DummyTypeDummyEnum& proto_dummy_enum) -> DummyType::DummyEnum { - switch (proto_dummy_enum) { - case proto::DummyTypeDummyEnum::A: - return DummyType::DummyEnum::A; - case proto::DummyTypeDummyEnum::B: - return DummyType::DummyEnum::B; - case proto::DummyTypeDummyEnum::C: - return DummyType::DummyEnum::C; - case proto::DummyTypeDummyEnum::D: - return DummyType::DummyEnum::D; - case proto::DummyTypeDummyEnum::E: - return DummyType::DummyEnum::E; - case proto::DummyTypeDummyEnum::F: - return DummyType::DummyEnum::F; - case proto::DummyTypeDummyEnum::G: - return DummyType::DummyEnum::G; - default: // A default case is needed to capture values autogenerated by protobuf. - return {}; - } -} - //================================================================================================= // DummyPrimitivesType //================================================================================================= @@ -126,10 +82,14 @@ auto fromProto(const proto::DummyPrimitivesType& proto_dummy_primitives_type, //================================================================================================= // DummyType //================================================================================================= + auto toProto(proto::DummyType& proto_dummy_type, const DummyType& dummy_type) -> void { toProto(*proto_dummy_type.mutable_dummy_primitives_type(), dummy_type.dummy_primitives_type); - proto_dummy_type.set_dummy_enum(getAsProto(dummy_type.dummy_enum)); + proto_dummy_type.set_internal_dummy_enum( + serdes::protobuf::toProtoEnum(dummy_type.internal_dummy_enum)); + proto_dummy_type.set_external_dummy_enum( + serdes::protobuf::toProtoEnum(dummy_type.external_dummy_enum)); proto_dummy_type.set_dummy_string(dummy_type.dummy_string); @@ -139,7 +99,8 @@ auto toProto(proto::DummyType& proto_dummy_type, const DummyType& dummy_type) -> auto fromProto(const proto::DummyType& proto_dummy_type, DummyType& dummy_type) -> void { fromProto(proto_dummy_type.dummy_primitives_type(), dummy_type.dummy_primitives_type); - dummy_type.dummy_enum = getFromProto(proto_dummy_type.dummy_enum()); + serdes::protobuf::fromProto(proto_dummy_type.internal_dummy_enum(), dummy_type.internal_dummy_enum); + serdes::protobuf::fromProto(proto_dummy_type.external_dummy_enum(), dummy_type.external_dummy_enum); dummy_type.dummy_string = proto_dummy_type.dummy_string(); diff --git a/modules/types_proto/tests/serialization_tests.cpp b/modules/types_proto/tests/serialization_tests.cpp index acbe9e8e..a1b1d0d1 100644 --- a/modules/types_proto/tests/serialization_tests.cpp +++ b/modules/types_proto/tests/serialization_tests.cpp @@ -46,7 +46,8 @@ TYPED_TEST(SerializationTests, TestSerialization) { const auto value = random::random(mt); TypeParam value_des{}; - if constexpr (!std::is_same_v) { // sample space of bool is too small + if constexpr (!std::is_same_v || !std::is_same_v || + !std::is_same_v) { // sample space of bool is too small EXPECT_NE(value, value_des); }