diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 9633ab35..5ee5d7da 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks", "headers"] + format: ["JSON", "AVRO", "BOOST_SERIALIZATION", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks", "headers"] compiler: [llvm, gcc] compiler-version: [11, 12, 13, 14, 16, 17, 18] cxx: [20, 23] diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 6aa4e3a0..59e76349 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks"] + format: ["JSON", "AVRO", "BOOST_SERIALIZATION", "CAPNPROTO", "CBOR", "CEREAL", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML", "benchmarks"] name: "windows-msvc (${{ matrix.format }})" concurrency: group: "windows-${{ github.ref }}-${{ github.job }}-${{ matrix.format }}" @@ -77,7 +77,7 @@ jobs: strategy: fail-fast: false matrix: - format: ["JSON", "AVRO", "CAPNPROTO", "CBOR", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML"] + format: ["JSON", "AVRO", "BOOST_SERIALIZATION", "CAPNPROTO", "CBOR", "FLEXBUFFERS", "MSGPACK", "PARQUET", "TOML", "UBJSON", "XML", "YAML"] name: "windows-msvc-shared (${{ matrix.format }})" runs-on: windows-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b8b2408..a3d684a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ option(REFLECTCPP_XML "Enable XML support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_TOML "Enable TOML support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_UBJSON "Enable UBJSON support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_YAML "Enable YAML support" ${REFLECTCPP_ALL_FORMATS}) +option(REFLECTCPP_BOOST_SERIALIZATION "Enable Boost.Serialization support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_BUILD_BENCHMARKS "Build benchmarks" OFF) option(REFLECTCPP_BUILD_TESTS "Build tests" OFF) @@ -57,6 +58,7 @@ if(REFLECTCPP_BUILD_BENCHMARKS) set(REFLECTCPP_TOML ON CACHE BOOL "" FORCE) set(REFLECTCPP_UBJSON ON CACHE BOOL "" FORCE) set(REFLECTCPP_YAML ON CACHE BOOL "" FORCE) + set(REFLECTCPP_BOOST_SERIALIZATION ON CACHE BOOL "" FORCE) endif() if ( @@ -65,6 +67,7 @@ if ( REFLECTCPP_CHECK_HEADERS OR (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES) OR REFLECTCPP_AVRO OR + REFLECTCPP_BOOST_SERIALIZATION OR REFLECTCPP_BSON OR REFLECTCPP_CAPNPROTO OR REFLECTCPP_CBOR OR @@ -95,6 +98,10 @@ if (REFLECTCPP_USE_VCPKG) list(APPEND VCPKG_MANIFEST_FEATURES "avro") endif() + if (REFLECTCPP_BOOST_SERIALIZATION OR REFLECTCPP_CHECK_HEADERS) + list(APPEND VCPKG_MANIFEST_FEATURES "boost-serialization") + endif() + if (REFLECTCPP_BSON OR REFLECTCPP_CHECK_HEADERS) list(APPEND VCPKG_MANIFEST_FEATURES "bson") endif() @@ -424,6 +431,13 @@ if (REFLECTCPP_YAML OR REFLECTCPP_CHECK_HEADERS) target_link_libraries(reflectcpp PUBLIC yaml-cpp::yaml-cpp) endif () +if (REFLECTCPP_BOOST_SERIALIZATION OR REFLECTCPP_CHECK_HEADERS) + if (NOT TARGET Boost::serialization) + find_package(Boost REQUIRED COMPONENTS serialization) + endif () + target_link_libraries(reflectcpp PUBLIC Boost::serialization) +endif () + set_target_properties(reflectcpp PROPERTIES LINKER_LANGUAGE CXX) target_sources(reflectcpp PRIVATE ${REFLECT_CPP_SOURCES}) target_precompile_headers(reflectcpp PRIVATE [["rfl.hpp"]] ) diff --git a/README.md b/README.md index 1232851a..6ada9d1b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ The following table lists the serialization formats currently supported by refle |--------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------| | JSON | [yyjson](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository | | Avro | [avro-c](https://avro.apache.org/docs/1.11.1/api/c/) | >= 1.11.3 | Apache 2.0 | Schemaful binary format | +| Boost.Serialization | [Boost.Serialization](https://www.boost.org/doc/libs/release/libs/serialization/) | >= 1.74.0 | BSL 1.0 | Streaming binary format with archive interop | | BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format | | Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format | | CBOR | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format | diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index e848ffea..853b98c6 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,18 @@ static void BM_canada_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_bson); +static void BM_canada_read_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = rfl::boost_serialization::write(load_data()); + for (auto _ : state) { + const auto res = rfl::boost_serialization::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_boost_serialization); + static void BM_canada_read_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = rfl::capnproto::write(load_data(), schema); @@ -237,4 +250,3 @@ BENCHMARK(BM_canada_read_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace canada_read - diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 3b7005c0..87254f14 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,18 @@ static void BM_canada_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_bson); +static void BM_canada_write_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::boost_serialization::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_boost_serialization); + static void BM_canada_write_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = load_data(); @@ -221,4 +234,3 @@ BENCHMARK(BM_canada_write_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace canada_write - diff --git a/benchmarks/all/licenses_read.cpp b/benchmarks/all/licenses_read.cpp index 28958252..b12bd5d1 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -86,6 +87,18 @@ static void BM_licenses_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_bson); +static void BM_licenses_read_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = rfl::boost_serialization::write(load_data()); + for (auto _ : state) { + const auto res = rfl::boost_serialization::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_boost_serialization); + static void BM_licenses_read_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = rfl::capnproto::write(load_data(), schema); @@ -260,4 +273,3 @@ BENCHMARK(BM_licenses_read_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace licenses_read - diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index 74e74746..fbfca915 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -86,6 +87,18 @@ static void BM_licenses_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_bson); +static void BM_licenses_write_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::boost_serialization::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_boost_serialization); + static void BM_licenses_write_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = load_data(); @@ -260,4 +273,3 @@ BENCHMARK(BM_licenses_write_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace licenses_write - diff --git a/benchmarks/all/person_read.cpp b/benchmarks/all/person_read.cpp index 80e79d26..24a995be 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,18 @@ static void BM_person_read_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_bson); +static void BM_person_read_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = rfl::boost_serialization::write(load_data()); + for (auto _ : state) { + const auto res = rfl::boost_serialization::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_boost_serialization); + static void BM_person_read_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = rfl::capnproto::write(load_data(), schema); @@ -238,4 +251,3 @@ BENCHMARK(BM_person_read_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace person_read - diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index 3e2f5131..eb946c4e 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -65,6 +66,18 @@ static void BM_person_write_reflect_cpp_bson(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_bson); +static void BM_person_write_reflect_cpp_boost_serialization( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::boost_serialization::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_boost_serialization); + static void BM_person_write_reflect_cpp_capnproto(benchmark::State &state) { const auto schema = rfl::capnproto::to_schema(); const auto data = load_data(); @@ -239,4 +252,3 @@ BENCHMARK(BM_person_write_reflect_cpp_yaml); // ---------------------------------------------------------------------------- } // namespace person_write - diff --git a/docs/supported_formats/boost_serialization.md b/docs/supported_formats/boost_serialization.md new file mode 100644 index 00000000..191ca61d --- /dev/null +++ b/docs/supported_formats/boost_serialization.md @@ -0,0 +1,90 @@ +# Boost.Serialization + +For Boost.Serialization support, you must also include the header `` and link to the [Boost.Serialization](https://www.boost.org/doc/libs/release/libs/serialization/) library. +Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_BOOST_SERIALIZATION=ON` to cmake. If you are using vcpkg, there +should be an appropriate feature that will abstract this away for you. + +Unlike most other formats supported by reflect-cpp, which use document-tree based libraries, Boost.Serialization streams data sequentially through archive objects. reflect-cpp implements this as a schemaful binary format, similar to Avro and Cap'n Proto. + +## Reading and writing + +Suppose you have a struct like this: + +```cpp +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; +``` + +A `Person` can be serialized like this: + +```cpp +const auto person = Person{...}; +const std::vector bytes = rfl::boost_serialization::write(person); +``` + +You can parse bytes like this: + +```cpp +const rfl::Result result = rfl::boost_serialization::read(bytes); +``` + +## Loading and saving + +You can also load and save to disc using a very similar syntax: + +```cpp +const rfl::Result result = rfl::boost_serialization::load("/path/to/file.bin"); + +const auto person = Person{...}; +rfl::boost_serialization::save("/path/to/file.bin", person); +``` + +## Reading from and writing into streams + +You can also read from and write into any `std::istream` and `std::ostream` respectively. + +```cpp +const rfl::Result result = rfl::boost_serialization::read(my_istream); + +const auto person = Person{...}; +rfl::boost_serialization::write(person, my_ostream); +``` + +## Archive interop + +One of the key features of the Boost.Serialization backend is that you can use it with existing Boost archives directly. This allows reflect-cpp types to participate in larger Boost serialization workflows. + +```cpp +#include +#include +#include + +// Writing into an existing Boost archive +boost::archive::binary_oarchive oa(my_ostream); +rfl::boost_serialization::write(oa, person); + +// Reading from an existing Boost archive +boost::archive::binary_iarchive ia(my_istream); +const auto result = rfl::boost_serialization::read_from_archive< + Person, boost::archive::binary_iarchive, + boost::archive::binary_oarchive>(ia); +``` + +This also works with text archives: + +```cpp +#include +#include + +boost::archive::text_oarchive oa(my_ostream); +rfl::boost_serialization::write(oa, person); + +boost::archive::text_iarchive ia(my_istream); +const auto result = rfl::boost_serialization::read_from_archive< + Person, boost::archive::text_iarchive, + boost::archive::text_oarchive>(ia); +``` diff --git a/include/rfl/boost_serialization.hpp b/include/rfl/boost_serialization.hpp new file mode 100644 index 00000000..237da0e0 --- /dev/null +++ b/include/rfl/boost_serialization.hpp @@ -0,0 +1,13 @@ +#ifndef RFL_BOOST_SERIALIZATION_HPP_ +#define RFL_BOOST_SERIALIZATION_HPP_ + +#include "../rfl.hpp" +#include "boost_serialization/Parser.hpp" +#include "boost_serialization/Reader.hpp" +#include "boost_serialization/Writer.hpp" +#include "boost_serialization/load.hpp" +#include "boost_serialization/read.hpp" +#include "boost_serialization/save.hpp" +#include "boost_serialization/write.hpp" + +#endif diff --git a/include/rfl/boost_serialization/Parser.hpp b/include/rfl/boost_serialization/Parser.hpp new file mode 100644 index 00000000..98b88450 --- /dev/null +++ b/include/rfl/boost_serialization/Parser.hpp @@ -0,0 +1,137 @@ +#ifndef RFL_BOOST_SERIALIZATION_PARSER_HPP_ +#define RFL_BOOST_SERIALIZATION_PARSER_HPP_ + +#include "../Generic.hpp" +#include "../NamedTuple.hpp" +#include "../Tuple.hpp" +#include "../always_false.hpp" +#include "../parsing/Parser.hpp" +#include "Reader.hpp" +#include "Writer.hpp" + +namespace rfl { +namespace parsing { + +/// Boost.Serialization requires us to explicitly set all fields. Because +/// of that, we require all of the fields and then set them to nullptr, if +/// necessary. +template + requires AreReaderAndWriter, + boost_serialization::Writer, + NamedTuple> +struct Parser, + boost_serialization::Writer, NamedTuple, + ProcessorsType> + : public NamedTupleParser< + boost_serialization::Reader, + boost_serialization::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> {}; + +template + requires AreReaderAndWriter, + boost_serialization::Writer, + rfl::Tuple> +struct Parser, + boost_serialization::Writer, rfl::Tuple, + ProcessorsType> + : public TupleParser, + boost_serialization::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, ProcessorsType, + rfl::Tuple> {}; + +template + requires AreReaderAndWriter, + boost_serialization::Writer, + std::tuple> +struct Parser, + boost_serialization::Writer, std::tuple, + ProcessorsType> + : public TupleParser, + boost_serialization::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, ProcessorsType, + std::tuple> {}; + +template + requires AreReaderAndWriter, + boost_serialization::Writer, Generic> +struct Parser, + boost_serialization::Writer, Generic, ProcessorsType> { + template + static Result read(const boost_serialization::Reader&, + const T&) noexcept { + static_assert(always_false_v, + "Generics are unsupported in Boost.Serialization."); + return error("Unsupported"); + } + + template + static void write(const boost_serialization::Writer&, + const Generic&, const P&) noexcept { + static_assert(always_false_v

, + "Generics are unsupported in Boost.Serialization."); + } + + template + static schema::Type to_schema(T*) { + static_assert(always_false_v, + "Generics are unsupported in Boost.Serialization."); + return schema::Type{}; + } +}; + +template + requires AreReaderAndWriter< + boost_serialization::Reader, + boost_serialization::Writer, + internal::Skip> +struct Parser, + boost_serialization::Writer, + internal::Skip, + ProcessorsType> { + using R = boost_serialization::Reader; + using W = boost_serialization::Writer; + + template + static Result> + read(const R&, const U&) noexcept { + static_assert(always_false_v, + "rfl::Skip is unsupported in Boost.Serialization."); + return error("Unsupported"); + } + + template + static void write(const W& /*_w*/, + const internal::Skip& /*_skip*/, + const P& /*_parent*/) noexcept { + static_assert(always_false_v

, + "rfl::Skip is unsupported in Boost.Serialization."); + } + + template + static schema::Type to_schema(U* /*_definitions*/) { + static_assert(always_false_v, + "rfl::Skip is unsupported in Boost.Serialization."); + return schema::Type{}; + } +}; + +} // namespace parsing +} // namespace rfl + +namespace rfl::boost_serialization { + +template +using Parser = + parsing::Parser, Writer, T, ProcessorsType>; + +} // namespace rfl::boost_serialization + +#endif diff --git a/include/rfl/boost_serialization/Reader.hpp b/include/rfl/boost_serialization/Reader.hpp new file mode 100644 index 00000000..03bc0087 --- /dev/null +++ b/include/rfl/boost_serialization/Reader.hpp @@ -0,0 +1,186 @@ +#ifndef RFL_BOOST_SERIALIZATION_READER_HPP_ +#define RFL_BOOST_SERIALIZATION_READER_HPP_ + +#include +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" + +namespace rfl::boost_serialization { + +template +struct Reader { + struct BoostInputArray { + IArchive* ar; + size_t size; + }; + + struct BoostInputMap { + IArchive* ar; + size_t size; + }; + + struct BoostInputObject { + IArchive* ar; + size_t size; + }; + + struct BoostInputUnion { + IArchive* ar; + }; + + struct BoostInputVar { + IArchive* ar; + }; + + using InputArrayType = BoostInputArray; + using InputMapType = BoostInputMap; + using InputObjectType = BoostInputObject; + using InputUnionType = BoostInputUnion; + using InputVarType = BoostInputVar; + + template + static constexpr bool has_custom_constructor = false; + + bool is_empty(const InputVarType& /*_var*/) const noexcept { return false; } + + template + rfl::Result to_basic_type(const InputVarType& _var) const noexcept { + try { + if constexpr (std::is_same, std::string>()) { + std::string val; + *_var.ar >> val; + return val; + } else if constexpr (std::is_same, bool>()) { + bool val = false; + *_var.ar >> val; + return val; + } else if constexpr (std::is_floating_point>()) { + double val = 0.0; + *_var.ar >> val; + return static_cast(val); + } else if constexpr (std::is_unsigned>()) { + std::uint64_t val = 0; + *_var.ar >> val; + return static_cast(val); + } else if constexpr (std::is_integral>()) { + std::int64_t val = 0; + *_var.ar >> val; + return static_cast(val); + } else if constexpr (internal::is_literal_v) { + std::int64_t val = 0; + *_var.ar >> val; + return T::from_value( + static_cast::ValueType>(val)); + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + } catch (std::exception& e) { + return error(e.what()); + } + } + + rfl::Result to_array( + const InputVarType& _var) const noexcept { + try { + std::uint64_t size = 0; + *_var.ar >> size; + return InputArrayType{_var.ar, static_cast(size)}; + } catch (std::exception& e) { + return error(e.what()); + } + } + + rfl::Result to_object( + const InputVarType& _var) const noexcept { + try { + std::uint64_t size = 0; + *_var.ar >> size; + return InputObjectType{_var.ar, static_cast(size)}; + } catch (std::exception& e) { + return error(e.what()); + } + } + + rfl::Result to_map(const InputVarType& _var) const noexcept { + try { + std::uint64_t size = 0; + *_var.ar >> size; + return InputMapType{_var.ar, static_cast(size)}; + } catch (std::exception& e) { + return error(e.what()); + } + } + + rfl::Result to_union( + const InputVarType& _var) const noexcept { + return InputUnionType{_var.ar}; + } + + template + std::optional read_array(const ArrayReader& _array_reader, + const InputArrayType& _arr) const noexcept { + for (size_t i = 0; i < _arr.size; ++i) { + auto var = InputVarType{_arr.ar}; + const auto err = _array_reader.read(var); + if (err) { + return err; + } + } + return std::nullopt; + } + + template + std::optional read_map(const MapReader& _map_reader, + const InputMapType& _map) const noexcept { + try { + for (size_t i = 0; i < _map.size; ++i) { + std::string key; + *_map.ar >> key; + auto var = InputVarType{_map.ar}; + _map_reader.read(std::string_view(key), var); + } + return std::nullopt; + } catch (std::exception& e) { + return Error(e.what()); + } + } + + template + std::optional read_object(const ObjectReader& _object_reader, + const InputObjectType& _obj) const noexcept { + for (size_t i = 0; i < _obj.size; ++i) { + auto var = InputVarType{_obj.ar}; + _object_reader.read(static_cast(i), var); + } + return std::nullopt; + } + + template + rfl::Result read_union( + const InputUnionType& _union) const noexcept { + try { + std::uint64_t disc = 0; + *_union.ar >> disc; + auto var = InputVarType{_union.ar}; + return UnionReaderType::read(*this, static_cast(disc), var); + } catch (std::exception& e) { + return error(e.what()); + } + } + + template + rfl::Result use_custom_constructor( + const InputVarType& /*_var*/) const noexcept { + return rfl::error("Custom constructors are not supported."); + } +}; + +} // namespace rfl::boost_serialization + +#endif diff --git a/include/rfl/boost_serialization/Writer.hpp b/include/rfl/boost_serialization/Writer.hpp new file mode 100644 index 00000000..df8bf4e5 --- /dev/null +++ b/include/rfl/boost_serialization/Writer.hpp @@ -0,0 +1,252 @@ +#ifndef RFL_BOOST_SERIALIZATION_WRITER_HPP_ +#define RFL_BOOST_SERIALIZATION_WRITER_HPP_ + +#include +#include +#include +#include + +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" + +namespace rfl::boost_serialization { + +template +class Writer { + public: + struct BoostOutputArray {}; + + struct BoostOutputMap {}; + + struct BoostOutputObject {}; + + struct BoostOutputUnion {}; + + struct BoostOutputVar {}; + + using OutputArrayType = BoostOutputArray; + using OutputMapType = BoostOutputMap; + using OutputObjectType = BoostOutputObject; + using OutputUnionType = BoostOutputUnion; + using OutputVarType = BoostOutputVar; + + Writer(OArchive* _ar) : ar_(_ar) {} + + ~Writer() = default; + + OutputArrayType array_as_root(const size_t _size) const { + *ar_ << static_cast(_size); + return OutputArrayType{}; + } + + OutputMapType map_as_root(const size_t _size) const { + *ar_ << static_cast(_size); + return OutputMapType{}; + } + + OutputObjectType object_as_root(const size_t _size) const { + *ar_ << static_cast(_size); + return OutputObjectType{}; + } + + OutputVarType null_as_root() const { return OutputVarType{}; } + + OutputUnionType union_as_root() const { return OutputUnionType{}; } + + template + OutputVarType value_as_root(const T& _var) const { + new_value(_var); + return OutputVarType{}; + } + + OutputArrayType add_array_to_array(const size_t _size, + OutputArrayType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputArrayType{}; + } + + OutputArrayType add_array_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + *ar_ << static_cast(_size); + return OutputArrayType{}; + } + + OutputArrayType add_array_to_object(const std::string_view& /*_name*/, + const size_t _size, + OutputObjectType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputArrayType{}; + } + + OutputArrayType add_array_to_union(const size_t _index, const size_t _size, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + *ar_ << static_cast(_size); + return OutputArrayType{}; + } + + OutputMapType add_map_to_array(const size_t _size, + OutputArrayType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputMapType{}; + } + + OutputMapType add_map_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + *ar_ << static_cast(_size); + return OutputMapType{}; + } + + OutputMapType add_map_to_object(const std::string_view& /*_name*/, + const size_t _size, + OutputObjectType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputMapType{}; + } + + OutputMapType add_map_to_union(const size_t _index, const size_t _size, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + *ar_ << static_cast(_size); + return OutputMapType{}; + } + + OutputObjectType add_object_to_array(const size_t _size, + OutputArrayType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputObjectType{}; + } + + OutputObjectType add_object_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + *ar_ << static_cast(_size); + return OutputObjectType{}; + } + + OutputObjectType add_object_to_object(const std::string_view& /*_name*/, + const size_t _size, + OutputObjectType* /*_parent*/) const { + *ar_ << static_cast(_size); + return OutputObjectType{}; + } + + OutputObjectType add_object_to_union(const size_t _index, const size_t _size, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + *ar_ << static_cast(_size); + return OutputObjectType{}; + } + + OutputUnionType add_union_to_array(OutputArrayType* /*_parent*/) const { + return OutputUnionType{}; + } + + OutputUnionType add_union_to_map(const std::string_view& _name, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + return OutputUnionType{}; + } + + OutputUnionType add_union_to_object(const std::string_view& /*_name*/, + OutputObjectType* /*_parent*/) const { + return OutputUnionType{}; + } + + OutputUnionType add_union_to_union(const size_t _index, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + return OutputUnionType{}; + } + + template + OutputVarType add_value_to_array(const T& _var, + OutputArrayType* /*_parent*/) const { + new_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + new_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_object(const std::string_view& /*_name*/, + const T& _var, + OutputObjectType* /*_parent*/) const { + new_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_union(const size_t _index, const T& _var, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + new_value(_var); + return OutputVarType{}; + } + + OutputVarType add_null_to_array(OutputArrayType* /*_parent*/) const { + return OutputVarType{}; + } + + OutputVarType add_null_to_map(const std::string_view& _name, + OutputMapType* /*_parent*/) const { + *ar_ << std::string(_name); + return OutputVarType{}; + } + + OutputVarType add_null_to_object(const std::string_view& /*_name*/, + OutputObjectType* /*_parent*/) const { + return OutputVarType{}; + } + + OutputVarType add_null_to_union(const size_t _index, + OutputUnionType* /*_parent*/) const { + *ar_ << static_cast(_index); + return OutputVarType{}; + } + + void end_array(OutputArrayType* /*_arr*/) const {} + + void end_map(OutputMapType* /*_obj*/) const {} + + void end_object(OutputObjectType* /*_obj*/) const {} + + private: + template + void new_value(const T& _var) const { + using Type = std::remove_cvref_t; + if constexpr (std::is_same()) { + *ar_ << _var; + } else if constexpr (std::is_same()) { + *ar_ << _var; + } else if constexpr (std::is_floating_point()) { + *ar_ << static_cast(_var); + } else if constexpr (std::is_unsigned()) { + *ar_ << static_cast(_var); + } else if constexpr (std::is_integral()) { + *ar_ << static_cast(_var); + } else if constexpr (internal::is_literal_v) { + *ar_ << static_cast(_var.value()); + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + } + + private: + OArchive* ar_; +}; + +} // namespace rfl::boost_serialization + +#endif diff --git a/include/rfl/boost_serialization/load.hpp b/include/rfl/boost_serialization/load.hpp new file mode 100644 index 00000000..e487e009 --- /dev/null +++ b/include/rfl/boost_serialization/load.hpp @@ -0,0 +1,22 @@ +#ifndef RFL_BOOST_SERIALIZATION_LOAD_HPP_ +#define RFL_BOOST_SERIALIZATION_LOAD_HPP_ + +#include "../Result.hpp" +#include "../io/load_bytes.hpp" +#include "read.hpp" + +namespace rfl { +namespace boost_serialization { + +template +Result load(const std::string& _fname) { + const auto read_bytes = [](const auto& _bytes) { + return read(_bytes); + }; + return rfl::io::load_bytes(_fname).and_then(read_bytes); +} + +} // namespace boost_serialization +} // namespace rfl + +#endif diff --git a/include/rfl/boost_serialization/read.hpp b/include/rfl/boost_serialization/read.hpp new file mode 100644 index 00000000..1773f82e --- /dev/null +++ b/include/rfl/boost_serialization/read.hpp @@ -0,0 +1,85 @@ +#ifndef RFL_BOOST_SERIALIZATION_READ_HPP_ +#define RFL_BOOST_SERIALIZATION_READ_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../concepts.hpp" +#include "../internal/wrap_in_rfl_array_t.hpp" +#include "Parser.hpp" +#include "Reader.hpp" + +namespace rfl::boost_serialization { + +namespace detail { + +template +auto read_from_archive(IArchive& _ar) { + using R = Reader; + auto r = R(); + auto var = typename R::InputVarType{&_ar}; + return Parser>::read(r, var); +} + +/// A read-only streambuf that wraps an existing memory buffer without copying. +class MemBuf : public std::streambuf { + public: + MemBuf(const char* _data, size_t _size) { + auto* p = const_cast(_data); + setg(p, p, p + _size); + } +}; + +} // namespace detail + +/// Reads from an existing Boost input archive. +template +auto read_from_archive(IArchive& _ar) { + return detail::read_from_archive(_ar); +} + +/// Parses an object from bytes using a binary archive. +template +Result> read( + const concepts::ByteLike auto* _bytes, const size_t _size) { + try { + detail::MemBuf buf(reinterpret_cast(_bytes), _size); + std::istream stream(&buf); + boost::archive::binary_iarchive ar( + stream, boost::archive::no_header | boost::archive::no_tracking); + return detail::read_from_archive( + ar); + } catch (std::exception& e) { + return error(e.what()); + } +} + +/// Parses an object from a byte container using a binary archive. +template +auto read(const concepts::ContiguousByteContainer auto& _bytes) { + return read(_bytes.data(), _bytes.size()); +} + +/// Parses an object from a stream using a binary archive. +template +auto read(std::istream& _stream) { + try { + boost::archive::binary_iarchive ar( + _stream, boost::archive::no_header | boost::archive::no_tracking); + return detail::read_from_archive( + ar); + } catch (std::exception& e) { + return Result>(error(e.what())); + } +} + +} // namespace rfl::boost_serialization + +#endif diff --git a/include/rfl/boost_serialization/save.hpp b/include/rfl/boost_serialization/save.hpp new file mode 100644 index 00000000..fd5229e5 --- /dev/null +++ b/include/rfl/boost_serialization/save.hpp @@ -0,0 +1,25 @@ +#ifndef RFL_BOOST_SERIALIZATION_SAVE_HPP_ +#define RFL_BOOST_SERIALIZATION_SAVE_HPP_ + +#include +#include +#include + +#include "../Result.hpp" +#include "../io/save_bytes.hpp" +#include "write.hpp" + +namespace rfl::boost_serialization { + +template +Result save(const std::string& _fname, const auto& _obj) { + const auto write_func = [](const auto& _obj, + std::ostream& _stream) -> std::ostream& { + return write(_obj, _stream); + }; + return rfl::io::save_bytes(_fname, _obj, write_func); +} + +} // namespace rfl::boost_serialization + +#endif diff --git a/include/rfl/boost_serialization/write.hpp b/include/rfl/boost_serialization/write.hpp new file mode 100644 index 00000000..88e8595c --- /dev/null +++ b/include/rfl/boost_serialization/write.hpp @@ -0,0 +1,57 @@ +#ifndef RFL_BOOST_SERIALIZATION_WRITE_HPP_ +#define RFL_BOOST_SERIALIZATION_WRITE_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../parsing/Parent.hpp" +#include "Parser.hpp" +#include "Writer.hpp" + +namespace rfl::boost_serialization { + +/// Writes into an existing Boost output archive. +template +void write(OArchive& _ar, const auto& _obj) { + using T = std::remove_cvref_t; + using W = Writer; + using ParentType = parsing::Parent; + auto w = W(&_ar); + Parser>::write( + w, _obj, typename ParentType::Root{}); +} + +/// Returns serialized bytes using a binary archive. +template +std::vector write(const auto& _obj) { + std::ostringstream stream; + { + boost::archive::binary_oarchive ar( + stream, boost::archive::no_header | boost::archive::no_tracking); + write(ar, _obj); + } + const auto str = stream.str(); + return std::vector(str.begin(), str.end()); +} + +/// Writes into an ostream using a binary archive. +template +std::ostream& write(const auto& _obj, std::ostream& _stream) { + boost::archive::binary_oarchive ar( + _stream, boost::archive::no_header | boost::archive::no_tracking); + write(ar, _obj); + return _stream; +} + +} // namespace rfl::boost_serialization + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6401a739..bedcec41 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,3 +65,7 @@ endif() if (REFLECTCPP_YAML) add_subdirectory(yaml) endif() + +if (REFLECTCPP_BOOST_SERIALIZATION) + add_subdirectory(boost_serialization) +endif() diff --git a/tests/boost_serialization/CMakeLists.txt b/tests/boost_serialization/CMakeLists.txt new file mode 100644 index 00000000..84c9cab2 --- /dev/null +++ b/tests/boost_serialization/CMakeLists.txt @@ -0,0 +1,20 @@ +project(reflect-cpp-boost-serialization-tests) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") + +add_executable( + reflect-cpp-boost-serialization-tests + ${SOURCES} +) +target_precompile_headers(reflect-cpp-boost-serialization-tests PRIVATE [["rfl.hpp"]] ) + + +target_link_libraries(reflect-cpp-boost-serialization-tests PRIVATE reflectcpp_tests_crt) + +add_custom_command(TARGET reflect-cpp-boost-serialization-tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy -t $ $ + COMMAND_EXPAND_LISTS +) + +find_package(GTest) +gtest_discover_tests(reflect-cpp-boost-serialization-tests) diff --git a/tests/boost_serialization/test_add_struct_name.cpp b/tests/boost_serialization/test_add_struct_name.cpp new file mode 100644 index 00000000..55a4cb4e --- /dev/null +++ b/tests/boost_serialization/test_add_struct_name.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_add_struct_name { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(boost_serialization, test_add_struct_name) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read>(homer); +} +} // namespace test_add_struct_name diff --git a/tests/boost_serialization/test_array.cpp b/tests/boost_serialization/test_array.cpp new file mode 100644 index 00000000..67e286db --- /dev/null +++ b/tests/boost_serialization/test_array.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +// Make sure things still compile when +// rfl.hpp is included after rfl/cbor.hpp. +#include + +#include "write_and_read.hpp" + +namespace test_array { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(boost_serialization, test_array) { + auto bart = Person{.first_name = "Bart"}; + + auto lisa = Person{.first_name = "Lisa"}; + + auto maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{ + .first_name = "Homer", + .children = std::make_unique>(std::array{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_array diff --git a/tests/boost_serialization/test_box.cpp b/tests/boost_serialization/test_box.cpp new file mode 100644 index 00000000..1afcdff5 --- /dev/null +++ b/tests/boost_serialization/test_box.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_box { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Box lesser; + rfl::Box greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(boost_serialization, test_box) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_box(DecisionTree{leaf1}), + .greater = rfl::make_box(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_box diff --git a/tests/boost_serialization/test_combined_processors.cpp b/tests/boost_serialization/test_combined_processors.cpp new file mode 100644 index 00000000..7f5e041f --- /dev/null +++ b/tests/boost_serialization/test_combined_processors.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_combined_processors { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(boost_serialization, test_combined_processors) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + using Processors = + rfl::Processors>; + + write_and_read(homer); +} +} // namespace test_combined_processors diff --git a/tests/boost_serialization/test_custom_class1.cpp b/tests/boost_serialization/test_custom_class1.cpp new file mode 100644 index 00000000..b07d3746 --- /dev/null +++ b/tests/boost_serialization/test_custom_class1.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class1 { + +struct Person { + struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; + }; + + using ReflectionType = PersonImpl; + + Person(const PersonImpl& _impl) : impl(_impl) {} + + Person(const std::string& _first_name) + : impl(PersonImpl{.first_name = _first_name}) {} + + const ReflectionType& reflection() const { return impl; }; + + private: + PersonImpl impl; +}; + +TEST(boost_serialization, test_custom_class1) { + const auto bart = Person("Bart"); + + write_and_read(bart); +} +} // namespace test_custom_class1 diff --git a/tests/boost_serialization/test_custom_class3.cpp b/tests/boost_serialization/test_custom_class3.cpp new file mode 100644 index 00000000..79510b45 --- /dev/null +++ b/tests/boost_serialization/test_custom_class3.cpp @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class3 { + +struct Person { + Person(const std::string& _first_name, const std::string& _last_name, + const int _age) + : first_name_(_first_name), last_name_(_last_name), age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + std::string last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + int age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = _p.last_name(), + .age = _p.age()}; + } + + Person to_class() const { return Person(first_name(), last_name(), age); } +}; +} // namespace test_custom_class3 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class3 { + +TEST(boost_serialization, test_custom_class3) { + const auto bart = Person("Bart", "Simpson", 10); + + write_and_read(bart); +} + +} // namespace test_custom_class3 diff --git a/tests/boost_serialization/test_custom_class4.cpp b/tests/boost_serialization/test_custom_class4.cpp new file mode 100644 index 00000000..4cb88bc7 --- /dev/null +++ b/tests/boost_serialization/test_custom_class4.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_custom_class4 { + +struct Person { + Person(const std::string& _first_name, + const rfl::Box& _last_name, int _age) + : first_name_(_first_name), + last_name_(rfl::make_box(*_last_name)), + age_(_age) {} + + const auto& first_name() const { return first_name_; } + + const auto& last_name() const { return last_name_; } + + auto age() const { return age_; } + + private: + std::string first_name_; + rfl::Box last_name_; + int age_; +}; + +struct PersonImpl { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; + + static PersonImpl from_class(const Person& _p) noexcept { + return PersonImpl{.first_name = _p.first_name(), + .last_name = rfl::make_box(*_p.last_name()), + .age = _p.age()}; + } +}; + +} // namespace test_custom_class4 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class4 { + +TEST(boost_serialization, test_custom_class4) { + const auto bart = test_custom_class4::Person( + "Bart", rfl::make_box("Simpson"), 10); + + write_and_read(bart); +} +} // namespace test_custom_class4 diff --git a/tests/boost_serialization/test_default_values.cpp b/tests/boost_serialization/test_default_values.cpp new file mode 100644 index 00000000..1a55dfd8 --- /dev/null +++ b/tests/boost_serialization/test_default_values.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_default_values { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::vector children; +}; + +TEST(boost_serialization, test_default_values) { + const auto bart = Person{.first_name = "Bart"}; + const auto lisa = Person{.first_name = "Lisa"}; + const auto maggie = Person{.first_name = "Maggie"}; + const auto homer = + Person{.first_name = "Homer", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_default_values diff --git a/tests/boost_serialization/test_deque.cpp b/tests/boost_serialization/test_deque.cpp new file mode 100644 index 00000000..55a3373a --- /dev/null +++ b/tests/boost_serialization/test_deque.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_deque { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(boost_serialization, test_default_values) { + auto children = std::make_unique>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_deque diff --git a/tests/boost_serialization/test_enum.cpp b/tests/boost_serialization/test_enum.cpp new file mode 100644 index 00000000..a26019ab --- /dev/null +++ b/tests/boost_serialization/test_enum.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_enum { + +enum class Color { red, green, blue, yellow }; + +struct Circle { + float radius; + Color color; +}; + +TEST(boost_serialization, test_enum) { + const auto circle = Circle{.radius = 2.0, .color = Color::green}; + + write_and_read(circle); +} + +} // namespace test_enum diff --git a/tests/boost_serialization/test_field_variant.cpp b/tests/boost_serialization/test_field_variant.cpp new file mode 100644 index 00000000..eb94c59f --- /dev/null +++ b/tests/boost_serialization/test_field_variant.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = rfl::Variant, + rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>>; + +TEST(boost_serialization, test_field_variant) { + const Shapes r = + rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5}); + + write_and_read(r); +} +} // namespace test_field_variant diff --git a/tests/boost_serialization/test_flag_enum.cpp b/tests/boost_serialization/test_flag_enum.cpp new file mode 100644 index 00000000..8d09cdd5 --- /dev/null +++ b/tests/boost_serialization/test_flag_enum.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(boost_serialization, test_flag_enum) { + const auto circle = + Circle{.radius = 2.0, .color = Color::blue | Color::orange}; + + write_and_read(circle); +} + +} // namespace test_flag_enum diff --git a/tests/boost_serialization/test_flag_enum_with_int.cpp b/tests/boost_serialization/test_flag_enum_with_int.cpp new file mode 100644 index 00000000..d4e887f0 --- /dev/null +++ b/tests/boost_serialization/test_flag_enum_with_int.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_flag_enum_with_int { + +enum class Color { + red = 256, + green = 512, + blue = 1024, + yellow = 2048, + orange = (256 | 2048) // red + yellow = orange +}; + +inline Color operator|(Color c1, Color c2) { + return static_cast(static_cast(c1) | static_cast(c2)); +} + +struct Circle { + float radius; + Color color; +}; + +TEST(boost_serialization, test_flag_enum_with_int) { + const auto circle = Circle{.radius = 2.0, .color = static_cast(10000)}; + + write_and_read(circle); +} + +} // namespace test_flag_enum_with_int diff --git a/tests/boost_serialization/test_flatten.cpp b/tests/boost_serialization/test_flatten.cpp new file mode 100644 index 00000000..d8a5d6ca --- /dev/null +++ b/tests/boost_serialization/test_flatten.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten { + +struct Person { + rfl::Field<"firstName", std::string> first_name; + rfl::Field<"lastName", rfl::Box> last_name; + rfl::Field<"age", int> age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Field<"employer", rfl::Box> employer; + rfl::Field<"salary", float> salary; +}; + +TEST(boost_serialization, test_flatten) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} +} // namespace test_flatten diff --git a/tests/boost_serialization/test_flatten_anonymous.cpp b/tests/boost_serialization/test_flatten_anonymous.cpp new file mode 100644 index 00000000..bfad4b0d --- /dev/null +++ b/tests/boost_serialization/test_flatten_anonymous.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_flatten_anonymous { + +struct Person { + std::string first_name; + rfl::Box last_name; + int age; +}; + +struct Employee { + rfl::Flatten person; + rfl::Box employer; + float salary; +}; + +TEST(boost_serialization, test_flatten_anonymous) { + const auto employee = Employee{ + .person = Person{.first_name = "Homer", + .last_name = rfl::make_box("Simpson"), + .age = 45}, + .employer = rfl::make_box("Mr. Burns"), + .salary = 60000.0}; + + write_and_read(employee); +} + +} // namespace test_flatten_anonymous diff --git a/tests/boost_serialization/test_forward_list.cpp b/tests/boost_serialization/test_forward_list.cpp new file mode 100644 index 00000000..801c5295 --- /dev/null +++ b/tests/boost_serialization/test_forward_list.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_forward_list { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(boost_serialization, test_forward_list) { + auto children = std::make_unique>(); + children->emplace_front(Person{.first_name = "Maggie"}); + children->emplace_front(Person{.first_name = "Lisa"}); + children->emplace_front(Person{.first_name = "Bart"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_forward_list diff --git a/tests/boost_serialization/test_literal.cpp b/tests/boost_serialization/test_literal.cpp new file mode 100644 index 00000000..519d85c4 --- /dev/null +++ b/tests/boost_serialization/test_literal.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal { + +using FirstName = rfl::Literal<"Homer", "Marge", "Bart", "Lisa", "Maggie">; +using LastName = rfl::Literal<"Simpson">; + +struct Person { + rfl::Rename<"firstName", FirstName> first_name; + rfl::Rename<"lastName", LastName> last_name; + std::vector children; +}; + +TEST(boost_serialization, test_literal) { + const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + + write_and_read(bart); +} +} // namespace test_literal diff --git a/tests/boost_serialization/test_literal_map.cpp b/tests/boost_serialization/test_literal_map.cpp new file mode 100644 index 00000000..c3335248 --- /dev/null +++ b/tests/boost_serialization/test_literal_map.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal_map { + +using FieldName = rfl::Literal<"firstName", "lastName">; + +TEST(boost_serialization, test_literal_map) { + std::map> homer; + homer.insert(std::make_pair(FieldName::make<"firstName">(), + std::make_unique("Homer"))); + homer.insert(std::make_pair(FieldName::make<"lastName">(), + std::make_unique("Simpson"))); + + write_and_read(homer); +} +} // namespace test_literal_map diff --git a/tests/boost_serialization/test_map.cpp b/tests/boost_serialization/test_map.cpp new file mode 100644 index 00000000..d8d463f4 --- /dev/null +++ b/tests/boost_serialization/test_map.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::map children; +}; + +TEST(boost_serialization, test_map) { + auto children = std::map(); + children.insert(std::make_pair("child1", Person{.first_name = "Bart"})); + children.insert(std::make_pair("child2", Person{.first_name = "Lisa"})); + children.insert(std::make_pair("child3", Person{.first_name = "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_map diff --git a/tests/boost_serialization/test_map_with_key_validation.cpp b/tests/boost_serialization/test_map_with_key_validation.cpp new file mode 100644 index 00000000..4a422b1c --- /dev/null +++ b/tests/boost_serialization/test_map_with_key_validation.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map_with_key_validation { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(boost_serialization, test_map_with_key_validation) { + auto children = std::make_unique>(); + + children->insert(std::make_pair("Bart", Person{.first_name = "Bart"})); + children->insert(std::make_pair("Lisa", Person{.first_name = "Lisa"})); + children->insert(std::make_pair("Maggie", Person{.first_name = "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_map_with_key_validation diff --git a/tests/boost_serialization/test_monster_example.cpp b/tests/boost_serialization/test_monster_example.cpp new file mode 100644 index 00000000..b3c412ee --- /dev/null +++ b/tests/boost_serialization/test_monster_example.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_monster_example { + +using Color = rfl::Literal<"Red", "Green", "Blue">; + +struct Weapon { + std::string name; + short damage; +}; + +using Equipment = rfl::Variant>; + +struct Vec3 { + float x; + float y; + float z; +}; + +struct Monster { + Vec3 pos; + short mana = 150; + short hp = 100; + std::string name; + bool friendly = false; + std::vector inventory; + Color color = Color::make<"Blue">(); + std::vector weapons; + Equipment equipped; + std::vector path; +}; + +TEST(boost_serialization, test_monster_example) { + const auto sword = Weapon{.name = "Sword", .damage = 3}; + const auto axe = Weapon{.name = "Axe", .damage = 5}; + + const auto weapons = std::vector({sword, axe}); + + const auto position = Vec3{1.0f, 2.0f, 3.0f}; + + const auto inventory = std::vector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + const auto orc = Monster{.pos = position, + .mana = 150, + .hp = 80, + .name = "MyMonster", + .inventory = inventory, + .color = Color::make<"Red">(), + .weapons = weapons, + .equipped = rfl::make_field<"weapon">(axe)}; + + write_and_read(orc); +} +} // namespace test_monster_example diff --git a/tests/boost_serialization/test_readme_example.cpp b/tests/boost_serialization/test_readme_example.cpp new file mode 100644 index 00000000..17377053 --- /dev/null +++ b/tests/boost_serialization/test_readme_example.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example { + +using Age = rfl::Validator, rfl::Maximum<130>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::string town = "Springfield"; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector child; +}; + +TEST(boost_serialization, test_readme_example) { + const auto bart = Person{.first_name = "Bart", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com"}; + + const auto lisa = Person{.first_name = "Lisa", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer = Person{.first_name = "Homer", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_readme_example diff --git a/tests/boost_serialization/test_readme_example2.cpp b/tests/boost_serialization/test_readme_example2.cpp new file mode 100644 index 00000000..83529ab2 --- /dev/null +++ b/tests/boost_serialization/test_readme_example2.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example2 { + +struct Person { + std::string first_name; + std::string last_name; + int age; +}; + +TEST(boost_serialization, test_readme_example2) { + const auto homer = + Person{.first_name = "Homer", .last_name = "Simpson", .age = 45}; + + write_and_read(homer); +} +} // namespace test_readme_example2 diff --git a/tests/boost_serialization/test_ref.cpp b/tests/boost_serialization/test_ref.cpp new file mode 100644 index 00000000..c019a181 --- /dev/null +++ b/tests/boost_serialization/test_ref.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_ref { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + rfl::Ref lesser; + rfl::Ref greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(boost_serialization, test_ref) { + const auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + const auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = rfl::make_ref(DecisionTree{leaf1}), + .greater = rfl::make_ref(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_ref diff --git a/tests/boost_serialization/test_save_load.cpp b/tests/boost_serialization/test_save_load.cpp new file mode 100644 index 00000000..e97c6e98 --- /dev/null +++ b/tests/boost_serialization/test_save_load.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include +#include +#include +#include + +namespace test_save_load { + +using Age = rfl::Validator, rfl::Maximum<130>>>; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + Age age; + rfl::Email email; + std::vector children; +}; + +TEST(boost_serialization, test_save_load) { + const auto bart = Person{.first_name = "Bart", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 10, + .email = "bart@simpson.com", + .children = std::vector()}; + + const auto lisa = Person{.first_name = "Lisa", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 8, + .email = "lisa@simpson.com"}; + + const auto maggie = Person{.first_name = "Maggie", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 0, + .email = "maggie@simpson.com"}; + + const auto homer1 = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .age = 45, + .email = "homer@simpson.com", + .children = std::vector({bart, lisa, maggie})}; + + rfl::boost_serialization::save("homer.boost_ser", homer1); + + const auto homer2 = + rfl::boost_serialization::load("homer.boost_ser").value(); + + const auto string1 = rfl::boost_serialization::write(homer1); + const auto string2 = rfl::boost_serialization::write(homer2); + + EXPECT_EQ(string1, string2); +} +} // namespace test_save_load diff --git a/tests/boost_serialization/test_set.cpp b/tests/boost_serialization/test_set.cpp new file mode 100644 index 00000000..f783bb2c --- /dev/null +++ b/tests/boost_serialization/test_set.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_set { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(boost_serialization, test_set) { + auto children = std::make_unique>( + std::set({"Bart", "Lisa", "Maggie"})); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_set diff --git a/tests/boost_serialization/test_size.cpp b/tests/boost_serialization/test_size.cpp new file mode 100644 index 00000000..3c91004c --- /dev/null +++ b/tests/boost_serialization/test_size.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_size { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + rfl::Validator, + rfl::Size, rfl::EqualTo<3>>>> + children; +}; + +TEST(boost_serialization, test_size) { + const auto bart = Person{ + .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_size diff --git a/tests/boost_serialization/test_snake_case_to_camel_case.cpp b/tests/boost_serialization/test_snake_case_to_camel_case.cpp new file mode 100644 index 00000000..20f108a7 --- /dev/null +++ b/tests/boost_serialization/test_snake_case_to_camel_case.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_snake_case_to_camel_case { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; + +TEST(boost_serialization, test_snake_case_to_camel_case) { + const auto bart = Person{ + .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_snake_case_to_camel_case diff --git a/tests/boost_serialization/test_snake_case_to_pascal_case.cpp b/tests/boost_serialization/test_snake_case_to_pascal_case.cpp new file mode 100644 index 00000000..df0a8123 --- /dev/null +++ b/tests/boost_serialization/test_snake_case_to_pascal_case.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_snake_case_to_pascal_case { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; + +TEST(boost_serialization, test_snake_case_to_pascal_case) { + const auto bart = Person{ + .first_name = "Bart", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto lisa = Person{ + .first_name = "Lisa", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto maggie = Person{ + .first_name = "Maggie", .last_name = "Simpson", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_snake_case_to_pascal_case diff --git a/tests/boost_serialization/test_string_map.cpp b/tests/boost_serialization/test_string_map.cpp new file mode 100644 index 00000000..14887045 --- /dev/null +++ b/tests/boost_serialization/test_string_map.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_string_map { +TEST(boost_serialization, test_string_map) { + std::map> homer; + homer.insert( + std::make_pair("firstName", std::make_unique("Homer"))); + homer.insert( + std::make_pair("lastName", std::make_unique("Simpson"))); + + write_and_read(homer); +} +} // namespace test_string_map diff --git a/tests/boost_serialization/test_tagged_union.cpp b/tests/boost_serialization/test_tagged_union.cpp new file mode 100644 index 00000000..956292bf --- /dev/null +++ b/tests/boost_serialization/test_tagged_union.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = rfl::TaggedUnion<"shape", Circle, Square, Rectangle>; + +TEST(boost_serialization, test_tagged_union) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union diff --git a/tests/boost_serialization/test_timestamp.cpp b/tests/boost_serialization/test_timestamp.cpp new file mode 100644 index 00000000..dbeb95bf --- /dev/null +++ b/tests/boost_serialization/test_timestamp.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_timestamp { + +using TS = rfl::Timestamp<"%Y-%m-%d">; + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + TS birthday; +}; + +TEST(boost_serialization, test_timestamp) { + const auto result = TS::from_string("nonsense"); + + if (result) { + std::cout << "Failed: Expected an error, but got none." << std::endl; + return; + } + + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + + write_and_read(bart); +} +} // namespace test_timestamp diff --git a/tests/boost_serialization/test_unique_ptr.cpp b/tests/boost_serialization/test_unique_ptr.cpp new file mode 100644 index 00000000..186be261 --- /dev/null +++ b/tests/boost_serialization/test_unique_ptr.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children; +}; + +TEST(boost_serialization, test_unique_ptr) { + auto children = std::make_unique>(); + children->emplace_back(Person{.first_name = "Bart"}); + children->emplace_back(Person{.first_name = "Lisa"}); + children->emplace_back(Person{.first_name = "Maggie"}); + + const auto homer = + Person{.first_name = "Homer", .children = std::move(children)}; + + write_and_read(homer); +} +} // namespace test_unique_ptr diff --git a/tests/boost_serialization/test_unique_ptr2.cpp b/tests/boost_serialization/test_unique_ptr2.cpp new file mode 100644 index 00000000..f4610757 --- /dev/null +++ b/tests/boost_serialization/test_unique_ptr2.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr2 { + +struct DecisionTree { + struct Leaf { + using Tag = rfl::Literal<"Leaf">; + double value; + }; + + struct Node { + using Tag = rfl::Literal<"Node">; + rfl::Rename<"criticalValue", double> critical_value; + std::unique_ptr lesser; + std::unique_ptr greater; + }; + + using LeafOrNode = rfl::TaggedUnion<"type", Leaf, Node>; + + rfl::Field<"leafOrNode", LeafOrNode> leaf_or_node; +}; + +TEST(boost_serialization, test_unique_ptr2) { + auto leaf1 = DecisionTree::Leaf{.value = 3.0}; + + auto leaf2 = DecisionTree::Leaf{.value = 5.0}; + + auto node = DecisionTree::Node{ + .critical_value = 10.0, + .lesser = std::make_unique(DecisionTree{leaf1}), + .greater = std::make_unique(DecisionTree{leaf2})}; + + const DecisionTree tree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_unique_ptr2 diff --git a/tests/boost_serialization/test_variant.cpp b/tests/boost_serialization/test_variant.cpp new file mode 100644 index 00000000..4b87ecb7 --- /dev/null +++ b/tests/boost_serialization/test_variant.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = std::variant>; + +TEST(boost_serialization, test_variant) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + + write_and_read(r); +} +} // namespace test_variant diff --git a/tests/boost_serialization/write_and_read.hpp b/tests/boost_serialization/write_and_read.hpp new file mode 100644 index 00000000..1714dbc5 --- /dev/null +++ b/tests/boost_serialization/write_and_read.hpp @@ -0,0 +1,33 @@ +#ifndef WRITE_AND_READ_ +#define WRITE_AND_READ_ + +#include + +#include +#include +#include + +template +void write_and_read(const auto& _struct) { + using T = std::remove_cvref_t; + const auto serialized1 = rfl::boost_serialization::write(_struct); + const auto res = rfl::boost_serialization::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + const auto serialized2 = rfl::boost_serialization::write(res.value()); + EXPECT_EQ(serialized1, serialized2); +} + +template +void write_and_read_with_json(const auto& _struct) { + using T = std::remove_cvref_t; + const auto serialized1 = rfl::boost_serialization::write(_struct); + const auto res = rfl::boost_serialization::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + const auto serialized2 = rfl::boost_serialization::write(res.value()); + EXPECT_EQ(serialized1, serialized2); + EXPECT_EQ(rfl::json::write(_struct), + rfl::json::write(res.value())); +} +#endif diff --git a/vcpkg.json b/vcpkg.json index 64404444..02257f41 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -52,6 +52,14 @@ } ] }, + "boost-serialization": { + "description": "Enable Boost.Serialization support", + "dependencies": [ + { + "name": "boost-serialization" + } + ] + }, "capnproto": { "description": "Enable Cap'n Proto support", "dependencies": [