Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Types proto enum serialization #176

Merged
merged 19 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/ipc/apps/zenoh_topic_echo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
#include "hephaestus/ipc/zenoh/session.h"
#include "hephaestus/serdes/dynamic_deserializer.h"
#include "hephaestus/serdes/type_info.h"
#include "hephaestus/types/type_formatting.h"
#include "hephaestus/utils/exception.h"
#include "hephaestus/utils/format/format.h"
#include "hephaestus/utils/signal_handler.h"
#include "hephaestus/utils/stack_trace.h"

Expand Down Expand Up @@ -95,7 +95,7 @@ class TopicEcho {
truncateLongItems(msg_json, noarr_, max_array_length_);
fmt::println("From: {}. Topic: {}\nSequence: {} | Timestamp: {}\n{}", metadata.sender_id, metadata.topic,
metadata.sequence_id,
types::toString(std::chrono::time_point<std::chrono::system_clock>(
utils::format::toString(std::chrono::time_point<std::chrono::system_clock>(
std::chrono::duration_cast<std::chrono::system_clock::duration>(metadata.timestamp))),
msg_json);
}
Expand Down
6 changes: 6 additions & 0 deletions modules/serdes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,22 @@ 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/dynamic_deserializer.cpp
src/protobuf/enums.cpp
src/protobuf/protobuf.cpp
src/protobuf/protobuf_internal.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/concepts.h
include/hephaestus/serdes/protobuf/enums.h
include/hephaestus/serdes/protobuf/dynamic_deserializer.h
include/hephaestus/serdes/protobuf/protobuf.h
include/hephaestus/serdes/protobuf/protobuf_internal.h
Expand Down
120 changes: 120 additions & 0 deletions modules/serdes/include/hephaestus/serdes/protobuf/enums.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//=================================================================================================
// Copyright (C) 2023-2024 HEPHAESTUS Contributors
//=================================================================================================

#pragma once

#include <string_view>
#include <type_traits>
#include <unordered_map>

#include <fmt/core.h>
#include <magic_enum.hpp>

#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 <EnumType T, EnumType ProtoT>
[[nodiscard]] auto toProtoEnum(const T& enum_value) -> ProtoT;

template <EnumType T, EnumType ProtoT>
auto fromProto(const ProtoT& proto_enum_value, T& enum_value) -> void;
lhruby marked this conversation as resolved.
Show resolved Hide resolved

//=================================================================================================
// Implementation
//=================================================================================================

namespace internal {
template <EnumType ProtoT>
[[nodiscard]] auto getProtoPrefix() -> std::string {
const auto enum_type_name = magic_enum::enum_type_name<ProtoT>();

// Underscores indicate nested proto enums, no underscore means it's a top-level proto enum. Top level enums
// do not have a prefix.
if (const auto underscore_pos = std::find(enum_type_name.begin(), enum_type_name.end(), '_');
underscore_pos == enum_type_name.end()) {
return "";
}

// 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 : { BAR1, BAR2 };
/// enum Foo_InternalEnum : { Foo_InternalEnum_BAR1, Foo_InternalEnum_BAR2 };
template <EnumType T, EnumType ProtoT>
[[nodiscard]] auto getAsProtoEnum(T e) -> ProtoT {
const auto proto_prefix = getProtoPrefix<ProtoT>();
auto proto_enum_name =
fmt::format("{}{}", proto_prefix, magic_enum::enum_name(e)); // ClassName_EnumName_ENUM_VALUE

auto proto_enum = magic_enum::enum_cast<ProtoT>(proto_enum_name);
heph::throwExceptionIf<heph::InvalidParameterException>(
!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<ProtoT>())));

return proto_enum.value();
}

template <EnumType T, EnumType ProtoT>
[[nodiscard]] auto createEnumLookupTable() -> std::unordered_map<T, ProtoT> {
std::unordered_map<T, ProtoT> lookup_table;

for (const auto& e : magic_enum::enum_values<T>()) {
lookup_table[e] = getAsProtoEnum<T, ProtoT>(e);
}

return lookup_table;
}

/// @brief Create inverse lookup table. Unique values are guaranteed by the enum layout.
template <EnumType T, EnumType ProtoT>
[[nodiscard]] auto createInverseLookupTable(const std::unordered_map<T, ProtoT>& enum_to_proto_enum)
-> std::unordered_map<ProtoT, T> {
std::unordered_map<ProtoT, T> 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 <EnumType T, EnumType ProtoT>
auto toProtoEnum(const T& enum_value) -> ProtoT {
static const auto enum_to_proto_enum = internal::createEnumLookupTable<T, ProtoT>();
try {
return enum_to_proto_enum.at(enum_value);
} catch (const std::out_of_range&) {
throw std::out_of_range(
fmt::format("Enum {} not found in the lookup table", magic_enum::enum_name(enum_value)));
}
lhruby marked this conversation as resolved.
Show resolved Hide resolved
}

template <EnumType T, EnumType ProtoT>
auto fromProto(const ProtoT& proto_enum_value, T& enum_value) -> void {
static const auto proto_enum_value_to_enum =
internal::createInverseLookupTable(internal::createEnumLookupTable<T, ProtoT>());
try {
enum_value = proto_enum_value_to_enum.at(proto_enum_value);
} catch (const std::out_of_range&) {
throw std::out_of_range(
fmt::format("Proto enum {} not found in the lookup table", magic_enum::enum_name(proto_enum_value)));
}
lhruby marked this conversation as resolved.
Show resolved Hide resolved
}

} // namespace heph::serdes::protobuf
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
// Copyright (C) 2023-2024 HEPHAESTUS Contributors
//=================================================================================================

#include "hephaestus/types/type_formatting.h" // NOLINT(misc-include-cleaner)
#include "hephaestus/serdes/protobuf/enums.h" // NOLINT(misc-include-cleaner)
10 changes: 2 additions & 8 deletions modules/types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@ find_package(magic_enum REQUIRED)
find_package(range-v3 REQUIRED)

# library sources
set(SOURCES
src/bounds.cpp
src/dummy_type.cpp
src/type_formatting.cpp
README.md
include/hephaestus/types/bounds.h
include/hephaestus/types/dummy_type.h
include/hephaestus/types/type_formatting.h
set(SOURCES src/bounds.cpp src/dummy_type.cpp README.md include/hephaestus/types/bounds.h
include/hephaestus/types/dummy_type.h
)

# library target
Expand Down
7 changes: 5 additions & 2 deletions modules/types/include/hephaestus/types/dummy_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,22 @@ 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;

[[nodiscard]] static auto random(std::mt19937_64& mt) -> DummyType;

DummyPrimitivesType dummy_primitives_type{};

DummyEnum dummy_enum{};
InternalDummyEnum internal_dummy_enum{ InternalDummyEnum::ALPHA };
ExternalDummyEnum external_dummy_enum{ ExternalDummyEnum::A };

std::string dummy_string{};
std::vector<int32_t> dummy_vector{};
Expand Down
13 changes: 7 additions & 6 deletions modules/types/src/dummy_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <magic_enum.hpp>

#include "hephaestus/random/random_object_creator.h"
#include "hephaestus/types/type_formatting.h"
#include "hephaestus/utils/format/format.h"

namespace heph::types {

Expand Down Expand Up @@ -48,18 +48,19 @@ auto operator<<(std::ostream& os, const DummyPrimitivesType& dummy_primitives_ty

auto DummyType::random(std::mt19937_64& mt) -> DummyType {
return { .dummy_primitives_type = random::random<decltype(dummy_primitives_type)>(mt),
.dummy_enum = random::random<decltype(dummy_enum)>(mt),
.internal_dummy_enum = random::random<decltype(internal_dummy_enum)>(mt),
.external_dummy_enum = random::random<decltype(external_dummy_enum)>(mt),
.dummy_string = random::random<decltype(dummy_string)>(mt),
.dummy_vector = random::random<decltype(dummy_vector)>(mt) };
}

auto operator<<(std::ostream& os, const DummyType& dummy_type) -> std::ostream& {
return os << "DummyType{"
<< "\n"
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=" << magic_enum::enum_name(dummy_type.internal_dummy_enum) << "\n"
<< " external_dummy_enum=" << magic_enum::enum_name(dummy_type.external_dummy_enum) << "\n"
<< " dummy_string=" << dummy_type.dummy_string << "\n"
<< " dummy_vector=" << toString(dummy_type.dummy_vector) << "\n"
<< " dummy_vector=" << utils::format::toString(dummy_type.dummy_vector) << "\n"
<< "}";
}

Expand Down
5 changes: 0 additions & 5 deletions modules/types/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,3 @@ define_module_test(
NAME tests
SOURCES tests.cpp
)

define_module_test(
NAME type_formatting_tests
SOURCES type_formatting_tests.cpp
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ syntax = "proto3";

package heph.types.proto;

enum DummyTypeDummyEnum {
enum DummyTypeExternalDummyEnum {
A = 0;
B = 1;
C = 2;
lhruby marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -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;
}
60 changes: 13 additions & 47 deletions modules/types_proto/src/dummy_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include <cstddef>
#include <vector>

#include <fmt/core.h>
#include <magic_enum.hpp>

#include "hephaestus/serdes/protobuf/enums.h"
#include "hephaestus/types/dummy_type.h"
#include "hephaestus/types/proto/dummy_type.pb.h"

Expand Down Expand Up @@ -35,51 +39,6 @@ auto fromProto(const google::protobuf::RepeatedField<ProtoT>& 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
//=================================================================================================
Expand Down Expand Up @@ -126,10 +85,16 @@ 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<DummyType::InternalDummyEnum, proto::DummyType::InternalDummyEnum>(
dummy_type.internal_dummy_enum));
proto_dummy_type.set_external_dummy_enum(
serdes::protobuf::toProtoEnum<ExternalDummyEnum, proto::DummyTypeExternalDummyEnum>(
dummy_type.external_dummy_enum));

proto_dummy_type.set_dummy_string(dummy_type.dummy_string);

Expand All @@ -139,7 +104,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();

Expand Down
2 changes: 2 additions & 0 deletions modules/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ configure_file(src/version.in ${CMAKE_CURRENT_SOURCE_DIR}/src/version_impl.h @ON
set(SOURCES
src/filesystem/file.cpp
src/filesystem/scoped_path.cpp
src/format/format.cpp
src/string/string_literal.cpp
src/string/string_utils.cpp
src/timing/watchdog.cpp
Expand All @@ -33,6 +34,7 @@ set(SOURCES
README.md
include/hephaestus/utils/filesystem/file.h
include/hephaestus/utils/filesystem/scoped_path.h
include/hephaestus/utils/format/format.h
include/hephaestus/utils/string/string_literal.h
include/hephaestus/utils/string/string_utils.h
include/hephaestus/utils/timing/watchdog.h
Expand Down
Loading