diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index d6b4e387..a8fb3de1 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -2,9 +2,9 @@ name: Clang Format Diff on: push: - branches: [ master] + branches: [ master, struct_pb ] pull_request: - branches: [ master] + branches: [ master, struct_pb ] jobs: build: diff --git a/.github/workflows/linux-clang.yml b/.github/workflows/linux-clang.yml index 3607a243..d3056a27 100644 --- a/.github/workflows/linux-clang.yml +++ b/.github/workflows/linux-clang.yml @@ -2,9 +2,9 @@ name: Ubuntu (clang) on: push: - branches: [ master] + branches: [ master, struct_pb ] pull_request: - branches: [ master] + branches: [ master, struct_pb ] jobs: build: diff --git a/.github/workflows/linux-gcc.yml b/.github/workflows/linux-gcc.yml index 4c40e46b..4b9db201 100644 --- a/.github/workflows/linux-gcc.yml +++ b/.github/workflows/linux-gcc.yml @@ -2,9 +2,9 @@ name: Ubuntu (gcc) on: push: - branches: [ master] + branches: [ master, struct_pb ] pull_request: - branches: [ master] + branches: [ master, struct_pb ] jobs: ubuntu_gcc: @@ -21,8 +21,11 @@ jobs: - name: checkout gcc version run: gcc --version + - name: Install Protobuf and Dev Package + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler libprotobuf-dev + - name: configure cmake - run: CXX=g++ CC=gcc cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DCMAKE_CXX_STANDARD=${{ matrix.cpp_version }} + run: CXX=g++ CC=gcc cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DHAS_PROTOBUF=ON -DCMAKE_CXX_STANDARD=${{ matrix.cpp_version }} - name: build project run: cmake --build ${{ github.workspace }}/build --config ${{ matrix.mode }} diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 6ea0450c..e51340f9 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -2,9 +2,9 @@ name: macOS Monterey 12 on: push: - branches: [ master] + branches: [ master, struct_pb ] pull_request: - branches: [ master] + branches: [ master, struct_pb ] jobs: build: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 28fe9cbc..21039968 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,10 +1,10 @@ name: Windows Server 2022 -on: +on: push: - branches: [ master] + branches: [ master, struct_pb ] pull_request: - branches: [ master] + branches: [ master, struct_pb ] jobs: build: diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f8882a..6e01e4ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 2.15) project(iguana) +message(STATUS "C++ Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}") + if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++20") add_compile_options(/utf-8) @@ -82,6 +84,9 @@ set(YAMLBENCH benchmark/yaml_benchmark.cpp) set(TEST_NOTHROW test/test_yaml_nothrow.cpp) set(TEST_UTIL test/test_util.cpp) set(TEST_XMLNOTHROW test/test_xml_nothrow.cpp) +set(TEST_PB test/test_pb.cpp) +set(TEST_PROTO test/test_proto3.cpp) +set(PB_BENCHMARK benchmark/pb_benchmark.cpp) add_executable(json_example ${JSON_EXAMPLE}) add_executable(json_benchmark ${JSONBENCHMARK}) @@ -97,6 +102,31 @@ add_executable(test_yaml ${TEST_YAML}) add_executable(yaml_benchmark ${YAMLBENCH}) add_executable(test_nothrow ${TEST_NOTHROW}) add_executable(test_util ${TEST_UTIL}) +add_executable(test_pb ${TEST_PB}) + +option(HAS_PROTOBUF "import protobuf" OFF) +if(HAS_PROTOBUF) + find_package(Protobuf REQUIRED) + if(Protobuf_FOUND) + message(STATUS "Found Protobuf: ${Protobuf_VERSION}") + else() + message(STATUS "Protobuf not found") + endif() + + include_directories(${Protobuf_INCLUDE_DIRS}) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + set(PROTO_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test/proto) + file(GLOB PROTO_FILES ${PROTO_SRC_DIR}/*.proto) + + protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES}) + message(STATUS "Proto source files: ${PROTO_SRCS}") + message(STATUS "Proto header files: ${PROTO_HDRS}") + + add_executable(pb_benchmark ${PB_BENCHMARK} ${PROTO_SRCS}) + target_link_libraries(pb_benchmark ${Protobuf_LIBRARIES}) + add_executable(test_proto ${PROTO_SRCS} ${TEST_PROTO}) + target_link_libraries(test_proto ${Protobuf_LIBRARIES}) +endif() # unit test option(BUILD_UNIT_TESTS "Build unit tests" ON) @@ -121,4 +151,5 @@ add_test(NAME test_xml COMMAND test_xml) add_test(NAME test_yaml COMMAND test_yaml) add_test(NAME test_nothrow COMMAND test_nothrow) add_test(NAME test_util COMMAND test_util) -add_test(NAME test_xml_nothrow COMMAND test_xml_nothrow) \ No newline at end of file +add_test(NAME test_xml_nothrow COMMAND test_xml_nothrow) +add_test(NAME test_pb COMMAND test_pb) \ No newline at end of file diff --git a/benchmark/pb_benchmark.cpp b/benchmark/pb_benchmark.cpp new file mode 100644 index 00000000..142f6143 --- /dev/null +++ b/benchmark/pb_benchmark.cpp @@ -0,0 +1,367 @@ +#include + +#include "../test/proto/unittest_proto3.h" + +class ScopedTimer { + public: + ScopedTimer(const char *name) + : m_name(name), m_beg(std::chrono::high_resolution_clock::now()) {} + ScopedTimer(const char *name, uint64_t &ns) : ScopedTimer(name) { + m_ns = &ns; + } + ~ScopedTimer() { + auto end = std::chrono::high_resolution_clock::now(); + auto dur = + std::chrono::duration_cast(end - m_beg); + if (m_ns) + *m_ns = dur.count(); + else + std::cout << std::left << std::setw(45) << m_name << " : " << std::right + << std::setw(12) << dur.count() << " ns\n"; + } + + private: + const char *m_name; + std::chrono::time_point m_beg; + uint64_t *m_ns = nullptr; +}; + +void bench(int Count) { + // init the benchmark data + stpb::BaseTypeMsg base_type_st{{}, + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + true, + std::string(40, 'x'), + stpb::Enum::BAZ}; + pb::BaseTypeMsg base_type_msg; + SetBaseTypeMsg(base_type_st, base_type_msg); + + stpb::IguanaTypeMsg iguana_type_st{{}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}}; + pb::IguanaTypeMsg iguana_type_msg{}; + SetIguanaTypeMsg(iguana_type_st, iguana_type_msg); + + stpb::RepeatBaseTypeMsg re_base_type_st{ + {}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {}, + {std::numeric_limits::max(), std::numeric_limits::min()}, + {std::string(40, 'x'), std::string(40, 'x'), std::string(40, 'x')}, + {stpb::Enum::NEG, stpb::Enum::FOO}}; + pb::RepeatBaseTypeMsg re_base_type_msg; + SetRepeatBaseTypeMsg(re_base_type_st, re_base_type_msg); + + stpb::RepeatIguanaTypeMsg re_iguana_type_st{ + {}, + {{0}, {1}, {3}}, + {{4}, {5}, {6}}, + {{7}, {8}, {9}}, + {{10}, {11}, {12}}, + {{13}, {14}, {15}}, + {}, + }; + pb::RepeatIguanaTypeMsg re_iguana_type_msg; + SetRepeatIguanaTypeMsg(re_iguana_type_st, re_iguana_type_msg); + + stpb::NestedMsg nest_st{ + {}, + /* base_msg */ + {{}, 100, 200, 300, 400, 31.4f, 62.8, false, "World", stpb::Enum::BAZ}, + /* repeat_base_msg */ + {{{}, 1, 2, 3, 4, 5.5f, 6.6, true, "Hello", stpb::Enum::FOO}, + {{}, 7, 8, 9, 10, 11.11f, 12.12, false, "Hi", stpb::Enum::BAR}}, + /* iguana_type_msg */ {{}, {100}, {200}, {300}, {400}, {31}, {32}}, + /* repeat_iguna_msg */ + {{}, {{1}, {2}, {3}}, {{4}, {5}, {6}}, {{7}, {8}, {9}}}, + /* repeat_repeat_base_msg */ + {{{}, + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {10, 11, 12}, + {13.1, 14.2, 15.3}, + {16.4, 17.5, 18.6}, + {"a", "b", "c"}, + {stpb::Enum::FOO, stpb::Enum::BAR, stpb::Enum::BAZ}}, + {{}, + {19, 20, 21}, + {22, 23, 24}, + {25, 26, 27}, + {28, 29, 30}, + {31.1, 32.2, 33.3}, + {34.4, 35.5, 36.6}, + {"x", "y", "z"}, + {stpb::Enum::ZERO, stpb::Enum::NEG, stpb::Enum::FOO}}}}; + pb::NestedMsg nest_msg; + SetNestedMsg(nest_st, nest_msg); + + stpb::MapMsg map_st{}; + map_st.sfix64_str_map.emplace(iguana::sfixed64_t{10}, "ten"); + map_st.sfix64_str_map.emplace(iguana::sfixed64_t{20}, "twenty"); + + map_st.str_iguana_type_msg_map.emplace( + "first", stpb::IguanaTypeMsg{{}, {10}, {20}, {30}, {40}, {50}, {60}}); + map_st.str_iguana_type_msg_map.emplace( + "second", stpb::IguanaTypeMsg{{}, {11}, {21}, {31}, {41}, {51}, {61}}); + + map_st.int_repeat_base_msg_map.emplace( + 1, stpb::RepeatBaseTypeMsg{{}, + {1, 2}, + {3, 4}, + {5, 6}, + {7, 8}, + {9.0f, 10.0f}, + {11.0, 12.0}, + {"one", "two"}, + {stpb::Enum::FOO, stpb::Enum::BAR}}); + map_st.int_repeat_base_msg_map.emplace( + 2, stpb::RepeatBaseTypeMsg{{}, + {2, 3}, + {4, 5}, + {6, 7}, + {8, 9}, + {10.0f, 11.0f}, + {12.0, 13.0}, + {"three", "four"}, + {stpb::Enum::BAZ, stpb::Enum::NEG}}); + pb::MapMsg map_msg{}; + SetMapMsg(map_st, map_msg); + + stpb::BaseTypeMsg baseTypeMsg{{}, 100, 200, 300, 400, + 31.4f, 62.8, false, "World", stpb::Enum::BAZ}; + stpb::BaseOneofMsg base_one_of_st{{}, 123, baseTypeMsg, 456.78}; + pb::BaseOneofMsg base_one_of_msg; + SetBaseOneofMsg(base_one_of_st, base_one_of_msg); + + // iguana serialization benchmark + { + ScopedTimer timer("struct_pb serialize"); + for (int i = 0; i < Count; ++i) { + std::string base_type_st_ss; + iguana::to_pb(base_type_st, base_type_st_ss); + + std::string iguana_type_st_ss; + iguana::to_pb(iguana_type_st, iguana_type_st_ss); + + std::string re_base_type_st_ss; + iguana::to_pb(re_base_type_st, re_base_type_st_ss); + + std::string re_iguana_type_st_ss; + iguana::to_pb(re_iguana_type_st, re_iguana_type_st_ss); + + std::string nest_st_ss; + iguana::to_pb(nest_st, nest_st_ss); + + std::string map_st_ss; + iguana::to_pb(map_st, map_st_ss); + + std::string base_one_of_st_ss; + iguana::to_pb(base_one_of_st, base_one_of_st_ss); + } + } + + // protobuf serialization benchmark + { + ScopedTimer timer("protobuf serialize"); + for (int i = 0; i < Count; ++i) { + std::string base_type_msg_ss; + base_type_msg_ss.reserve(base_type_msg.ByteSizeLong()); + base_type_msg.SerializeToString(&base_type_msg_ss); + + std::string iguana_type_msg_ss; + iguana_type_msg_ss.reserve(iguana_type_msg.ByteSizeLong()); + iguana_type_msg.SerializeToString(&iguana_type_msg_ss); + + std::string re_base_type_msg_ss; + re_base_type_msg_ss.reserve(re_base_type_msg.ByteSizeLong()); + re_base_type_msg.SerializeToString(&re_base_type_msg_ss); + + std::string re_iguana_type_msg_ss; + re_iguana_type_msg_ss.reserve(re_iguana_type_msg.ByteSizeLong()); + re_iguana_type_msg.SerializeToString(&re_iguana_type_msg_ss); + + std::string nest_msg_ss; + nest_msg.SerializeToString(&nest_msg_ss); + + std::string map_msg_ss; + map_msg.SerializeToString(&map_msg_ss); + + std::string base_one_of_msg_ss; + base_one_of_msg.SerializeToString(&base_one_of_msg_ss); + } + } + + // prepare the string for deserialization benchmark + std::string base_type_st_ss; + iguana::to_pb(base_type_st, base_type_st_ss); + + std::string iguana_type_st_ss; + iguana::to_pb(iguana_type_st, iguana_type_st_ss); + + std::string re_base_type_st_ss; + iguana::to_pb(re_base_type_st, re_base_type_st_ss); + + std::string re_iguana_type_st_ss; + iguana::to_pb(re_iguana_type_st, re_iguana_type_st_ss); + + std::string nest_st_ss; + iguana::to_pb(nest_st, nest_st_ss); + + std::string map_st_ss; + iguana::to_pb(map_st, map_st_ss); + + std::string base_one_of_st_ss; + iguana::to_pb(base_one_of_st, base_one_of_st_ss); + + std::string base_type_msg_ss; + base_type_msg.SerializeToString(&base_type_msg_ss); + + std::string iguana_type_msg_ss; + iguana_type_msg.SerializeToString(&iguana_type_msg_ss); + + std::string re_base_type_msg_ss; + re_base_type_msg.SerializeToString(&re_base_type_msg_ss); + + std::string re_iguana_type_msg_ss; + re_iguana_type_msg.SerializeToString(&re_iguana_type_msg_ss); + + std::string nest_msg_ss; + nest_msg.SerializeToString(&nest_msg_ss); + + std::string map_msg_ss; + map_msg.SerializeToString(&map_msg_ss); + + std::string base_one_of_msg_ss; + base_one_of_msg.SerializeToString(&base_one_of_msg_ss); + + // ensure serialize correction + assert(base_type_st_ss == base_type_msg_ss); + assert(iguana_type_st_ss == iguana_type_msg_ss); + assert(re_base_type_st_ss == re_base_type_msg_ss); + assert(re_iguana_type_st_ss == re_iguana_type_msg_ss); + assert(nest_st_ss == nest_msg_ss); + assert(base_one_of_st_ss == base_one_of_msg_ss); + // iguana deserialization benchmark + { + ScopedTimer timer("struct_pb deserialize"); + for (int i = 0; i < Count; ++i) { + stpb::BaseTypeMsg base_type_st_de; + iguana::from_pb(base_type_st_de, base_type_st_ss); + + stpb::IguanaTypeMsg iguana_type_st_de; + iguana::from_pb(iguana_type_st_de, iguana_type_st_ss); + + stpb::RepeatBaseTypeMsg re_base_type_st_de; + iguana::from_pb(re_base_type_st_de, re_base_type_st_ss); + + stpb::RepeatIguanaTypeMsg re_iguana_type_st_de; + iguana::from_pb(re_iguana_type_st_de, re_iguana_type_st_ss); + + stpb::NestedMsg nest_st_de; + iguana::from_pb(nest_st_de, nest_st_ss); + + stpb::MapMsg map_st_de; + iguana::from_pb(map_st_de, map_st_ss); + + stpb::BaseOneofMsg base_one_of_st_de; + iguana::from_pb(base_one_of_st_de, base_one_of_st_ss); + } + } + + // protobuf deserialization benchmark + { + ScopedTimer timer("protobuf deserialize"); + for (int i = 0; i < Count; ++i) { + pb::BaseTypeMsg base_type_msg_de; + base_type_msg_de.ParseFromString(base_type_msg_ss); + + pb::IguanaTypeMsg iguana_type_msg_de; + iguana_type_msg_de.ParseFromString(iguana_type_msg_ss); + + pb::RepeatBaseTypeMsg re_base_type_msg_de; + re_base_type_msg_de.ParseFromString(re_base_type_msg_ss); + + pb::RepeatIguanaTypeMsg re_iguana_type_msg_de; + re_iguana_type_msg_de.ParseFromString(re_iguana_type_msg_ss); + + pb::NestedMsg nest_msg_de; + nest_msg_de.ParseFromString(nest_msg_ss); + + pb::MapMsg map_msg_de; + map_msg_de.ParseFromString(map_msg_ss); + + pb::BaseOneofMsg base_one_of_msg_de; + base_one_of_msg_de.ParseFromString(base_one_of_msg_ss); + } + } + + // ensure deserialize correction + stpb::BaseTypeMsg base_type_st_de{}; + iguana::from_pb(base_type_st_de, base_type_st_ss); + + stpb::IguanaTypeMsg iguana_type_st_de{}; + iguana::from_pb(iguana_type_st_de, iguana_type_st_ss); + + stpb::RepeatBaseTypeMsg re_base_type_st_de{}; + iguana::from_pb(re_base_type_st_de, re_base_type_st_ss); + + stpb::RepeatIguanaTypeMsg re_iguana_type_st_de{}; + iguana::from_pb(re_iguana_type_st_de, re_iguana_type_st_ss); + + stpb::NestedMsg nest_st_de{}; + iguana::from_pb(nest_st_de, nest_st_ss); + + stpb::MapMsg map_st_de{}; + iguana::from_pb(map_st_de, map_st_ss); + + stpb::BaseOneofMsg base_one_of_st_de{}; + iguana::from_pb(base_one_of_st_de, base_one_of_st_ss); + + pb::BaseTypeMsg base_type_msg_de; + base_type_msg_de.ParseFromString(base_type_msg_ss); + + pb::IguanaTypeMsg iguana_type_msg_de; + iguana_type_msg_de.ParseFromString(iguana_type_msg_ss); + + pb::RepeatBaseTypeMsg re_base_type_msg_de; + re_base_type_msg_de.ParseFromString(re_base_type_msg_ss); + + pb::RepeatIguanaTypeMsg re_iguana_type_msg_de; + re_iguana_type_msg_de.ParseFromString(re_iguana_type_msg_ss); + + pb::NestedMsg nest_msg_de; + nest_msg_de.ParseFromString(nest_msg_ss); + + pb::MapMsg map_msg_de; + map_msg_de.ParseFromString(map_msg_ss); + + pb::BaseOneofMsg base_one_of_msg_de; + base_one_of_msg_de.ParseFromString(base_one_of_msg_ss); + + CheckBaseTypeMsg(base_type_st_de, base_type_msg_de); + CheckIguanaTypeMsg(iguana_type_st_de, iguana_type_msg_de); + CheckRepeatBaseTypeMsg(re_base_type_st_de, re_base_type_msg_de); + CheckRepeatIguanaTypeMsg(re_iguana_type_st_de, re_iguana_type_msg_de); + CheckNestedMsg(nest_st_de, nest_msg_de); + CheckMapMsg(map_st_de, map_msg_de); + CheckBaseOneofMsg(base_one_of_st_de, base_one_of_msg_de); +} + +int main() { bench(100000); } diff --git a/iguana/detail/pb_type.hpp b/iguana/detail/pb_type.hpp new file mode 100644 index 00000000..6a06e21a --- /dev/null +++ b/iguana/detail/pb_type.hpp @@ -0,0 +1,135 @@ +#pragma once + +namespace iguana { + +struct sint32_t { + using value_type = int32_t; + int32_t val; + bool operator==(const sint32_t& other) const { return val == other.val; } +}; + +inline bool operator==(sint32_t value1, int32_t value2) { + return value1.val == value2; +} + +// for key in std::map +bool operator<(const sint32_t& lhs, const sint32_t& rhs) { + return lhs.val < rhs.val; +} + +struct sint64_t { + using value_type = int64_t; + int64_t val; + bool operator==(const sint64_t& other) const { return val == other.val; } +}; + +inline bool operator==(sint64_t value1, int64_t value2) { + return value1.val == value2; +} + +bool operator<(const sint64_t& lhs, const sint64_t& rhs) { + return lhs.val < rhs.val; +} + +struct fixed32_t { + using value_type = uint32_t; + uint32_t val; + bool operator==(const fixed32_t& other) const { return val == other.val; } +}; + +inline bool operator==(fixed32_t value1, uint32_t value2) { + return value1.val == value2; +} + +bool operator<(const fixed32_t& lhs, const fixed32_t& rhs) { + return lhs.val < rhs.val; +} + +struct fixed64_t { + using value_type = uint64_t; + uint64_t val; + bool operator==(const fixed64_t& other) const { return val == other.val; } +}; + +inline bool operator==(fixed64_t value1, uint64_t value2) { + return value1.val == value2; +} + +bool operator<(const fixed64_t& lhs, const fixed64_t& rhs) { + return lhs.val < rhs.val; +} + +struct sfixed32_t { + using value_type = int32_t; + int32_t val; + bool operator==(const sfixed32_t& other) const { return val == other.val; } +}; + +inline bool operator==(sfixed32_t value1, int32_t value2) { + return value1.val == value2; +} + +bool operator<(const sfixed32_t& lhs, const sfixed32_t& rhs) { + return lhs.val < rhs.val; +} + +struct sfixed64_t { + using value_type = int64_t; + int64_t val; + bool operator==(const sfixed64_t& other) const { return val == other.val; } +}; + +inline bool operator==(sfixed64_t value1, int64_t value2) { + return value1.val == value2; +} + +bool operator<(const sfixed64_t& lhs, const sfixed64_t& rhs) { + return lhs.val < rhs.val; +} + +} // namespace iguana + +// for key in std::unordered_map +namespace std { +template <> +struct hash { + size_t operator()(const iguana::sint32_t& x) const noexcept { + return std::hash()(x.val); + } +}; + +template <> +struct hash { + size_t operator()(const iguana::sint64_t& x) const noexcept { + return std::hash()(x.val); + } +}; + +template <> +struct hash { + size_t operator()(const iguana::fixed32_t& x) const noexcept { + return std::hash()(x.val); + } +}; + +template <> +struct hash { + size_t operator()(const iguana::fixed64_t& x) const noexcept { + return std::hash()(x.val); + } +}; + +template <> +struct hash { + size_t operator()(const iguana::sfixed32_t& x) const noexcept { + return std::hash()(x.val); + } +}; + +template <> +struct hash { + size_t operator()(const iguana::sfixed64_t& x) const noexcept { + return std::hash()(x.val); + } +}; +} // namespace std diff --git a/iguana/detail/traits.hpp b/iguana/detail/traits.hpp index d3b9aac8..b9e3e155 100644 --- a/iguana/detail/traits.hpp +++ b/iguana/detail/traits.hpp @@ -15,7 +15,6 @@ #include "iguana/define.h" - namespace iguana { template @@ -71,5 +70,39 @@ struct has_type> template inline constexpr bool is_int64_v = std::is_same_v || std::is_same_v; + +template +struct is_variant : std::false_type {}; + +template +struct is_variant> : std::true_type {}; + +template +struct member_traits { + using value_type = T; +}; + +template +struct member_traits { + using owner_type = Owner; + using value_type = T; +}; + +template +using member_value_type_t = typename member_traits::value_type; + +template +struct variant_type_at { + using type = T; +}; + +template +struct variant_type_at::value>> { + using type = std::variant_alternative_t; +}; +template +using variant_type_at_t = + typename variant_type_at::value_type, I>::type; + } // namespace iguana #endif // SERIALIZE_TRAITS_HPP diff --git a/iguana/pb_reader.hpp b/iguana/pb_reader.hpp new file mode 100644 index 00000000..550c9ba6 --- /dev/null +++ b/iguana/pb_reader.hpp @@ -0,0 +1,215 @@ +#pragma once +#include "pb_util.hpp" + +namespace iguana { + +template +IGUANA_INLINE void from_pb(T& t, std::string_view pb_str); + +namespace detail { + +template +IGUANA_INLINE void from_pb_impl(T& val, std::string_view& pb_str, + uint32_t field_no = 0); + +template +IGUANA_INLINE void decode_pair_value(T& val, std::string_view& pb_str) { + size_t pos; + uint32_t key = detail::decode_varint(pb_str, pos); + pb_str = pb_str.substr(pos); + WireType wire_type = static_cast(key & 0b0111); + if (wire_type != detail::get_wire_type>()) { + return; + } + from_pb_impl(val, pb_str); +} + +template +IGUANA_INLINE void from_pb_impl(T& val, std::string_view& pb_str, + uint32_t field_no) { + size_t pos = 0; + if constexpr (is_reflection_v) { + size_t pos; + uint32_t size = detail::decode_varint(pb_str, pos); + pb_str = pb_str.substr(pos); + if (pb_str.size() < size) + IGUANA_UNLIKELY { + throw std::invalid_argument("Invalid fixed int value: too few bytes."); + } + if (size == 0) { + return; + } + from_pb(val, pb_str.substr(0, size)); + pb_str = pb_str.substr(size); + } + else if constexpr (is_sequence_container::value) { + using item_type = typename T::value_type; + if constexpr (is_lenprefix_v) { + // item_type non-packed + while (!pb_str.empty()) { + item_type item{}; + from_pb_impl(item, pb_str); + val.push_back(std::move(item)); + if (pb_str.empty()) { + break; + } + uint32_t key = detail::decode_varint(pb_str, pos); + uint32_t field_number = key >> 3; + if (field_number != field_no) { + break; + } + else { + pb_str = pb_str.substr(pos); + } + } + } + else { + // item_type packed + size_t pos; + uint32_t size = detail::decode_varint(pb_str, pos); + pb_str = pb_str.substr(pos); + if (pb_str.size() < size) + IGUANA_UNLIKELY { + throw std::invalid_argument( + "Invalid fixed int value: too few bytes."); + } + using item_type = typename T::value_type; + size_t start = pb_str.size(); + + while (!pb_str.empty()) { + item_type item; + from_pb_impl(item, pb_str); + val.push_back(std::move(item)); + if (start - pb_str.size() == size) { + break; + } + } + } + } + else if constexpr (is_map_container::value) { + using item_type = std::pair; + while (!pb_str.empty()) { + size_t pos; + uint32_t size = detail::decode_varint(pb_str, pos); + pb_str = pb_str.substr(pos); + if (pb_str.size() < size) + IGUANA_UNLIKELY { + throw std::invalid_argument( + "Invalid fixed int value: too few bytes."); + } + item_type item = {}; + decode_pair_value(item.first, pb_str); + decode_pair_value(item.second, pb_str); + val.emplace(std::move(item)); + + if (pb_str.empty()) { + break; + } + uint32_t key = detail::decode_varint(pb_str, pos); + uint32_t field_number = key >> 3; + if (field_number != field_no) { + break; + } + pb_str = pb_str.substr(pos); + } + } + else if constexpr (std::is_integral_v) { + val = static_cast(detail::decode_varint(pb_str, pos)); + pb_str = pb_str.substr(pos); + } + else if constexpr (detail::is_signed_varint_v) { + constexpr size_t len = sizeof(typename T::value_type); + uint64_t temp = detail::decode_varint(pb_str, pos); + if constexpr (len == 8) { + val.val = detail::decode_zigzag(temp); + } + else { + val.val = detail::decode_zigzag(static_cast(temp)); + } + pb_str = pb_str.substr(pos); + } + else if constexpr (detail::is_fixed_v) { + constexpr size_t size = sizeof(typename T::value_type); + if (pb_str.size() < size) + IGUANA_UNLIKELY { + throw std::invalid_argument("Invalid fixed int value: too few bytes."); + } + memcpy(&(val.val), pb_str.data(), size); + pb_str = pb_str.substr(size); + } + else if constexpr (std::is_same_v || std::is_same_v) { + constexpr size_t size = sizeof(T); + if (pb_str.size() < size) + IGUANA_UNLIKELY { + throw std::invalid_argument("Invalid fixed int value: too few bytes."); + } + memcpy(&(val), pb_str.data(), size); + pb_str = pb_str.substr(size); + } + else if constexpr (std::is_same_v || + std::is_same_v) { + size_t size = detail::decode_varint(pb_str, pos); + if (pb_str.size() < pos + size) + IGUANA_UNLIKELY { + throw std::invalid_argument("Invalid string value: too few bytes."); + } + if constexpr (std::is_same_v) { + val = std::string_view(pb_str.data() + pos, size); + } + else { + val.resize(size); + memcpy(val.data(), pb_str.data() + pos, size); + } + pb_str = pb_str.substr(size + pos); + } + else if constexpr (std::is_enum_v) { + using U = std::underlying_type_t; + U value{}; + from_pb_impl(value, pb_str); + val = static_cast(value); + } + else if constexpr (optional_v) { + from_pb_impl(val.emplace(), pb_str); + } + else { + static_assert(!sizeof(T), "err"); + } +} + +template +IGUANA_INLINE void parse_oneof(T& t, const Field& f, std::string_view& pb_str) { + using item_type = typename std::decay_t::sub_type; + from_pb_impl(t.template emplace(), pb_str, f.field_no); +} +} // namespace detail + +template +IGUANA_INLINE void from_pb(T& t, std::string_view pb_str) { + size_t pos = 0; + while (!pb_str.empty()) { + uint32_t key = detail::decode_varint(pb_str, pos); + WireType wire_type = static_cast(key & 0b0111); + uint32_t field_number = key >> 3; + + pb_str = pb_str.substr(pos); + constexpr static auto map = get_members(); + auto& member = map.at(field_number); + std::visit( + [&t, &pb_str, wire_type](auto& val) IGUANA__INLINE_LAMBDA { + using sub_type = typename std::decay_t::sub_type; + using value_type = typename std::decay_t::value_type; + // sub_type is the element type when value_type is the variant type; + // otherwise, they are the same. + if (wire_type != detail::get_wire_type()) + IGUANA_UNLIKELY { throw std::runtime_error("unmatched wire_type"); } + if constexpr (variant_v) { + detail::parse_oneof(val.value(t), val, pb_str); + } + else { + detail::from_pb_impl(val.value(t), pb_str, val.field_no); + } + }, + member); + } +} +} // namespace iguana diff --git a/iguana/pb_util.hpp b/iguana/pb_util.hpp index 6fca21d6..5292860b 100644 --- a/iguana/pb_util.hpp +++ b/iguana/pb_util.hpp @@ -1,34 +1,106 @@ #pragma once #include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include +#include "detail/pb_type.hpp" +#include "reflection.hpp" +#include "util.hpp" + namespace iguana { + +enum class WireType : uint32_t { + Varint = 0, + Fixed64 = 1, + LengthDelimeted = 2, + StartGroup = 3, + EndGroup = 4, + Fixed32 = 5, + Unknown +}; + +struct pb_base { + size_t cache_size; +}; + +template +constexpr bool inherits_from_pb_base_v = std::is_base_of_v; + namespace detail { -[[nodiscard]] inline uint32_t encode_zigzag(int32_t v) { +template +constexpr bool is_fixed_v = + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v; + +template +constexpr bool is_signed_varint_v = + std::is_same_v || std::is_same_v; + +template +constexpr inline WireType get_wire_type() { + if constexpr (std::is_integral_v || is_signed_varint_v || + std::is_enum_v || std::is_same_v) { + return WireType::Varint; + } + else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return WireType::Fixed32; + } + else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return WireType::Fixed64; + } + else if constexpr (std::is_same_v || + std::is_same_v || + is_reflection_v || is_sequence_container::value || + is_map_container::value) { + return WireType::LengthDelimeted; + } + else if constexpr (optional_v) { + return get_wire_type(); + } + else { + throw std::runtime_error("unknown type"); + } +} + +template +constexpr bool is_lenprefix_v = (get_wire_type() == + WireType::LengthDelimeted); + +[[nodiscard]] IGUANA_INLINE uint32_t encode_zigzag(int32_t v) { return (static_cast(v) << 1U) ^ static_cast( -static_cast(static_cast(v) >> 31U)); } -[[nodiscard]] inline uint64_t encode_zigzag(int64_t v) { +[[nodiscard]] IGUANA_INLINE uint64_t encode_zigzag(int64_t v) { return (static_cast(v) << 1U) ^ static_cast( -static_cast(static_cast(v) >> 63U)); } -[[nodiscard]] inline int64_t decode_zigzag(uint64_t u) { +[[nodiscard]] IGUANA_INLINE int64_t decode_zigzag(uint64_t u) { return static_cast((u >> 1U)) ^ static_cast(-static_cast(u & 1U)); } -[[nodiscard]] inline int64_t decode_zigzag(uint32_t u) { +[[nodiscard]] IGUANA_INLINE int64_t decode_zigzag(uint32_t u) { return static_cast((u >> 1U)) ^ static_cast(-static_cast(u & 1U)); } template -inline uint64_t decode_varint(T& data, size_t& pos) { +IGUANA_INLINE uint64_t decode_varint(T& data, size_t& pos) { const int8_t* begin = reinterpret_cast(data.data()); const int8_t* end = begin + data.size(); const int8_t* p = begin; @@ -97,9 +169,10 @@ inline uint64_t decode_varint(T& data, size_t& pos) { val |= static_cast(*p++ & 0x7f) << shift; shift += 7; } - if (p == end) { - throw std::invalid_argument("Invalid varint value: too few bytes."); - } + if (p == end) + IGUANA_UNLIKELY { + throw std::invalid_argument("Invalid varint value: too few bytes."); + } val |= static_cast(*p++) << shift; } @@ -107,14 +180,348 @@ inline uint64_t decode_varint(T& data, size_t& pos) { return val; } +// value == 0 ? 1 : floor(log2(value)) / 7 + 1 +constexpr size_t variant_uint32_size_constexpr(uint32_t value) { + if (value == 0) { + return 1; + } + int log = 0; + while (value >>= 1) ++log; + return log / 7 + 1; +} + +template +IGUANA_INLINE void append_varint_u32_constexpr(Stream& out, + std::index_sequence) { + return (out.push_back(static_cast((v >> (7 * I)) | 0x80)), ...); +} + +template +IGUANA_INLINE void serialize_varint_u32_constexpr(Stream& out) { + constexpr auto size = variant_uint32_size_constexpr(v); + append_varint_u32_constexpr(out, std::make_index_sequence{}); + out.push_back(static_cast(v >> (7 * (size - 1)))); +} + template -inline void serialize_varint(uint64_t v, Stream& out) { - while (v >= 0x80) { +IGUANA_INLINE void serialize_varint(uint64_t v, Stream& out) { + if (v < 0x80) { + out.push_back(static_cast(v)); + return; + } + out.push_back(static_cast(v | 0x80)); + v >>= 7; + if (v < 0x80) { + out.push_back(static_cast(v)); + return; + } + do { out.push_back(static_cast(v | 0x80)); v >>= 7; - } + } while (v >= 0x80); out.push_back(static_cast(v)); } +IGUANA_INLINE uint32_t log2_floor_uint32(uint32_t n) { +#if defined(__GNUC__) + return 31 ^ static_cast(__builtin_clz(n)); +#else + unsigned long where; + _BitScanReverse(&where, n); + return where; +#endif +} + +IGUANA_INLINE size_t variant_uint32_size(uint32_t value) { + // This computes value == 0 ? 1 : floor(log2(value)) / 7 + 1 + // Use an explicit multiplication to implement the divide of + // a number in the 1..31 range. + // Explicit OR 0x1 to avoid calling Bits::Log2FloorNonZero(0), which is + // undefined. + uint32_t log2value = log2_floor_uint32(value | 0x1); + return static_cast((log2value * 9 + 73) / 64); +} + +IGUANA_INLINE uint32_t log2_floor_uint64(uint64_t n) { +#if defined(__GNUC__) + return 63 ^ static_cast(__builtin_clzll(n)); +#else + unsigned long where; + _BitScanReverse64(&where, n); + return where; +#endif +} + +IGUANA_INLINE size_t variant_uint64_size(uint64_t value) { + // This computes value == 0 ? 1 : floor(log2(value)) / 7 + 1 + // Use an explicit multiplication to implement the divide of + // a number in the 1..63 range. + // Explicit OR 0x1 to avoid calling Bits::Log2FloorNonZero(0), which is + // undefined. + uint32_t log2value = log2_floor_uint64(value | 0x1); + return static_cast((log2value * 9 + 73) / 64); +} + +template +constexpr IGUANA_INLINE size_t variant_intergal_size(U value) { + using T = std::remove_reference_t; + if constexpr (sizeof(T) == 8) { + return variant_uint64_size(static_cast(value)); + } + else if constexpr (sizeof(T) == 4) { + if constexpr (std::is_same_v) { + if (value < 0) { + return 10; + } + } + return variant_uint32_size(static_cast(value)); + } + else { + static_assert(!sizeof(T), "intergal in not supported"); + } +} + +template +constexpr void for_each_n(F&& f, std::index_sequence) { + (std::forward(f)(std::integral_constant{}), ...); +} + +// cache the size of reflection type +template +auto& get_set_size_cache(T& t) { + static std::map cache; + return cache[reinterpret_cast(&t)]; +} + +template +IGUANA_INLINE size_t numeric_size(T&& t) { + using value_type = std::remove_const_t>; + if constexpr (omit_default_val) { + if constexpr (is_fixed_v || is_signed_varint_v) { + if (t.val == 0) + IGUANA_UNLIKELY { return 0; } + } + else { + if (t == static_cast(0)) + IGUANA_UNLIKELY { return 0; } + } + } + if constexpr (std::is_integral_v) { + if constexpr (std::is_same_v) { + return 1 + key_size; + } + else { + return key_size + variant_intergal_size(t); + } + } + else if constexpr (detail::is_signed_varint_v) { + return key_size + variant_intergal_size(encode_zigzag(t.val)); + } + else if constexpr (detail::is_fixed_v) { + return key_size + sizeof(typename value_type::value_type); + } + else if constexpr (std::is_same_v || + std::is_same_v) { + return key_size + sizeof(value_type); + } + else if constexpr (std::is_enum_v) { + using U = std::underlying_type_t; + return key_size + variant_intergal_size(static_cast(t)); + } + else { + static_assert(!sizeof(value_type), "err"); + } +} + +template +IGUANA_INLINE size_t pb_key_value_size(T&& t); + +template +constexpr inline size_t get_variant_index() { + if constexpr (I == 0) { + static_assert(std::is_same_v, T>, + "Type T is not found in Variant"); + return 0; + } + else if constexpr (std::is_same_v, + T>) { + return I; + } + else { + return get_variant_index(); + } +} + +template +IGUANA_INLINE size_t pb_oneof_size(Type&& t) { + using T = std::decay_t; + int len = 0; + std::visit( + [&len](auto&& value) IGUANA__INLINE_LAMBDA { + using value_type = + std::remove_const_t>; + constexpr auto offset = + get_variant_index - 1>(); + constexpr uint32_t key = + ((field_no + offset) << 3) | + static_cast(get_wire_type()); + len = pb_key_value_size( + std::forward(value)); + }, + std::forward(t)); + return len; +} + +// returns size = key_size + optional(len_size) + len +// when key_size == 0, return len +template +IGUANA_INLINE size_t pb_key_value_size(T&& t) { + using value_type = std::remove_const_t>; + if constexpr (is_reflection_v || + is_custom_reflection_v) { + size_t len = 0; + constexpr auto tuple = get_members_tuple(); + constexpr size_t SIZE = std::tuple_size_v>; + for_each_n( + [&len, &t](auto i) IGUANA__INLINE_LAMBDA { + constexpr auto tuple = get_members_tuple(); + using field_type = + std::tuple_element_t>; + constexpr auto value = std::get(tuple); + using U = typename field_type::value_type; + auto& val = value.value(t); + if constexpr (variant_v) { + constexpr auto offset = + get_variant_index - 1>(); + if constexpr (offset == 0) { + len += pb_oneof_size(val); + } + } + else { + constexpr uint32_t sub_key = + (value.field_no << 3) | + static_cast(get_wire_type()); + constexpr auto sub_keysize = variant_uint32_size_constexpr(sub_key); + len += pb_key_value_size(val); + } + }, + std::make_index_sequence{}); + if constexpr (inherits_from_pb_base_v) { + t.cache_size = len; + } + else { + get_set_size_cache(t) = len; + } + + if constexpr (key_size == 0) { + // for top level + return len; + } + else { + if (len == 0) { + return 0; + } + else { + return key_size + variant_uint32_size(static_cast(len)) + len; + } + } + } + else if constexpr (is_sequence_container::value) { + using item_type = typename value_type::value_type; + size_t len = 0; + if constexpr (is_lenprefix_v) { + for (auto& item : t) { + len += pb_key_value_size(item); + } + return len; + } + else { + for (auto& item : t) { + // here 0 to get pakced size + len += numeric_size(item); + } + if (len == 0) { + return 0; + } + else { + return key_size + variant_uint32_size(static_cast(len)) + len; + } + } + } + else if constexpr (is_map_container::value) { + size_t len = 0; + for (auto& [k, v] : t) { + // the key_size of k and v is constant 1 + size_t kv_len = pb_key_value_size<1>(k) + pb_key_value_size<1>(v); + len += key_size + variant_uint32_size(static_cast(kv_len)) + + kv_len; + } + return len; + } + else if constexpr (optional_v) { + if (!t.has_value()) { + return 0; + } + return pb_key_value_size(*t); + } + else if constexpr (std::is_same_v || + std::is_same_v) { + if (t.size() == 0) { + return 0; + } + if constexpr (key_size == 0) { + return t.size(); + } + else { + return key_size + variant_uint32_size(static_cast(t.size())) + + t.size(); + } + } + else { + return numeric_size(t); + } +} + +// return the payload size +template +IGUANA_INLINE size_t pb_value_size(T&& t) { + using value_type = std::remove_const_t>; + if constexpr (is_reflection_v) { + if constexpr (inherits_from_pb_base_v) { + return t.cache_size; + } + else { + return get_set_size_cache(t); + } + } + else if constexpr (is_sequence_container::value) { + using item_type = typename value_type::value_type; + size_t len = 0; + if constexpr (!is_lenprefix_v) { + for (auto& item : t) { + len += numeric_size(item); + } + return len; + } + else { + static_assert(!sizeof(item_type), "the size of this type is meaningless"); + } + } + else if constexpr (is_map_container::value) { + static_assert(!sizeof(value_type), "the size of this type is meaningless"); + } + else if constexpr (optional_v) { + if (!t.has_value()) { + return 0; + } + return pb_value_size(*t); + } + else { + return pb_key_value_size<0>(t); + } +} + } // namespace detail } // namespace iguana \ No newline at end of file diff --git a/iguana/pb_writer.hpp b/iguana/pb_writer.hpp new file mode 100644 index 00000000..7d9c0506 --- /dev/null +++ b/iguana/pb_writer.hpp @@ -0,0 +1,214 @@ +#pragma once +#include "pb_util.hpp" + +namespace iguana { +namespace detail { + +template +IGUANA_INLINE void encode_varint_field(V val, Stream& out) { + static_assert(std::is_integral_v, "must be integral"); + if constexpr (key != 0) { + serialize_varint_u32_constexpr(out); + } + serialize_varint(val, out); +} + +template +IGUANA_INLINE void encode_fixed_field(V val, Stream& out) { + if constexpr (key != 0) { + serialize_varint_u32_constexpr(out); + } + constexpr size_t size = sizeof(V); + // TODO: check Stream continuous + auto end = out.size(); + out.resize(out.size() + size); + memcpy(&out[end], &val, size); +} + +template +IGUANA_INLINE void to_pb_impl(Type&& t, Stream& out); + +template +IGUANA_INLINE void encode_pair_value(V&& val, Stream& out, size_t size) { + if (size == 0) + IGUANA_UNLIKELY { + // map keys can't be omitted even if values are empty + serialize_varint_u32_constexpr(out); + serialize_varint(0, out); + } + else { + to_pb_impl(val, out); + } +} + +template +IGUANA_INLINE void encode_numeric_field(T t, Stream& out) { + if constexpr (omit_default_val) { + if constexpr (is_fixed_v || is_signed_varint_v) { + if (t.val == 0) { + return; + } + } + else { + if (t == static_cast(0)) + IGUANA_UNLIKELY { return; } + } + } + if constexpr (std::is_integral_v) { + detail::encode_varint_field(t, out); + } + else if constexpr (detail::is_signed_varint_v) { + detail::encode_varint_field(encode_zigzag(t.val), out); + } + else if constexpr (detail::is_fixed_v) { + detail::encode_fixed_field(t.val, out); + } + else if constexpr (std::is_same_v || std::is_same_v) { + detail::encode_fixed_field(t, out); + } + else if constexpr (std::is_enum_v) { + using U = std::underlying_type_t; + detail::encode_varint_field(static_cast(t), out); + } + else { + static_assert(!sizeof(T), "unsupported type"); + } +} + +template +IGUANA_INLINE void to_pb_oneof(Type&& t, Stream& out) { + using T = std::decay_t; + std::visit( + [&out](auto&& value) IGUANA__INLINE_LAMBDA { + using value_type = + std::remove_const_t>; + constexpr auto offset = + get_variant_index - 1>(); + constexpr uint32_t key = + ((field_no + offset) << 3) | + static_cast(get_wire_type()); + to_pb_impl(std::forward(value), out); + }, + std::forward(t)); +} + +// omit_default_val = true indicates to omit the default value in searlization +template +IGUANA_INLINE void to_pb_impl(Type&& t, Stream& out) { + using T = std::remove_const_t>; + if constexpr (is_reflection_v || is_custom_reflection_v) { + // TODO: improve the key serialize + auto len = pb_value_size(t); + // can't be omitted even if values are empty + if constexpr (key != 0) { + serialize_varint_u32_constexpr(out); + serialize_varint(len, out); + if (len == 0) + IGUANA_UNLIKELY { return; } + } + constexpr auto tuple = get_members_tuple(); + constexpr size_t SIZE = std::tuple_size_v>; + for_each_n( + [&t, &out](auto i) IGUANA__INLINE_LAMBDA { + constexpr auto tuple = get_members_tuple(); + using field_type = + std::tuple_element_t>; + constexpr auto value = std::get(tuple); + auto& val = value.value(t); + + using U = typename field_type::value_type; + if constexpr (variant_v) { + constexpr auto offset = + get_variant_index - 1>(); + if constexpr (offset == 0) { + to_pb_oneof(val, out); + } + } + else { + constexpr uint32_t sub_key = + (value.field_no << 3) | + static_cast(get_wire_type()); + to_pb_impl(val, out); + } + }, + std::make_index_sequence{}); + } + else if constexpr (is_sequence_container::value) { + // TODO support std::array + // repeated values can't be omitted even if values are empty + using item_type = typename T::value_type; + if constexpr (is_lenprefix_v) { + // non-packed + for (auto& item : t) { + to_pb_impl(item, out); + } + } + else { + if (t.empty()) + IGUANA_UNLIKELY { return; } + serialize_varint_u32_constexpr(out); + serialize_varint(pb_value_size(t), out); + for (auto& item : t) { + encode_numeric_field(item, out); + } + } + } + else if constexpr (is_map_container::value) { + using first_type = typename T::key_type; + using second_type = typename T::mapped_type; + constexpr uint32_t key1 = + (1 << 3) | static_cast(get_wire_type()); + constexpr auto key1_size = variant_uint32_size_constexpr(key1); + constexpr uint32_t key2 = + (2 << 3) | static_cast(get_wire_type()); + constexpr auto key2_size = variant_uint32_size_constexpr(key2); + + for (auto& [k, v] : t) { + serialize_varint_u32_constexpr(out); + auto k_len = pb_value_size(k); + auto v_len = pb_value_size(v); + auto pair_len = key1_size + key2_size + k_len + v_len; + if constexpr (is_lenprefix_v) { + pair_len += variant_uint32_size(k_len); + } + if constexpr (is_lenprefix_v) { + pair_len += variant_uint32_size(v_len); + } + serialize_varint(pair_len, out); + // map k and v can't be omitted even if values are empty + encode_pair_value(k, out, k_len); + encode_pair_value(v, out, v_len); + } + } + else if constexpr (optional_v) { + if (!t.has_value()) { + return; + } + to_pb_impl(*t, out); + } + else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (omit_default_val) { + if (t.size() == 0) + IGUANA_UNLIKELY { return; } + } + serialize_varint_u32_constexpr(out); + serialize_varint(t.size(), out); + out.append(t); + } + else { + encode_numeric_field(t, out); + } +} +} // namespace detail + +template +IGUANA_INLINE void to_pb(T& t, Stream& out) { + auto byte_len = detail::pb_key_value_size<0>(t); + out.reserve(out.size() + byte_len); + detail::to_pb_impl<0>(t, out); +} +} // namespace iguana diff --git a/iguana/reflection.hpp b/iguana/reflection.hpp index c29e0904..918ff16c 100644 --- a/iguana/reflection.hpp +++ b/iguana/reflection.hpp @@ -692,26 +692,19 @@ inline constexpr auto get_iguana_struct_map() { } } -template -struct member_tratis {}; - -template -struct member_tratis { - using owner_type = Owner; - using value_type = T; -}; - -template +template ::value_type> struct field_t { using member_type = T; - using owner_type = typename member_tratis::owner_type; - using value_type = typename member_tratis::value_type; - field_t() = default; - field_t(T member, uint32_t number, const std::string &name = "") + using owner_type = typename member_traits::owner_type; + using value_type = typename member_traits::value_type; + using sub_type = ElementType; + constexpr field_t() = default; + constexpr field_t(T member, uint32_t number, frozen::string name) : member_ptr(member), field_name(name), field_no(number) {} T member_ptr; - std::string field_name; + frozen::string field_name; uint32_t field_no; auto &value(owner_type &value) const { return value.*member_ptr; } @@ -722,69 +715,117 @@ struct field_type_t; template struct field_type_t> { - using value_type = std::variant...>; + using value_type = std::variant; }; +template +struct is_custom_reflection : std::false_type {}; + template -struct user_field_type_t; +struct is_custom_reflection< + T, std::void_t()))>> + : std::true_type {}; -template -struct user_field_type_t> { - using value_type = std::variant; -}; +template +struct is_reflection : std::false_type {}; -struct field_helper { - template - auto operator()(T &tp, U arr, std::index_sequence) { - return std::make_tuple( - field_t{std::get(tp), I + 1, std::string(arr[I].data())}...); - } -}; +template +inline constexpr bool is_reflection_v = is_reflection::value; template -inline auto get_members_impl(T &&) { - using reflect_members = decltype(iguana_reflect_type(std::declval())); - using Tuple = decltype(reflect_members::apply_impl()); - const auto &tp = reflect_members::apply_impl(); - const auto &arr = reflect_members::arr(); - return field_helper{}(tp, arr, - std::make_index_sequence>{}); +inline constexpr bool is_custom_reflection_v = is_custom_reflection::value; + +template +constexpr inline auto build_variant_fields(T t, S &s, uint32_t base_idx, + std::index_sequence) { + using value_type = typename member_traits::value_type; + return std::tuple(field_t>{ + t, (base_idx + uint32_t(I)), s}...); } -template -struct member_helper { - template - auto operator()(Tuple &&tp, uint32_t sub_val, std::index_sequence) { - std::map map; - ((map[std::get(tp).field_no - sub_val] = - T{std::in_place_index, std::move(std::get(tp))}), - ...); - return map; +template +constexpr inline auto build_fields(T t, S &s, uint32_t &index) { + using value_type = typename member_traits::value_type; + if constexpr (is_variant::value) { + constexpr uint32_t Size = std::variant_size_v; + index += (Size - 1); + return build_variant_fields(t, s, I + 1, std::make_index_sequence{}); } -}; + else { + uint32_t field_no = (I == index) ? (I + 1) : (I + index); + index++; + return std::tuple(field_t{t, field_no, s}); + } +} -template -struct is_reflection : std::false_type {}; +template +constexpr inline auto get_members_tuple_impl(T &&tp, U &&arr, + std::index_sequence &&) { + uint32_t index = 0; + return std::tuple_cat(build_fields(std::get(tp), arr[I], index)...); +} template -inline decltype(auto) get_members(T &&t) { +constexpr inline auto get_members_tuple() { if constexpr (is_reflection::value) { using reflect_members = decltype(iguana_reflect_type(std::declval())); - using value_type = typename field_type_t< - decltype(reflect_members::apply_impl())>::value_type; - constexpr size_t Size = reflect_members::value(); - static auto map = member_helper{}( - get_members_impl(t), 1, std::make_index_sequence{}); - return map; + using Tuple = decltype(reflect_members::apply_impl()); + constexpr size_t Size = std::tuple_size_v; + return get_members_tuple_impl(reflect_members::apply_impl(), + reflect_members::arr(), + std::make_index_sequence{}); } - else { + else if constexpr (is_custom_reflection_v) { using U = std::remove_const_t>; - constexpr size_t Size = iguana_member_count((U *)nullptr); - using value_type = typename user_field_type_t::value_type; - static auto map = member_helper{}( - get_members_impl((U *)nullptr), 0, std::make_index_sequence{}); - return map; + return get_members_impl((U *)nullptr); + } + else { + static_assert(!sizeof(T), "expected reflection or custom reflection"); + } +} + +template +constexpr auto inline get_members_impl(Tuple &&tp, std::index_sequence) { + return frozen::unordered_map{ + {std::get(tp).field_no, + T{std::in_place_index, std::move(std::get(tp))}}...}; +} + +template +constexpr size_t count_variant_size() { + if constexpr (is_variant::value) { + return std::variant_size_v; + } + else { + return 1; + } +} + +template +constexpr size_t tuple_type_count_impl(std::index_sequence) { + return ( + (count_variant_size>>() + + ...)); +} + +template +constexpr size_t tuple_type_count() { + return tuple_type_count_impl( + std::make_index_sequence>{}); +} + +template +constexpr inline auto get_members() { + if constexpr (is_reflection_v || is_custom_reflection_v) { + constexpr auto tp = get_members_tuple(); + using Tuple = std::decay_t; + using value_type = typename field_type_t::value_type; + constexpr auto Size = tuple_type_count(); + return get_members_impl(tp, + std::make_index_sequence{}); + } + else { + static_assert(!sizeof(T), "expected reflection or custom reflection"); } } @@ -914,9 +955,6 @@ inline auto iguana_reflect_type(const T &t) { } } -template -inline constexpr bool is_reflection_v = is_reflection::value; - template typename Condition, typename Tuple, typename Owner> constexpr int element_index_helper() { diff --git a/iguana/struct_pb.hpp b/iguana/struct_pb.hpp deleted file mode 100644 index 87c873b8..00000000 --- a/iguana/struct_pb.hpp +++ /dev/null @@ -1,524 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "pb_util.hpp" -#include "reflection.hpp" -#include "util.hpp" - -namespace iguana { -enum class WireType : uint32_t { - Varint = 0, - Fixed64 = 1, - LengthDelimeted = 2, - StartGroup = 3, - EndGroup = 4, - Fixed32 = 5, - Unknown -}; -struct sint32_t { - using value_type = int32_t; - int32_t val; -}; - -struct sint64_t { - using value_type = int64_t; - int64_t val; -}; - -inline bool operator==(sint32_t value1, int32_t value2) { - return value1.val == value2; -} - -inline bool operator==(sint64_t value1, int64_t value2) { - return value1.val == value2; -} - -struct fixed32_t { - using value_type = uint32_t; - uint32_t val; -}; - -struct fixed64_t { - using value_type = uint64_t; - uint64_t val; -}; - -inline bool operator==(fixed32_t value1, uint32_t value2) { - return value1.val == value2; -} - -inline bool operator==(fixed64_t value1, uint64_t value2) { - return value1.val == value2; -} - -struct sfixed32_t { - using value_type = int32_t; - int32_t val; -}; - -struct sfixed64_t { - using value_type = int64_t; - int64_t val; -}; - -inline bool operator==(sfixed32_t value1, int32_t value2) { - return value1.val == value2; -} - -inline bool operator==(sfixed32_t value1, int64_t value2) { - return value1.val == value2; -} - -template -struct one_of_t { - static constexpr bool is_one_of_v = true; - using value_type = std::optional< - std::remove_reference_t(std::declval()))>>; - value_type value; -}; - -template -inline void to_pb(T& t, std::string& out); - -template -inline void from_pb(T& t, std::string_view pb_str); - -namespace detail { -template -constexpr bool is_fixed_v = - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v; - -template -constexpr bool is_signed_varint_v = - std::is_same_v || std::is_same_v; - -template -struct is_one_of_t : std::false_type {}; - -template -struct is_one_of_t> : std::true_type { -}; - -template -constexpr bool is_one_of_v = is_one_of_t::value; - -template -constexpr inline WireType get_wire_type() { - if constexpr (std::is_integral_v || is_signed_varint_v || - std::is_enum_v) { - return WireType::Varint; - } - else if constexpr (std::is_same_v || - std::is_same_v) { - return WireType::Fixed32; - } - else if constexpr (std::is_same_v || - std::is_same_v) { - return WireType::Fixed64; - } - else if constexpr (std::is_same_v || - std::is_same_v || - is_reflection_v || is_sequence_container::value || - is_map_container::value) { - return WireType::LengthDelimeted; - } - else if constexpr (optional_v || is_one_of_v) { - return get_wire_type(); - } - else { - return WireType::Unknown; - } -} - -template -inline void encode_key(uint32_t field_number, WireType type, T& out) { - if (field_number == 0) { - return; - } - - uint32_t key = (field_number << 3) | static_cast(type); - serialize_varint(key, out); -} - -template -inline void encode_varint_field(uint32_t field_number, WireType type, - Value value, T& out) { - static_assert(std::is_integral_v, "must be integral"); - if (value == 0) { - return; - } - - encode_key(field_number, type, out); - serialize_varint(value, out); -} - -template -inline void encode_signed_varint_field(uint32_t field_number, WireType type, - Value value, T& out) { - if (value == 0) { - return; - } - - encode_key(field_number, type, out); - serialize_varint(encode_zigzag(value.val), out); -} - -template -inline void encode_fixed_field(uint32_t field_number, WireType type, - Value value, T& out) { - if (value == 0) { - return; - } - - encode_key(field_number, type, out); - - constexpr size_t size = sizeof(Value); - char buf[size]{}; - memcpy(buf, &value, size); - out.append(buf, size); -} - -template -inline void encode_string_field(uint32_t field_number, WireType type, - std::string_view str, T& out) { - if (str.empty()) { - return; - } - - encode_key(field_number, type, out); - - serialize_varint(str.size(), out); - out.append(str); -} - -template -inline void from_pb_impl(T& val, std::string_view& pb_str, - uint32_t field_no = 0); - -template -inline void decode_pair_value(T& val, std::string_view& pb_str) { - size_t pos; - uint32_t key = detail::decode_varint(pb_str, pos); - pb_str = pb_str.substr(pos); - WireType wire_type = static_cast(key & 0b0111); - if (wire_type != detail::get_wire_type>()) { - return; - } - - from_pb_impl(val, pb_str); -} - -template -inline void from_pb_impl(T& val, std::string_view& pb_str, uint32_t field_no) { - size_t pos = 0; - if constexpr (is_reflection_v) { - size_t pos; - uint32_t size = detail::decode_varint(pb_str, pos); - if (pb_str.size() < size) { - throw std::invalid_argument("Invalid fixed int value: too few bytes."); - } - pb_str = pb_str.substr(pos); - if (size == 0) { - return; - } - - from_pb(val, pb_str); - pb_str = pb_str.substr(size); - } - else if constexpr (is_sequence_container::value) { - using item_type = typename value_type::value_type; - if constexpr (is_reflection_v) { - // message no size - while (!pb_str.empty()) { - item_type item; - from_pb_impl(item, pb_str); - val.push_back(std::move(item)); - if (pb_str.empty()) { - break; - } - uint32_t key = detail::decode_varint(pb_str, pos); - uint32_t field_number = key >> 3; - if (field_number != field_no) { - break; - } - else { - pb_str = pb_str.substr(pos); - } - } - } - else { - // non-message has size - size_t pos; - uint32_t size = detail::decode_varint(pb_str, pos); - if (pb_str.size() < size) { - throw std::invalid_argument("Invalid fixed int value: too few bytes."); - } - pb_str = pb_str.substr(pos); - - using item_type = typename value_type::value_type; - size_t start = pb_str.size(); - - while (!pb_str.empty()) { - item_type item; - from_pb_impl(item, pb_str); - val.push_back(std::move(item)); - if (start - pb_str.size() == size) { - break; - } - } - } - } - else if constexpr (is_map_container::value) { - using item_type = std::pair; - while (!pb_str.empty()) { - size_t pos; - uint32_t size = detail::decode_varint(pb_str, pos); - if (pb_str.size() < size) { - throw std::invalid_argument("Invalid fixed int value: too few bytes."); - } - pb_str = pb_str.substr(pos); - - item_type item = {}; - decode_pair_value(item.first, pb_str); - decode_pair_value(item.second, pb_str); - val.emplace(std::move(item)); - - if (pb_str.empty()) { - break; - } - - uint32_t key = detail::decode_varint(pb_str, pos); - uint32_t field_number = key >> 3; - if (field_number != field_no) { - break; - } - - pb_str = pb_str.substr(pos); - } - } - else if constexpr (std::is_integral_v) { - val = detail::decode_varint(pb_str, pos); - pb_str = pb_str.substr(pos); - } - else if constexpr (detail::is_signed_varint_v) { - constexpr size_t len = sizeof(typename value_type::value_type); - - uint64_t temp = detail::decode_varint(pb_str, pos); - if constexpr (len == 8) { - val = detail::decode_zigzag(temp); - } - else { - val = detail::decode_zigzag((uint32_t)temp); - } - pb_str = pb_str.substr(pos); - } - else if constexpr (detail::is_fixed_v) { - constexpr size_t size = sizeof(typename value_type::value_type); - if (pb_str.size() < size) { - throw std::invalid_argument("Invalid fixed int value: too few bytes."); - } - memcpy(&(val), pb_str.data(), size); - pb_str = pb_str.substr(size); - } - else if constexpr (std::is_same_v || - std::is_same_v) { - size_t size = detail::decode_varint(pb_str, pos); - if (pb_str.size() < pos + size) { - throw std::invalid_argument("Invalid string value: too few bytes."); - } - - if constexpr (std::is_same_v) { - val = std::string_view(pb_str.data() + pos, size); - } - else { - val.resize(size); - memcpy(val.data(), pb_str.data() + pos, size); - } - pb_str = pb_str.substr(size + pos); - } - else if constexpr (std::is_enum_v) { - using U = std::underlying_type_t; - U value; - from_pb_impl(value, pb_str); - val = static_cast(value); - } - else if constexpr (optional_v) { - from_pb_impl(val.emplace(), pb_str); - } - else if constexpr (is_one_of_v) { - from_pb_impl(val.value, pb_str); - } - else { - static_assert(!sizeof(value_type), "err"); - } -} - -template -inline void to_pb_impl(T& val, size_t field_no, std::string& out); - -template -inline std::string encode_pair_value(T& val, uint32_t field_no) { - std::string temp; - to_pb_impl(val, field_no, temp); - if (temp.empty()) { - encode_key(field_no, get_wire_type(), temp); - serialize_varint(0, temp); - } - return temp; -} - -template -inline void to_pb_impl(T& val, size_t field_no, std::string& out) { - if constexpr (is_reflection_v) { - std::string temp; - to_pb(val, temp); - detail::encode_string_field(field_no, WireType::LengthDelimeted, temp, out); - } - else if constexpr (is_sequence_container::value) { - using item_type = typename value_type::value_type; - if constexpr (is_reflection_v) { - for (auto& item : val) { - to_pb_impl(item, field_no, out); - } - } - else { - std::string temp; - for (auto& item : val) { - to_pb_impl(item, 0, temp); - } - detail::encode_string_field(field_no, WireType::LengthDelimeted, temp, - out); - } - } - else if constexpr (is_map_container::value) { - using first_type = typename value_type::key_type; - using second_type = typename value_type::mapped_type; - for (auto& [k, v] : val) { - std::string temp = encode_pair_value(k, 1); - std::string second_temp = encode_pair_value(v, 2); - encode_key(field_no, WireType::LengthDelimeted, out); - - serialize_varint(temp.size() + second_temp.size(), out); - out.append(temp).append(second_temp); - } - } - else if constexpr (std::is_integral_v) { - detail::encode_varint_field(field_no, WireType::Varint, val, out); - } - else if constexpr (detail::is_signed_varint_v) { - detail::encode_signed_varint_field(field_no, WireType::Varint, val, out); - } - else if constexpr (detail::is_fixed_v) { - if constexpr (sizeof(value_type) == 8) { - detail::encode_fixed_field(field_no, WireType::Fixed64, (uint64_t)(val), - out); - } - else { - detail::encode_fixed_field(field_no, WireType::Fixed32, (uint32_t)(val), - out); - } - } - else if constexpr (std::is_same_v || - std::is_same_v) { - detail::encode_string_field(field_no, WireType::LengthDelimeted, val, out); - } - else if constexpr (std::is_enum_v) { - using U = std::underlying_type_t; - detail::encode_varint_field(field_no, WireType::Varint, static_cast(val), - out); - } - else if constexpr (optional_v) { - if (!val.has_value()) { - return; - } - - to_pb_impl(*val, field_no, out); - } - else if constexpr (is_one_of_v) { - to_pb_impl(val.value, field_no, out); - } - else { - static_assert(!sizeof(value_type), "err"); - } -} -} // namespace detail - -template -inline constexpr size_t get_member_count_impl() { - if constexpr (is_reflection::value) { - return iguana::get_value>(); - } - else { - return iguana_member_count((T*)nullptr); - } -} - -template -inline void from_pb(T& t, std::string_view pb_str) { - using U = std::remove_const_t>; - constexpr size_t Count = get_member_count_impl(); - - size_t pos = 0; - while (!pb_str.empty()) { - uint32_t key = detail::decode_varint(pb_str, pos); - WireType wire_type = static_cast(key & 0b0111); - uint32_t field_number = key >> 3; - - pb_str = pb_str.substr(pos); - - const static auto& map = get_members(t); - uint32_t sub_val = 1; - if constexpr (!is_reflection_v) { - sub_val = 0; - } - auto& member = map.at(field_number - sub_val); - std::visit( - [&t, &pb_str, wire_type](auto& val) { - using value_type = typename std::decay_t::value_type; - if (wire_type != detail::get_wire_type()) { - return; - } - - if constexpr (detail::is_signed_varint_v || - detail::is_fixed_v) { - detail::from_pb_impl(val.value(t).val, pb_str); - } - else { - detail::from_pb_impl(val.value(t), pb_str, - val.field_no); - } - }, - member); - - if (field_number == Count) { - break; - } - } -} - -template -inline void to_pb(T& t, std::string& out) { - const static auto& map = get_members(t); - for (auto& [field_no, member] : map) { - std::visit( - [&t, &out](auto& val) { - using value_type = typename std::decay_t::value_type; - if constexpr (detail::is_fixed_v) { - detail::to_pb_impl(val.value(t).val, val.field_no, out); - } - else { - detail::to_pb_impl(val.value(t), val.field_no, out); - } - }, - member); - } -} -} // namespace iguana \ No newline at end of file diff --git a/iguana/util.hpp b/iguana/util.hpp index f910db79..5bf573a6 100644 --- a/iguana/util.hpp +++ b/iguana/util.hpp @@ -130,12 +130,6 @@ constexpr inline bool shared_ptr_v = template constexpr inline bool smart_ptr_v = shared_ptr_v || unique_ptr_v; -template -struct is_variant : std::false_type {}; - -template -struct is_variant> : std::true_type {}; - template constexpr inline bool variant_v = is_variant>::value; diff --git a/test/proto/unittest_proto3.h b/test/proto/unittest_proto3.h new file mode 100644 index 00000000..8899bca6 --- /dev/null +++ b/test/proto/unittest_proto3.h @@ -0,0 +1,439 @@ +#pragma once +#include "iguana/pb_reader.hpp" +#include "iguana/pb_writer.hpp" +#include "unittest_proto3.pb.h" // protoc gen + +#ifdef CHECK +#define PB_CHECK CHECK +#define PUBLIC +#else +#include +#define PB_CHECK assert +#define PUBLIC : iguana::pb_base +#endif + +// define the struct as msg in proto +namespace stpb { +enum class Enum { + ZERO = 0, + FOO = 1, + BAR = 2, + BAZ = 123456, + NEG = -1, // Intentionally negative. +}; + +struct BaseTypeMsg PUBLIC { + int32_t optional_int32; + int64_t optional_int64; + uint32_t optional_uint32; + uint64_t optional_uint64; + float optional_float; + double optional_double; + bool optional_bool; + std::string optional_string; + Enum optional_enum; + bool operator==(const BaseTypeMsg& other) const { + return optional_int32 == other.optional_int32 && + optional_int64 == other.optional_int64 && + optional_uint32 == other.optional_uint32 && + optional_uint64 == other.optional_uint64 && + optional_float == other.optional_float && + optional_double == other.optional_double && + optional_bool == other.optional_bool && + optional_string == other.optional_string && + optional_enum == other.optional_enum; + } +}; +REFLECTION(BaseTypeMsg, optional_int32, optional_int64, optional_uint32, + optional_uint64, optional_float, optional_double, optional_bool, + optional_string, optional_enum); + +struct IguanaTypeMsg PUBLIC { + iguana::sint32_t optional_sint32; + iguana::sint64_t optional_sint64; + iguana::fixed32_t optional_fixed32; + iguana::fixed64_t optional_fixed64; + iguana::sfixed32_t optional_sfixed32; + iguana::sfixed64_t optional_sfixed64; + + bool operator==(const IguanaTypeMsg& other) const { + return optional_sint32 == other.optional_sint32 && + optional_sint64 == other.optional_sint64 && + optional_fixed32 == other.optional_fixed32 && + optional_fixed64 == other.optional_fixed64 && + optional_sfixed32 == other.optional_sfixed32 && + optional_sfixed64 == other.optional_sfixed64; + } +}; +REFLECTION(IguanaTypeMsg, optional_sint32, optional_sint64, optional_fixed32, + optional_fixed64, optional_sfixed32, optional_sfixed64); + +struct RepeatBaseTypeMsg PUBLIC { + std::vector repeated_uint32; + std::vector repeated_uint64; + std::vector repeated_int32; + std::vector repeated_int64; + std::vector repeated_float; + std::vector repeated_double; + std::vector repeated_string; + std::vector repeated_enum; +}; + +REFLECTION(RepeatBaseTypeMsg, repeated_uint32, repeated_uint64, repeated_int32, + repeated_int64, repeated_float, repeated_double, repeated_string, + repeated_enum); + +struct RepeatIguanaTypeMsg PUBLIC { + std::vector repeated_sint32; + std::vector repeated_sint64; + std::vector repeated_fixed32; + std::vector repeated_fixed64; + std::vector repeated_sfixed32; + std::vector repeated_sfixed64; +}; + +REFLECTION(RepeatIguanaTypeMsg, repeated_sint32, repeated_sint64, + repeated_fixed32, repeated_fixed64, repeated_sfixed32, + repeated_sfixed64); + +struct NestedMsg PUBLIC { + BaseTypeMsg base_msg; + std::vector repeat_base_msg; + IguanaTypeMsg iguana_type_msg; + std::vector repeat_iguna_msg; + std::vector repeat_repeat_base_msg; +}; +REFLECTION(NestedMsg, base_msg, repeat_base_msg, iguana_type_msg, + repeat_iguna_msg, repeat_repeat_base_msg); + +struct MapMsg PUBLIC { + std::unordered_map sfix64_str_map{}; + std::unordered_map str_iguana_type_msg_map{}; + std::map int_repeat_base_msg_map{}; +}; +REFLECTION(MapMsg, sfix64_str_map, str_iguana_type_msg_map, + int_repeat_base_msg_map); + +struct BaseOneofMsg PUBLIC { + int32_t optional_int32; + std::variant one_of; + double optional_double; +}; +REFLECTION(BaseOneofMsg, optional_int32, one_of, optional_double); + +struct NestOneofMsg PUBLIC { + std::variant nest_one_of_msg; +}; +REFLECTION(NestOneofMsg, nest_one_of_msg); +} // namespace stpb + +void SetBaseTypeMsg(const stpb::BaseTypeMsg& st, pb::BaseTypeMsg& msg) { + msg.set_optional_int32(st.optional_int32); + msg.set_optional_int64(st.optional_int64); + msg.set_optional_uint32(st.optional_uint32); + msg.set_optional_uint64(st.optional_uint64); + msg.set_optional_float(st.optional_float); + msg.set_optional_double(st.optional_double); + msg.set_optional_bool(st.optional_bool); + msg.set_optional_string(st.optional_string); + msg.set_optional_enum(static_cast(st.optional_enum)); +} + +void CheckBaseTypeMsg(const stpb::BaseTypeMsg& st, const pb::BaseTypeMsg& msg) { + PB_CHECK(st.optional_int32 == msg.optional_int32()); + PB_CHECK(st.optional_int64 == msg.optional_int64()); + PB_CHECK(st.optional_uint32 == msg.optional_uint32()); + PB_CHECK(st.optional_uint64 == msg.optional_uint64()); + PB_CHECK(st.optional_float == msg.optional_float()); + PB_CHECK(st.optional_double == msg.optional_double()); + PB_CHECK(st.optional_bool == msg.optional_bool()); + PB_CHECK(st.optional_string == msg.optional_string()); + PB_CHECK(static_cast(st.optional_enum) == + static_cast(msg.optional_enum())); +} + +void SetIguanaTypeMsg(const stpb::IguanaTypeMsg& st, pb::IguanaTypeMsg& msg) { + msg.set_optional_sint32(st.optional_sint32.val); + msg.set_optional_sint64(st.optional_sint64.val); + msg.set_optional_fixed32(st.optional_fixed32.val); + msg.set_optional_fixed64(st.optional_fixed64.val); + msg.set_optional_sfixed32(st.optional_sfixed32.val); + msg.set_optional_sfixed64(st.optional_sfixed64.val); +} + +void CheckIguanaTypeMsg(const stpb::IguanaTypeMsg& st, + const pb::IguanaTypeMsg& msg) { + PB_CHECK(st.optional_sint32.val == msg.optional_sint32()); + PB_CHECK(st.optional_sint64.val == msg.optional_sint64()); + PB_CHECK(st.optional_fixed32.val == msg.optional_fixed32()); + PB_CHECK(st.optional_fixed64.val == msg.optional_fixed64()); + PB_CHECK(st.optional_sfixed32.val == msg.optional_sfixed32()); + PB_CHECK(st.optional_sfixed64.val == msg.optional_sfixed64()); +} + +void SetRepeatBaseTypeMsg(const stpb::RepeatBaseTypeMsg& st, + pb::RepeatBaseTypeMsg& msg) { + for (auto v : st.repeated_uint32) { + msg.add_repeated_uint32(v); + } + for (auto v : st.repeated_uint64) { + msg.add_repeated_uint64(v); + } + for (auto v : st.repeated_int32) { + msg.add_repeated_int32(v); + } + for (auto v : st.repeated_int64) { + msg.add_repeated_int64(v); + } + for (auto v : st.repeated_float) { + msg.add_repeated_float(v); + } + for (auto v : st.repeated_double) { + msg.add_repeated_double(v); + } + for (auto v : st.repeated_string) { + msg.add_repeated_string(v); + } + for (auto v : st.repeated_enum) { + msg.add_repeated_enum(static_cast(v)); + } +} + +void CheckRepeatBaseTypeMsg(const stpb::RepeatBaseTypeMsg& st, + const pb::RepeatBaseTypeMsg& msg) { + for (size_t i = 0; i < st.repeated_uint32.size(); ++i) { + PB_CHECK(st.repeated_uint32[i] == msg.repeated_uint32(i)); + } + for (size_t i = 0; i < st.repeated_uint64.size(); ++i) { + PB_CHECK(st.repeated_uint64[i] == msg.repeated_uint64(i)); + } + for (size_t i = 0; i < st.repeated_int32.size(); ++i) { + PB_CHECK(st.repeated_int32[i] == msg.repeated_int32(i)); + } + for (size_t i = 0; i < st.repeated_int64.size(); ++i) { + PB_CHECK(st.repeated_int64[i] == msg.repeated_int64(i)); + } + for (size_t i = 0; i < st.repeated_float.size(); ++i) { + PB_CHECK(st.repeated_float[i] == msg.repeated_float(i)); + } + for (size_t i = 0; i < st.repeated_double.size(); ++i) { + PB_CHECK(st.repeated_double[i] == msg.repeated_double(i)); + } + for (size_t i = 0; i < st.repeated_string.size(); ++i) { + PB_CHECK(st.repeated_string[i] == msg.repeated_string(i)); + } + for (size_t i = 0; i < st.repeated_enum.size(); ++i) { + PB_CHECK(static_cast(st.repeated_enum[i]) == + static_cast(msg.repeated_enum(i))); + } +} + +void SetRepeatIguanaTypeMsg(const stpb::RepeatIguanaTypeMsg& st, + pb::RepeatIguanaTypeMsg& msg) { + for (auto v : st.repeated_sint32) { + msg.add_repeated_sint32(v.val); + } + for (auto v : st.repeated_sint64) { + msg.add_repeated_sint64(v.val); + } + for (auto v : st.repeated_fixed32) { + msg.add_repeated_fixed32(v.val); + } + for (auto v : st.repeated_fixed64) { + msg.add_repeated_fixed64(v.val); + } + for (auto v : st.repeated_sfixed32) { + msg.add_repeated_sfixed32(v.val); + } + for (auto v : st.repeated_sfixed64) { + msg.add_repeated_sfixed64(v.val); + } +} + +void CheckRepeatIguanaTypeMsg(const stpb::RepeatIguanaTypeMsg& st, + const pb::RepeatIguanaTypeMsg& msg) { + for (size_t i = 0; i < st.repeated_sint32.size(); ++i) { + PB_CHECK(st.repeated_sint32[i].val == msg.repeated_sint32(i)); + } + for (size_t i = 0; i < st.repeated_sint64.size(); ++i) { + PB_CHECK(st.repeated_sint64[i].val == msg.repeated_sint64(i)); + } + for (size_t i = 0; i < st.repeated_fixed32.size(); ++i) { + PB_CHECK(st.repeated_fixed32[i].val == msg.repeated_fixed32(i)); + } + for (size_t i = 0; i < st.repeated_fixed64.size(); ++i) { + PB_CHECK(st.repeated_fixed64[i].val == msg.repeated_fixed64(i)); + } + for (size_t i = 0; i < st.repeated_sfixed32.size(); ++i) { + PB_CHECK(st.repeated_sfixed32[i].val == msg.repeated_sfixed32(i)); + } + for (size_t i = 0; i < st.repeated_sfixed64.size(); ++i) { + PB_CHECK(st.repeated_sfixed64[i].val == msg.repeated_sfixed64(i)); + } +} + +void SetNestedMsg(const stpb::NestedMsg& st, pb::NestedMsg& msg) { + SetBaseTypeMsg(st.base_msg, *msg.mutable_base_msg()); + + for (const auto& base_msg : st.repeat_base_msg) { + auto* base_msg_ptr = msg.add_repeat_base_msg(); + SetBaseTypeMsg(base_msg, *base_msg_ptr); + } + + SetIguanaTypeMsg(st.iguana_type_msg, *msg.mutable_iguana_type_msg()); + + for (const auto& iguana_type_msg : st.repeat_iguna_msg) { + auto* iguana_type_msg_ptr = msg.add_repeat_iguna_msg(); + SetIguanaTypeMsg(iguana_type_msg, *iguana_type_msg_ptr); + } + + for (const auto& repeat_base_msg : st.repeat_repeat_base_msg) { + auto* repeat_base_msg_ptr = msg.add_repeat_repeat_base_msg(); + SetRepeatBaseTypeMsg(repeat_base_msg, *repeat_base_msg_ptr); + } +} + +void CheckNestedMsg(const stpb::NestedMsg& st, const pb::NestedMsg& msg) { + CheckBaseTypeMsg(st.base_msg, msg.base_msg()); + + PB_CHECK(st.repeat_base_msg.size() == msg.repeat_base_msg_size()); + for (size_t i = 0; i < st.repeat_base_msg.size(); ++i) { + CheckBaseTypeMsg(st.repeat_base_msg[i], msg.repeat_base_msg(i)); + } + + CheckIguanaTypeMsg(st.iguana_type_msg, msg.iguana_type_msg()); + + PB_CHECK(st.repeat_iguna_msg.size() == msg.repeat_iguna_msg_size()); + for (size_t i = 0; i < st.repeat_iguna_msg.size(); ++i) { + CheckIguanaTypeMsg(st.repeat_iguna_msg[i], msg.repeat_iguna_msg(i)); + } + + PB_CHECK(st.repeat_repeat_base_msg.size() == + msg.repeat_repeat_base_msg_size()); + for (size_t i = 0; i < st.repeat_repeat_base_msg.size(); ++i) { + CheckRepeatBaseTypeMsg(st.repeat_repeat_base_msg[i], + msg.repeat_repeat_base_msg(i)); + } +} + +void SetMapMsg(const stpb::MapMsg& st, pb::MapMsg& msg) { + msg.Clear(); + for (const auto& pair : st.sfix64_str_map) { + (*msg.mutable_sfix64_str_map())[pair.first.val] = pair.second; + } + for (const auto& pair : st.str_iguana_type_msg_map) { + pb::IguanaTypeMsg* it_msg = + &((*msg.mutable_str_iguana_type_msg_map())[pair.first]); + SetIguanaTypeMsg(pair.second, *it_msg); + } + for (const auto& pair : st.int_repeat_base_msg_map) { + pb::RepeatBaseTypeMsg* rb_msg = + &((*msg.mutable_int_repeat_base_msg_map())[pair.first]); + SetRepeatBaseTypeMsg(pair.second, *rb_msg); + } +} + +void CheckMapMsg(const stpb::MapMsg& st, const pb::MapMsg& msg) { + PB_CHECK(msg.sfix64_str_map_size() == st.sfix64_str_map.size()); + for (const auto& pair : st.sfix64_str_map) { + auto it = msg.sfix64_str_map().find(pair.first.val); + PB_CHECK(it != msg.sfix64_str_map().end()); + PB_CHECK(it->second == pair.second); + } + PB_CHECK(msg.str_iguana_type_msg_map_size() == + st.str_iguana_type_msg_map.size()); + for (const auto& pair : st.str_iguana_type_msg_map) { + auto it = msg.str_iguana_type_msg_map().find(pair.first); + PB_CHECK(it != msg.str_iguana_type_msg_map().end()); + CheckIguanaTypeMsg(pair.second, it->second); + } + + PB_CHECK(msg.int_repeat_base_msg_map_size() == + st.int_repeat_base_msg_map.size()); + for (const auto& pair : st.int_repeat_base_msg_map) { + auto it = msg.int_repeat_base_msg_map().find(pair.first); + PB_CHECK(it != msg.int_repeat_base_msg_map().end()); + CheckRepeatBaseTypeMsg(pair.second, it->second); + } +} + +void SetBaseOneofMsg(const stpb::BaseOneofMsg& st, pb::BaseOneofMsg& msg) { + msg.set_optional_int32(st.optional_int32); + msg.set_optional_double(st.optional_double); + + std::visit( + [&](auto& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + msg.set_one_of_double(value); + } + else if constexpr (std::is_same_v) { + msg.set_one_of_string(value); + } + else if constexpr (std::is_same_v) { + auto* submsg = msg.mutable_one_of_base_type_msg(); + SetBaseTypeMsg(value, *submsg); + } + }, + st.one_of); +} + +void CheckBaseOneofMsg(const stpb::BaseOneofMsg& st, + const pb::BaseOneofMsg& msg) { + PB_CHECK(st.optional_int32 == msg.optional_int32()); + PB_CHECK(st.optional_double == msg.optional_double()); + + std::visit( + [&](auto& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + PB_CHECK(value == msg.one_of_double()); + } + else if constexpr (std::is_same_v) { + PB_CHECK(value == msg.one_of_string()); + } + else if constexpr (std::is_same_v) { + CheckBaseTypeMsg(value, msg.one_of_base_type_msg()); + } + }, + st.one_of); +} + +void SetNestOneofMsg(const stpb::NestOneofMsg& st, pb::NestOneofMsg& msg) { + std::visit( + [&](auto& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + msg.set_base_one_of_string(value); + } + else if constexpr (std::is_same_v) { + auto* submsg = msg.mutable_base_one_of_msg(); + SetBaseOneofMsg(value, *submsg); + } + }, + st.nest_one_of_msg); +} + +void CheckNestOneofMsg(const stpb::NestOneofMsg& st, + const pb::NestOneofMsg& msg) { + std::visit( + [&](auto& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + PB_CHECK(value == msg.base_one_of_string()); + } + else if constexpr (std::is_same_v) { + CheckBaseOneofMsg(value, msg.base_one_of_msg()); + } + }, + st.nest_one_of_msg); +} + +inline void print_hex_str(const std::string& str) { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (unsigned char c : str) { + oss << std::setw(2) << static_cast(c); + } + std::cout << oss.str() << std::endl; +} diff --git a/test/proto/unittest_proto3.proto b/test/proto/unittest_proto3.proto new file mode 100644 index 00000000..a64f7573 --- /dev/null +++ b/test/proto/unittest_proto3.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package pb; + +option optimize_for = SPEED; + +enum Enum { + ZERO = 0; + FOO = 1; + BAR = 2; + BAZ = 123456; + NEG = -1; // Intentionally negative. +} + +message BaseTypeMsg { + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + float optional_float = 5; + double optional_double = 6; + bool optional_bool = 7; + string optional_string = 8; + Enum optional_enum = 9; +} + +message RepeatBaseTypeMsg { + repeated uint32 repeated_uint32 = 1; + repeated uint64 repeated_uint64 = 2; + repeated int32 repeated_int32 = 3; + repeated int64 repeated_int64 = 4; + repeated float repeated_float = 5; + repeated double repeated_double = 6; + repeated string repeated_string = 7; + repeated Enum repeated_enum = 8; +} + +message IguanaTypeMsg { + sint32 optional_sint32 = 1; + sint64 optional_sint64 = 2; + fixed32 optional_fixed32 = 3; + fixed64 optional_fixed64 = 4; + sfixed32 optional_sfixed32 = 5; + sfixed64 optional_sfixed64 = 6; +} + +message RepeatIguanaTypeMsg { + repeated sfixed32 repeated_sint32 = 1; + repeated sfixed64 repeated_sint64 = 2; + repeated fixed32 repeated_fixed32 = 3; + repeated fixed64 repeated_fixed64 = 4; + repeated sfixed32 repeated_sfixed32 = 5; + repeated sfixed64 repeated_sfixed64 = 6; +} + +message NestedMsg { + BaseTypeMsg base_msg = 1; + repeated BaseTypeMsg repeat_base_msg = 2; + IguanaTypeMsg iguana_type_msg = 3; + repeated IguanaTypeMsg repeat_iguna_msg = 4; + repeated RepeatBaseTypeMsg repeat_repeat_base_msg = 5; +} + +message MapMsg { + map sfix64_str_map = 1; + map str_iguana_type_msg_map = 2; + map int_repeat_base_msg_map = 3; + // Key in map fields cannot be float/double, bytes or message types. +} + +message BaseOneofMsg { + int32 optional_int32 = 1; + oneof one_of { + double one_of_double = 2; + string one_of_string = 3; + BaseTypeMsg one_of_base_type_msg = 4; + } + double optional_double = 5; +} + +message NestOneofMsg { + oneof nest_one_of_msg { + string base_one_of_string = 1; + BaseOneofMsg base_one_of_msg = 2; + } +} + diff --git a/test/test.cpp b/test/test.cpp index adebbe42..f3da8372 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -12,7 +12,6 @@ #include "doctest.h" #include "iguana/json_reader.hpp" #include "iguana/prettify.hpp" -#include "iguana/struct_pb.hpp" #include "iguana/value.hpp" struct point_t { @@ -215,384 +214,6 @@ struct my_variant_t { }; REFLECTION(my_variant_t, name, var); -struct test_pb_st1 { - int x; - iguana::sint32_t y; - iguana::sint64_t z; -}; -REFLECTION(test_pb_st1, x, y, z); - -struct test_pb_st2 { - int x; - iguana::fixed32_t y; - iguana::fixed64_t z; -}; -REFLECTION(test_pb_st2, x, y, z); - -struct test_pb_st3 { - int x; - iguana::sfixed32_t y; - iguana::sfixed64_t z; -}; -REFLECTION(test_pb_st3, x, y, z); - -struct test_pb_st4 { - int x; - std::string y; -}; -REFLECTION(test_pb_st4, x, y); - -struct test_pb_st5 { - int x; - std::string_view y; -}; -REFLECTION(test_pb_st5, x, y); - -struct test_pb_st6 { - std::optional x; - std::optional y; -}; -REFLECTION(test_pb_st6, x, y); - -using Variant1 = std::variant; -struct test_pb_st7 { - iguana::one_of_t<0, Variant1> x; - iguana::one_of_t<1, Variant1> y; -}; -REFLECTION(test_pb_st7, x, y); - -struct pair_t { - int x; - int y; -}; -REFLECTION(pair_t, x, y); - -struct message_t { - int id; - pair_t t; -}; -REFLECTION(message_t, id, t); - -struct test_pb_st8 { - int x; - pair_t y; - message_t z; -}; -REFLECTION(test_pb_st8, x, y, z); - -struct test_pb_st9 { - int x; - std::vector y; - std::string z; -}; -REFLECTION(test_pb_st9, x, y, z); - -struct test_pb_st10 { - int x; - std::vector y; - std::string z; -}; -REFLECTION(test_pb_st10, x, y, z); - -struct test_pb_st11 { - int x; - std::vector> y; - std::vector z; -}; -REFLECTION(test_pb_st11, x, y, z); - -struct test_pb_st12 { - int x; - std::map y; - std::map z; -}; -REFLECTION(test_pb_st12, x, y, z); - -struct test_pb_st13 { - int x; - std::map y; - std::string z; -}; -REFLECTION(test_pb_st13, x, y, z); - -enum class colors_t { red, black }; - -enum level_t { debug, info }; - -struct test_pb_st14 { - int x; - colors_t y; - level_t z; -}; -REFLECTION(test_pb_st14, x, y, z); - -namespace client { -struct person { - std::string name; - int64_t age; -}; - -REFLECTION(person, name, age); -} // namespace client - -struct my_struct { - int x; - int y; - int z; -}; -REFLECTION(my_struct, x, y, z); - -struct nest1 { - std::string name; - my_struct value; - int var; -}; - -REFLECTION(nest1, name, value, var); - -TEST_CASE("test struct_pb") { - { - my_space::inner_struct inner{41, 42, 43}; - - std::string str; - iguana::to_pb(inner, str); - - my_space::inner_struct inner1; - iguana::from_pb(inner1, str); - CHECK(inner.x == inner1.x); - CHECK(inner.y == inner1.y); - CHECK(inner.z == inner1.z); - } - - { - test_pb_st1 st1{41, {42}, {43}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st1 st2; - iguana::from_pb(st2, str); - CHECK(st1.x == st2.x); - CHECK(st1.y.val == st2.y.val); - CHECK(st1.z.val == st2.z.val); - } - - { - test_pb_st2 st1{41, {42}, {43}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st2 st2; - iguana::from_pb(st2, str); - CHECK(st1.y.val == st2.y.val); - } - { - test_pb_st3 st1{41, {42}, {43}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st3 st2; - iguana::from_pb(st2, str); - CHECK(st1.y.val == st2.y.val); - } - { - test_pb_st4 st1{41, "it is a test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st4 st2; - iguana::from_pb(st2, str); - CHECK(st1.y == st2.y); - } - - { - test_pb_st5 st1{41, "it is a test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st5 st2; - iguana::from_pb(st2, str); - CHECK(st1.y == st2.y); - } - - { - test_pb_st5 st1{41, "it is a test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st5 st2; - iguana::from_pb(st2, str); - CHECK(st1.y == st2.y); - } - { - // optional - test_pb_st6 st1{41, "it is a test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st6 st2; - iguana::from_pb(st2, str); - CHECK(st1.y == st2.y); - } - { - // variant - test_pb_st7 st1{{"test"}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st7 st2; - iguana::from_pb(st2, str); - CHECK(st1.x.value == st2.x.value); - } - { - // sub nested objects - nest1 v{"Hi", {1, 2, 3}, 5}, v2; - std::string s; - iguana::to_pb(v, s); - iguana::from_pb(v2, s); - CHECK(v.var == v2.var); - - test_pb_st8 st1{1, {3, 4}, {5, {7, 8}}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st8 st2; - iguana::from_pb(st2, str); - CHECK(st1.z.t.y == st2.z.t.y); - } - { - // repeated messages - test_pb_st9 st1{1, {2, 4, 6}, "test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st9 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - test_pb_st10 st1{1, {{5, {7, 8}}, {9, {11, 12}}}, "test"}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st10 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - message_t m{1, {3, 4}}; - test_pb_st11 st1{1, {m}, {}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st11 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - message_t st1{}; - std::string str; - iguana::to_pb(st1, str); - - message_t st2{}; - iguana::from_pb(st2, str); - CHECK(st1.id == st2.id); - } - { - test_pb_st11 st1{1, {{{5, {7, 8}}}, {{9, {11, 12}}}}, {"test"}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st11 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - // map messages - test_pb_st12 st1{1, {{1, "test"}, {2, "ok"}}, {{"test", 4}, {"ok", 6}}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st12 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - // map messages - test_pb_st12 st1{1, {{1, ""}, {0, "ok"}}, {{"", 4}, {"ok", 0}}}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st12 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - // map messages - test_pb_st13 st1; - st1.x = 1; - st1.y.emplace(1, message_t{2, {3, 4}}); - st1.y.emplace(2, message_t{4, {6, 8}}); - st1.z = "test"; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st13 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - // map messages - test_pb_st13 st1; - st1.x = 1; - st1.y.emplace(2, message_t{}); - st1.y.emplace(3, message_t{}); - st1.z = "test"; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st13 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } - { - // enum - test_pb_st14 st1{1, colors_t::black, level_t::info}; - std::string str; - iguana::to_pb(st1, str); - - test_pb_st14 st2; - iguana::from_pb(st2, str); - CHECK(st1.z == st2.z); - } -} - -TEST_CASE("test members") { - using namespace iguana; - using namespace iguana::detail; - - my_space::inner_struct inner{41, 42, 43}; - const auto &map = iguana::get_members(inner); - std::visit( - [&inner](auto &member) mutable { - CHECK(member.field_no == 9); - CHECK(member.field_name == "b"); - CHECK(member.value(inner) == 42); - }, - map.at(9)); - - point_t pt{2, 3}; - iguana::get_members(pt); - const auto &arr1 = iguana::get_members(pt); - auto &val = arr1.at(0); - std::visit( - [&pt](auto &member) mutable { - CHECK(member.field_no == 1); - CHECK(member.field_name == "x"); - CHECK(member.value(pt) == 2); - }, - val); -} - TEST_CASE("test variant") { std::variant var; var = 1; diff --git a/test/test_pb.cpp b/test/test_pb.cpp new file mode 100644 index 00000000..fe32cd9b --- /dev/null +++ b/test/test_pb.cpp @@ -0,0 +1,475 @@ +#include + +#define DOCTEST_CONFIG_IMPLEMENT +#include "doctest.h" +#include "iguana/pb_reader.hpp" +#include "iguana/pb_writer.hpp" + +void print_hex_str(const std::string &str) { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (unsigned char c : str) { + oss << std::setw(2) << static_cast(c); + } + std::cout << oss.str() << std::endl; +} + +struct point_t { + int x; + double y; +}; +REFLECTION(point_t, x, y); + +namespace my_space { +struct inner_struct { + int x; + int y; + int z; +}; + +constexpr inline auto get_members_impl(inner_struct *) { + return std::make_tuple(iguana::field_t{&inner_struct::x, 7, "a"}, + iguana::field_t{&inner_struct::y, 9, "b"}, + iguana::field_t{&inner_struct::z, 12, "c"}); +} +} // namespace my_space + +struct test_pb_st1 { + int x; + iguana::sint32_t y; + iguana::sint64_t z; +}; +REFLECTION(test_pb_st1, x, y, z); + +struct test_pb_st2 { + int x; + iguana::fixed32_t y; + iguana::fixed64_t z; +}; +REFLECTION(test_pb_st2, x, y, z); + +struct test_pb_st3 { + int x; + iguana::sfixed32_t y; + iguana::sfixed64_t z; +}; +REFLECTION(test_pb_st3, x, y, z); + +struct test_pb_st4 { + int x; + std::string y; +}; +REFLECTION(test_pb_st4, x, y); + +struct test_pb_st5 { + int x; + std::string_view y; +}; +REFLECTION(test_pb_st5, x, y); + +struct test_pb_st6 { + std::optional x; + std::optional y; +}; +REFLECTION(test_pb_st6, x, y); + +struct pair_t { + int x; + int y; +}; +REFLECTION(pair_t, x, y); + +struct message_t { + int id; + pair_t t; +}; +REFLECTION(message_t, id, t); + +struct test_pb_st8 { + int x; + pair_t y; + message_t z; +}; +REFLECTION(test_pb_st8, x, y, z); + +struct test_pb_st9 { + int x; + std::vector y; + std::string z; +}; +REFLECTION(test_pb_st9, x, y, z); + +struct test_pb_st10 { + int x; + std::vector y; + std::string z; +}; +REFLECTION(test_pb_st10, x, y, z); + +struct test_pb_st11 { + int x; + std::vector> y; + std::vector z; +}; +REFLECTION(test_pb_st11, x, y, z); + +struct test_pb_st12 { + int x; + std::map y; + std::map z; +}; +REFLECTION(test_pb_st12, x, y, z); + +struct test_pb_st13 { + int x; + std::map y; + std::string z; +}; +REFLECTION(test_pb_st13, x, y, z); + +enum class colors_t { red, black }; + +enum level_t { debug, info }; + +struct test_pb_st14 { + int x; + colors_t y; + level_t z; +}; +REFLECTION(test_pb_st14, x, y, z); + +namespace client { +struct person { + std::string name; + int64_t age; +}; + +REFLECTION(person, name, age); +} // namespace client + +struct my_struct { + int x; + bool y; + iguana::fixed64_t z; +}; +REFLECTION(my_struct, x, y, z); + +struct nest1 { + std::string name; + my_struct value; + int var; +}; + +REFLECTION(nest1, name, value, var); + +struct numer_st { + bool a; + double b; + float c; +}; +REFLECTION(numer_st, a, b, c); + +TEST_CASE("test struct_pb") { + { + my_space::inner_struct inner{41, 42, 43}; + + std::string str; + iguana::to_pb(inner, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(inner)); + + my_space::inner_struct inner1; + iguana::from_pb(inner1, str); + CHECK(inner.x == inner1.x); + CHECK(inner.y == inner1.y); + CHECK(inner.z == inner1.z); + } + + { + test_pb_st1 st1{41, {42}, {43}}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st1 st2; + iguana::from_pb(st2, str); + CHECK(st1.x == st2.x); + CHECK(st1.y.val == st2.y.val); + CHECK(st1.z.val == st2.z.val); + } + + { + test_pb_st2 st1{41, {42}, {43}}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st2 st2; + iguana::from_pb(st2, str); + CHECK(st1.y.val == st2.y.val); + } + { + test_pb_st3 st1{41, {42}, {43}}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st3 st2; + iguana::from_pb(st2, str); + CHECK(st1.y.val == st2.y.val); + } + { + test_pb_st4 st1{41, "it is a test"}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st4 st2; + iguana::from_pb(st2, str); + CHECK(st1.y == st2.y); + } + + { + test_pb_st5 st1{41, "it is a test"}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st5 st2; + iguana::from_pb(st2, str); + CHECK(st1.y == st2.y); + } + { + // optional + test_pb_st6 st1{41, "it is a test"}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st6 st2; + iguana::from_pb(st2, str); + CHECK(st1.y == st2.y); + } + { + // sub nested objects + nest1 v{"Hi", {1, false, {3}}, 5}, v2{}; + std::string s; + iguana::to_pb(v, s); + iguana::from_pb(v2, s); + CHECK(v.var == v2.var); + CHECK(v.value.y == v2.value.y); + CHECK(v.value.z == v2.value.z); + + test_pb_st8 st1{1, {3, 4}, {5, {7, 8}}}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st8 st2; + iguana::from_pb(st2, str); + CHECK(st1.z.t.y == st2.z.t.y); + } + + { + // repeated messages + test_pb_st9 st1{1, {2, 4, 6}, "test"}; + std::string str; + iguana::to_pb(st1, str); + CHECK(str.size() == iguana::detail::pb_key_value_size<0>(st1)); + + test_pb_st9 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + test_pb_st10 st1{1, {{5, {7, 8}}, {9, {11, 12}}}, "test"}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st10 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + message_t m{1, {3, 4}}; + test_pb_st11 st1{1, {m}, {}}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st11 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + message_t st1{}; + std::string str; + iguana::to_pb(st1, str); + + message_t st2{}; + iguana::from_pb(st2, str); + CHECK(st1.id == st2.id); + } + { + test_pb_st11 st1{1, {{{5, {7, 8}}}, {{9, {11, 12}}}}, {"test"}}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st11 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // map messages + test_pb_st12 st1{1, {{1, "test"}, {2, "ok"}}, {{"test", 4}, {"ok", 6}}}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st12 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // map messages + test_pb_st12 st1{1, {{1, ""}, {0, "ok"}}, {{"", 4}, {"ok", 0}}}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st12 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // map messages + test_pb_st13 st1; + st1.x = 1; + st1.y.emplace(1, message_t{2, {3, 4}}); + st1.y.emplace(2, message_t{4, {6, 8}}); + st1.z = "test"; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st13 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // map messages + test_pb_st13 st1; + st1.x = 1; + st1.y.emplace(2, message_t{}); + st1.y.emplace(3, message_t{}); + st1.z = "test"; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st13 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // enum + test_pb_st14 st1{1, colors_t::black, level_t::info}; + std::string str; + iguana::to_pb(st1, str); + + test_pb_st14 st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + } + { + // bool float double + numer_st n{true, 10.25, 4.578}, n1; + std::string str; + iguana::to_pb(n, str); + + iguana::from_pb(n1, str); + CHECK(n1.a == n.a); + CHECK(n1.b == n.b); + CHECK(n1.c == n.c); + } +} + +TEST_CASE("test members") { + using namespace iguana; + using namespace iguana::detail; + + my_space::inner_struct inner{41, 42, 43}; + const auto &map = iguana::get_members(); + std::visit( + [&inner](auto &member) mutable { + CHECK(member.field_no == 9); + CHECK(member.field_name == "b"); + CHECK(member.value(inner) == 42); + }, + map.at(9)); + + point_t pt{2, 3}; + const auto &arr1 = iguana::get_members(); + auto &val = arr1.at(1); + std::visit( + [&pt](auto &member) mutable { + CHECK(member.field_no == 1); + CHECK(member.field_name == "x"); + CHECK(member.value(pt) == 2); + }, + val); +} + +struct test_variant { + int x; + std::variant y; + double z; +}; +REFLECTION(test_variant, x, y, z); + +TEST_CASE("test variant") { + { + constexpr auto tp = iguana::get_members_tuple(); + static_assert(std::get<0>(tp).field_no == 1); + static_assert(std::get<1>(tp).field_no == 2); + static_assert(std::get<2>(tp).field_no == 3); + static_assert(std::get<3>(tp).field_no == 4); + static_assert(std::get<4>(tp).field_no == 5); + } + { + constexpr static auto map = iguana::get_members(); + static_assert(map.find(1) != map.end()); + static_assert(map.find(2) != map.end()); + static_assert(map.find(3) != map.end()); + static_assert(map.find(4) != map.end()); + auto val1 = map.find(2); + auto val2 = map.find(3); + std::visit( + [](auto &member) mutable { + CHECK(member.field_no == 2); + CHECK(member.field_name == "y"); + }, + val1->second); + std::visit( + [](auto &member) mutable { + CHECK(member.field_no == 3); + CHECK(member.field_name == "y"); + }, + val2->second); + } + { + test_variant st1 = {5, "Hello, variant!", 3.14}; + std::string str; + iguana::to_pb(st1, str); + test_variant st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + CHECK(std::get(st2.y) == "Hello, variant!"); + } + { + test_variant st1 = {5, 3.88, 3.14}; + std::string str; + iguana::to_pb(st1, str); + test_variant st2; + iguana::from_pb(st2, str); + CHECK(st1.z == st2.z); + CHECK(std::get(st2.y) == 3.88); + } +} + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) +int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP diff --git a/test/test_proto3.cpp b/test/test_proto3.cpp new file mode 100644 index 00000000..55354a46 --- /dev/null +++ b/test/test_proto3.cpp @@ -0,0 +1,490 @@ +#define DOCTEST_CONFIG_IMPLEMENT +#include "doctest.h" +#include "proto/unittest_proto3.h" // msg reflection + +TEST_CASE("test BaseTypeMsg") { + { // normal test + stpb::BaseTypeMsg se_st{ + 100, 200, 300, 400, 31.4f, 62.8, false, "World", stpb::Enum::ZERO}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseTypeMsg se_msg; + SetBaseTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::BaseTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::BaseTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseTypeMsg(dese_st, dese_msg); + } + + { // test min and empty str + stpb::BaseTypeMsg se_st{std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + false, + "", + stpb::Enum::NEG}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseTypeMsg se_msg; + SetBaseTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::BaseTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::BaseTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseTypeMsg(dese_st, dese_msg); + } + { // test max and long str + stpb::BaseTypeMsg se_st{std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + true, + std::string(1000, 'x'), + stpb::Enum::BAZ}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseTypeMsg se_msg; + SetBaseTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::BaseTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::BaseTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseTypeMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test IguanaTypeMsg") { + { // test normal value + stpb::IguanaTypeMsg se_st{{100}, {200}, {300}, {400}, {31}, {32}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::IguanaTypeMsg se_msg{}; + SetIguanaTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::IguanaTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::IguanaTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckIguanaTypeMsg(dese_st, dese_msg); + } + + { // test min value + stpb::IguanaTypeMsg se_st{{std::numeric_limits::min()}, + {std::numeric_limits::min()}, + {std::numeric_limits::min()}, + {std::numeric_limits::min()}, + {std::numeric_limits::lowest()}, + {std::numeric_limits::lowest()}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::IguanaTypeMsg se_msg{}; + SetIguanaTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + stpb::IguanaTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::IguanaTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckIguanaTypeMsg(dese_st, dese_msg); + } + { // test max value + stpb::IguanaTypeMsg se_st{{std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}, + {std::numeric_limits::max()}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::IguanaTypeMsg se_msg; + SetIguanaTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::IguanaTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::IguanaTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckIguanaTypeMsg(dese_st, dese_msg); + } + { // test empty + stpb::IguanaTypeMsg se_st{{}, {}, {}, {}, {}, {}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::IguanaTypeMsg se_msg; + SetIguanaTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::IguanaTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::IguanaTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckIguanaTypeMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test RepeatBaseTypeMsg") { + { + stpb::RepeatBaseTypeMsg se_st{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {10, 11, 12}, + {13.1, 14.2, 15.3}, + {16.4, 17.5, 18.6}, + {"a", "b", "c"}, + {stpb::Enum::BAZ, stpb::Enum::ZERO, stpb::Enum::NEG}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::RepeatBaseTypeMsg se_msg; + SetRepeatBaseTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::RepeatBaseTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::RepeatBaseTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckRepeatBaseTypeMsg(dese_st, dese_msg); + } + { // max and min vlaue + stpb::RepeatBaseTypeMsg se_st{{std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {}, + {std::numeric_limits::max(), + std::numeric_limits::min()}, + {"", "", ""}, + {stpb::Enum::NEG, stpb::Enum::FOO}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::RepeatBaseTypeMsg se_msg; + SetRepeatBaseTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + stpb::RepeatBaseTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::RepeatBaseTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckRepeatBaseTypeMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test RepeatIguanaTypeMsg") { + { + stpb::RepeatIguanaTypeMsg se_st{ + {{0}, {1}, {3}}, {{4}, {5}, {6}}, {{7}, {8}, {9}}, + {{10}, {11}, {12}}, {{13}, {14}, {15}}, {}, + }; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::RepeatIguanaTypeMsg se_msg; + SetRepeatIguanaTypeMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::RepeatIguanaTypeMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + pb::RepeatIguanaTypeMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckRepeatIguanaTypeMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test NestedMsg") { + { + stpb::NestedMsg se_st{ + /* base_msg */ {100, 200, 300, 400, 31.4f, 62.8, false, "World", + stpb::Enum::BAZ}, + /* repeat_base_msg */ + {{1, 2, 3, 4, 5.5f, 6.6, true, "Hello", stpb::Enum::FOO}, + {7, 8, 9, 10, 11.11f, 12.12, false, "Hi", stpb::Enum::BAR}}, + /* iguana_type_msg */ {{100}, {200}, {300}, {400}, {31}, {32}}, + /* repeat_iguna_msg */ + {{{1}, {2}, {3}}, {{4}, {5}, {6}}, {{7}, {8}, {9}}}, + /* repeat_repeat_base_msg */ + {{{1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {10, 11, 12}, + {13.1, 14.2, 15.3}, + {16.4, 17.5, 18.6}, + {"a", "b", "c"}, + {stpb::Enum::FOO, stpb::Enum::BAR, stpb::Enum::BAZ}}, + {{19, 20, 21}, + {22, 23, 24}, + {25, 26, 27}, + {28, 29, 30}, + {31.1, 32.2, 33.3}, + {34.4, 35.5, 36.6}, + {"x", "y", "z"}, + {stpb::Enum::ZERO, stpb::Enum::NEG, stpb::Enum::FOO}}}}; + + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::NestedMsg se_msg; + SetNestedMsg(se_st, se_msg); + + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + + CHECK(st_ss == pb_ss); + + stpb::NestedMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::NestedMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + + CheckNestedMsg(dese_st, dese_msg); + } + { // test empty values + stpb::NestedMsg se_st{ + /* base_msg */ {0, 0, 0, 0, 0.0f, 0.0, true, "", stpb::Enum::ZERO}, + /* repeat_base_msg */ {}, + /* iguana_type_msg */ {{0}, {0}, {0}, {0}, {0}, {0}}, + /* repeat_iguna_msg */ {}, + /* repeat_repeat_base_msg */ {}}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::NestedMsg se_msg; + SetNestedMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + + // CHECK(st_ss == pb_ss); + print_hex_str(st_ss); + print_hex_str(pb_ss); + stpb::NestedMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::NestedMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + + CheckNestedMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test MapMsg") { + { + stpb::MapMsg se_st{}; + + se_st.sfix64_str_map.emplace(iguana::sfixed64_t{10}, "ten"); + se_st.sfix64_str_map.emplace(iguana::sfixed64_t{20}, "twenty"); + + se_st.str_iguana_type_msg_map.emplace( + "first", stpb::IguanaTypeMsg{{10}, {20}, {30}, {40}, {50}, {60}}); + se_st.str_iguana_type_msg_map.emplace( + "second", stpb::IguanaTypeMsg{{11}, {21}, {31}, {41}, {51}, {61}}); + + se_st.int_repeat_base_msg_map.emplace( + 1, stpb::RepeatBaseTypeMsg{{1, 2}, + {3, 4}, + {5, 6}, + {7, 8}, + {9.0f, 10.0f}, + {11.0, 12.0}, + {"one", "two"}, + {stpb::Enum::FOO, stpb::Enum::BAR}}); + se_st.int_repeat_base_msg_map.emplace( + 2, stpb::RepeatBaseTypeMsg{{2, 3}, + {4, 5}, + {6, 7}, + {8, 9}, + {10.0f, 11.0f}, + {12.0, 13.0}, + {"three", "four"}, + {stpb::Enum::BAZ, stpb::Enum::NEG}}); + + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::MapMsg se_msg{}; + SetMapMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + // It's okay not to satisfy this. + // CHECK(st_ss == pb_ss); + CHECK(st_ss.size() == pb_ss.size()); + stpb::MapMsg dese_st{}; + iguana::from_pb(dese_st, pb_ss); + pb::MapMsg dese_msg; + dese_msg.ParseFromString(st_ss); + CheckMapMsg(dese_st, dese_msg); + } + { + // key empty + stpb::MapMsg se_st{}; + se_st.sfix64_str_map.emplace(iguana::sfixed64_t{30}, ""); + se_st.str_iguana_type_msg_map.emplace( + "", stpb::IguanaTypeMsg{{0}, {0}, {0}, {0}, {0}, {0}}); + se_st.int_repeat_base_msg_map.emplace( + 3, stpb::RepeatBaseTypeMsg{{}, {}, {}, {}, {}, {}, {}, {}}); + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::MapMsg se_msg{}; + SetMapMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::MapMsg dese_st{}; + iguana::from_pb(dese_st, pb_ss); + pb::MapMsg dese_msg; + dese_msg.ParseFromString(st_ss); + CheckMapMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test BaseOneofMsg") { + { // test double + stpb::BaseOneofMsg se_st{123, 3.14159, 456.78}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseOneofMsg se_msg; + SetBaseOneofMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + // print_hex_str(st_ss); + // print_hex_str(pb_ss); + stpb::BaseOneofMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::BaseOneofMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseOneofMsg(dese_st, dese_msg); + } + { // test string + stpb::BaseOneofMsg se_st{123, std::string("Hello"), 456.78}; + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseOneofMsg se_msg; + SetBaseOneofMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::BaseOneofMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::BaseOneofMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseOneofMsg(dese_st, dese_msg); + } + { // test BaseTypeMsg + stpb::BaseTypeMsg baseTypeMsg{ + 100, 200, 300, 400, 31.4f, 62.8, false, "World", stpb::Enum::BAZ}; + stpb::BaseOneofMsg se_st{123, baseTypeMsg, 456.78}; + + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseOneofMsg se_msg; + SetBaseOneofMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + + stpb::BaseOneofMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::BaseOneofMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseOneofMsg(dese_st, dese_msg); + } + { // test empty variant + stpb::BaseOneofMsg se_st{123, {}, 456.78}; + + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::BaseOneofMsg se_msg; + SetBaseOneofMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + print_hex_str(st_ss); + print_hex_str(pb_ss); + stpb::BaseOneofMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::BaseOneofMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckBaseOneofMsg(dese_st, dese_msg); + } +} + +TEST_CASE("test NestOneofMsg ") { + { // Test BaseOneofMsg + stpb::BaseOneofMsg baseOneof{123, std::string("Hello"), 456.78}; + stpb::NestOneofMsg se_st{{baseOneof}}; + + std::string st_ss; + iguana::to_pb(se_st, st_ss); + + pb::NestOneofMsg se_msg; + SetNestOneofMsg(se_st, se_msg); + std::string pb_ss; + se_msg.SerializeToString(&pb_ss); + CHECK(st_ss == pb_ss); + stpb::NestOneofMsg dese_st{}; + iguana::from_pb(dese_st, st_ss); + + pb::NestOneofMsg dese_msg; + dese_msg.ParseFromString(pb_ss); + CheckNestOneofMsg(dese_st, dese_msg); + } +} + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP