diff --git a/.gitignore b/.gitignore index b4e87927..cc3a2926 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ !package.xml *.yml *.yaml +*.yas !vcpkg.json !.github/**/*.yml diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d684a8..5e5b94ef 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_YAS "Enable yas support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_BOOST_SERIALIZATION "Enable Boost.Serialization support" ${REFLECTCPP_ALL_FORMATS}) option(REFLECTCPP_BUILD_BENCHMARKS "Build benchmarks" OFF) @@ -58,6 +59,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_YAS ON CACHE BOOL "" FORCE) set(REFLECTCPP_BOOST_SERIALIZATION ON CACHE BOOL "" FORCE) endif() @@ -79,7 +81,8 @@ if ( REFLECTCPP_XML OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR - REFLECTCPP_YAML + REFLECTCPP_YAML OR + REFLECTCPP_YAS ) # enable vcpkg per default if features other than JSON are required set(REFLECTCPP_USE_VCPKG_DEFAULT ON) @@ -166,6 +169,10 @@ if (REFLECTCPP_USE_VCPKG) list(APPEND VCPKG_MANIFEST_FEATURES "yaml") endif() + if (REFLECTCPP_YAS OR REFLECTCPP_CHECK_HEADERS) + list(APPEND VCPKG_MANIFEST_FEATURES "yas") + endif() + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file") endif () @@ -438,6 +445,14 @@ if (REFLECTCPP_BOOST_SERIALIZATION OR REFLECTCPP_CHECK_HEADERS) target_link_libraries(reflectcpp PUBLIC Boost::serialization) endif () +if (REFLECTCPP_YAS OR REFLECTCPP_CHECK_HEADERS) + list(APPEND REFLECT_CPP_SOURCES + src/reflectcpp_yas.cpp + ) + find_path(YAS_INCLUDE_DIRS "yas/abseil_types.hpp") + target_include_directories(reflectcpp PRIVATE ${YAS_INCLUDE_DIRS}) +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 6ada9d1b..921bed8c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ The following table lists the serialization formats currently supported by refle | UBJSON | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format | | XML | [pugixml](https://github.com/zeux/pugixml) | >= 1.14 | MIT | Textual format used in many legacy projects | | YAML | [yaml-cpp](https://github.com/jbeder/yaml-cpp) | >= 0.8.0 | MIT | Textual format with an emphasis on readability | +| yas | [yas](https://github.com/niXman/yas) | >= 7.1.0 | BSL 1.0 | Very fast and compact serialization library | Support for more serialization formats is in development. Refer to the [issues](https://github.com/getml/reflect-cpp/issues) for details. @@ -163,6 +164,7 @@ rfl::msgpack::write(homer); rfl::toml::write(homer); rfl::ubjson::write(homer); rfl::xml::write(homer); +rfl::yas::write(homer); rfl::avro::read(avro_bytes); rfl::bson::read(bson_bytes); @@ -174,6 +176,7 @@ rfl::msgpack::read(msgpack_bytes); rfl::toml::read(toml_string); rfl::ubjson::read(ubjson_bytes); rfl::xml::read(xml_string); +rfl::yas::read(yas_bytes); ``` ### More Comprehensive Example diff --git a/benchmarks/README.md b/benchmarks/README.md index ce0a04bf..c0c1e7a7 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -50,7 +50,7 @@ In summary, it is fair to say that reflect-cpp is among the fastest JSON librari ### How the different formats supported by reflect-cpp compare to each other -- flexbuffers and msgpack are the fastest two formats supported by reflect-cpp. They are particularly fast on datasets with a lot of numerical data, such as *canada*. flexbuffers is usually the fastest at reading data and msgpack is the fastest at writing. +- flexbuffers, msgpack and yas are the fastest three formats supported by reflect-cpp. They are particularly fast on datasets with a lot of numerical data, such as *canada*. flexbuffers is usually the fastest at reading data and msgpack is the fastest at writing. - JSON is surprisingly fast. This is because reflect-cpp's JSON parser is built on top of yyjson, arguably the fastest JSON library for C or C++. - The performance of CBOR is disappointing. We have tried swapping out the underlying library, TinyCBOR, for libcbor, but the results are similar. - TOML and YAML are very slow when compared to the other formats. To be fair, these formats emphasize readability rather than speed. diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index 853b98c6..ade61e4f 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace canada_read { @@ -247,6 +248,17 @@ static void BM_canada_read_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_yaml); +static void BM_canada_read_reflect_cpp_yas(benchmark::State &state) { + const auto data = rfl::yas::write(load_data()); + for (auto _ : state) { + const auto res = rfl::yas::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace canada_read diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 87254f14..097dc688 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace canada_write { @@ -231,6 +232,17 @@ static void BM_canada_write_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_yaml); +static void BM_canada_write_reflect_cpp_yas(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::yas::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace canada_write diff --git a/benchmarks/all/licenses_read.cpp b/benchmarks/all/licenses_read.cpp index b12bd5d1..66fe4ce2 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace licenses_read { @@ -270,6 +271,17 @@ static void BM_licenses_read_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_yaml); +static void BM_licenses_read_reflect_cpp_yas(benchmark::State &state) { + const auto data = rfl::yas::write(load_data()); + for (auto _ : state) { + const auto res = rfl::yas::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace licenses_read diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index fbfca915..7819e9bc 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace licenses_write { @@ -270,6 +271,17 @@ static void BM_licenses_write_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_yaml); +static void BM_licenses_write_reflect_cpp_yas(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::yas::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace licenses_write diff --git a/benchmarks/all/person_read.cpp b/benchmarks/all/person_read.cpp index 24a995be..9d5a9b24 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace person_read { @@ -248,6 +249,17 @@ static void BM_person_read_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_yaml); +static void BM_person_read_reflect_cpp_yas(benchmark::State &state) { + const auto data = rfl::yas::write(load_data()); + for (auto _ : state) { + const auto res = rfl::yas::read(data); + if (!res) { + std::cout << res.error().what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace person_read diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index eb946c4e..3fb86403 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace person_write { @@ -249,6 +250,17 @@ static void BM_person_write_reflect_cpp_yaml(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_yaml); +static void BM_person_write_reflect_cpp_yas(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::yas::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_yas); + // ---------------------------------------------------------------------------- } // namespace person_write diff --git a/docs/supported_formats/yas.md b/docs/supported_formats/yas.md new file mode 100644 index 00000000..cbe44128 --- /dev/null +++ b/docs/supported_formats/yas.md @@ -0,0 +1,55 @@ +# yas + +For [yas](https://github.com/niXman/yas) support, you must also include the header `` and link to the yas library. +Furthermore, when compiling reflect-cpp, you need to pass `-DREFLECTCPP_YAS=ON` to cmake. If you are using vcpkg, there should be an appropriate feature that will abstract this away for you. + +yas is a very fast and compact serialization library. reflect-cpp implements it as a schemaful binary format. + +Note that even though yas is probably the fastest format supported by reflect-cpp, that speed comes at the cost of lack of backwards compatibility. If you change the structure of your data, you will not be able to read old files anymore. If you need backwards compatibility, consider using a different format like flexbuffers or msgpack, which are also supported by reflect-cpp. Both of these are very fast and compact as well, but they are schema-less and therefore more flexible when it comes to changing the structure of your data. + +## 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 to a bytes vector like this: + +```cpp +const auto person = Person{...}; +const std::vector bytes = rfl::yas::write(person); +``` + +You can parse bytes like this: + +```cpp +const rfl::Result result = rfl::yas::read(bytes); +``` + +## Loading and saving + +You can also load and save to disc using a very similar syntax: + +```cpp +const rfl::Result result = rfl::yas::load("/path/to/file.bin"); + +const auto person = Person{...}; +rfl::yas::save("/path/to/file.bin", person); +``` + +## Reading from and writing into streams + +You can also read from and write into any `std::ostream` respectively. + +```cpp +const auto person = Person{...}; +rfl::yas::write(person, my_ostream); +``` + diff --git a/include/rfl/yas.hpp b/include/rfl/yas.hpp new file mode 100644 index 00000000..39eb67b7 --- /dev/null +++ b/include/rfl/yas.hpp @@ -0,0 +1,13 @@ +#ifndef RFL_YAS_HPP_ +#define RFL_YAS_HPP_ + +#include "../rfl.hpp" +#include "yas/Parser.hpp" +#include "yas/Reader.hpp" +#include "yas/Writer.hpp" +#include "yas/load.hpp" +#include "yas/read.hpp" +#include "yas/save.hpp" +#include "yas/write.hpp" + +#endif diff --git a/include/rfl/yas/Parser.hpp b/include/rfl/yas/Parser.hpp new file mode 100644 index 00000000..9403b032 --- /dev/null +++ b/include/rfl/yas/Parser.hpp @@ -0,0 +1,58 @@ +#ifndef RFL_YAS_PARSER_HPP_ +#define RFL_YAS_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 { + +/// Yas requires us to explicitly set the number of fields in advance. +/// Because of that, we require all of the fields and then set them to nullptr, +/// if necessary. +template + requires AreReaderAndWriter> +struct Parser, + ProcessorsType> + : public NamedTupleParser< + yas::Reader, yas::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> {}; + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> + : public TupleParser> {}; + +template + requires AreReaderAndWriter> +struct Parser, ProcessorsType> + : public TupleParser> {}; + +} // namespace parsing +} // namespace rfl + +namespace rfl { +namespace yas { + +template +using Parser = parsing::Parser; + +} +} // namespace rfl + +#endif diff --git a/include/rfl/yas/Reader.hpp b/include/rfl/yas/Reader.hpp new file mode 100644 index 00000000..5979a015 --- /dev/null +++ b/include/rfl/yas/Reader.hpp @@ -0,0 +1,154 @@ +#ifndef RFL_YAS_READER_HPP_ +#define RFL_YAS_READER_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" +#include "../parsing/schemaful/IsSchemafulReader.hpp" + +namespace rfl::yas { + +struct Reader { + using IArchive = ::yas::binary_iarchive<::yas::mem_istream>; + + struct InputVarType { + IArchive* ar; + }; + + struct InputArrayType { + IArchive* ar; + }; + + struct InputObjectType { + IArchive* ar; + }; + + struct InputMapType { + IArchive* ar; + }; + + struct InputUnionType { + IArchive* ar; + }; + + 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 (internal::is_literal_v) { + std::string str; + (*_var.ar) & str; + return std::remove_cvref_t::from_string(str); + } else { + T value; + (*_var.ar) & value; + return value; + } + } catch (std::exception& e) { + return error(std::string("yas read error: ") + e.what()); + } + } + + rfl::Result to_array( + const InputVarType& _var) const noexcept { + return InputArrayType{_var.ar}; + } + + rfl::Result to_object( + const InputVarType& _var) const noexcept { + return InputObjectType{_var.ar}; + } + + rfl::Result to_map(const InputVarType& _var) const noexcept { + return InputMapType{_var.ar}; + } + + 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 { + try { + std::size_t size; + (*_arr.ar) & size; + for (std::size_t i = 0; i < size; ++i) { + const auto err = _array_reader.read(InputVarType{_arr.ar}); + if (err) { + return err; + } + } + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("yas array read error: ") + e.what()); + } + } + + template + std::optional read_map(const MapReader& _map_reader, + const InputMapType& _map) const noexcept { + try { + std::size_t size; + (*_map.ar) & size; + for (std::size_t i = 0; i < size; ++i) { + std::string key; + (*_map.ar) & key; + _map_reader.read(std::string_view(key), InputVarType{_map.ar}); + } + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("yas map read error: ") + e.what()); + } + } + + template + std::optional read_object(const ObjectReader& _object_reader, + const InputObjectType& _obj) const noexcept { + try { + for (std::size_t i = 0; i < ObjectReader::size(); ++i) { + _object_reader.read(i, InputVarType{_obj.ar}); + } + return std::nullopt; + } catch (std::exception& e) { + return Error(std::string("yas object read error: ") + e.what()); + } + } + + template + rfl::Result read_union(const InputUnionType& _union) const noexcept { + try { + std::size_t index; + (*_union.ar) & index; + return UnionReader::read(*this, index, InputVarType{_union.ar}); + } catch (std::exception& e) { + return error(std::string("yas union read error: ") + e.what()); + } + } + + template + rfl::Result use_custom_constructor( + const InputVarType& /*_var*/) const noexcept { + return rfl::error("Custom constructors are not supported."); + } +}; + +static_assert(parsing::schemaful::IsSchemafulReader); + +} // namespace rfl::yas + +#endif diff --git a/include/rfl/yas/Writer.hpp b/include/rfl/yas/Writer.hpp new file mode 100644 index 00000000..44bc1317 --- /dev/null +++ b/include/rfl/yas/Writer.hpp @@ -0,0 +1,224 @@ +#ifndef RFL_YAS_WRITER_HPP_ +#define RFL_YAS_WRITER_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../Bytestring.hpp" +#include "../Result.hpp" +#include "../Vectorstring.hpp" +#include "../always_false.hpp" +#include "../internal/is_literal.hpp" +#include "../parsing/schemaful/IsSchemafulWriter.hpp" + +namespace rfl::yas { + +struct Writer { + using OArchive = ::yas::binary_oarchive<::yas::mem_ostream>; + + struct OutputArrayType { + OArchive* ar; + }; + + struct OutputMapType { + OArchive* ar; + }; + + struct OutputObjectType { + OArchive* ar; + }; + + struct OutputUnionType { + OArchive* ar; + }; + + struct OutputVarType { + OArchive* ar; + }; + + Writer(OArchive* _ar) : ar_(_ar) {} + + template + static constexpr bool has_custom_constructor = false; + + OutputArrayType array_as_root(const size_t _size) const noexcept { + (*ar_) & _size; + return OutputArrayType{ar_}; + } + + OutputMapType map_as_root(const size_t _size) const noexcept { + (*ar_) & _size; + return OutputMapType{ar_}; + } + + OutputObjectType object_as_root(const size_t /*_size*/) const noexcept { + return OutputObjectType{ar_}; + } + + OutputUnionType union_as_root() const noexcept { + return OutputUnionType{ar_}; + } + + OutputVarType null_as_root() const noexcept { return OutputVarType{ar_}; } + + template + OutputVarType value_as_root(const T& _var) const noexcept { + add_value(_var); + return OutputVarType{ar_}; + } + + OutputArrayType add_array_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept { + (*_parent->ar) & _size; + return OutputArrayType{_parent->ar}; + } + + OutputArrayType add_array_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputArrayType add_array_to_object( + const std::string_view& /*_name*/, const size_t _size, + OutputObjectType* _parent) const noexcept { + (*_parent->ar) & _size; + return OutputArrayType{_parent->ar}; + } + + OutputArrayType add_array_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputMapType add_map_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept { + (*_parent->ar) & _size; + return OutputMapType{_parent->ar}; + } + + OutputMapType add_map_to_object(const std::string_view& /*_name*/, + const size_t _size, + OutputObjectType* _parent) const noexcept { + (*_parent->ar) & _size; + return OutputMapType{_parent->ar}; + } + + OutputMapType add_map_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputMapType add_map_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputObjectType add_object_to_array( + const size_t /*_size*/, OutputArrayType* _parent) const noexcept { + return OutputObjectType{_parent->ar}; + } + + OutputObjectType add_object_to_map(const std::string_view& _name, + const size_t _size, + OutputMapType* _parent) const noexcept; + + OutputObjectType add_object_to_object( + const std::string_view& /*_name*/, const size_t /*_size*/, + OutputObjectType* _parent) const noexcept { + return OutputObjectType{_parent->ar}; + } + + OutputObjectType add_object_to_union(const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept; + + OutputUnionType add_union_to_array(OutputArrayType* _parent) const noexcept { + return OutputUnionType{_parent->ar}; + } + + OutputUnionType add_union_to_map(const std::string_view& _name, + OutputMapType* _parent) const noexcept; + + OutputUnionType add_union_to_object( + const std::string_view& /*_name*/, + OutputObjectType* _parent) const noexcept { + return OutputUnionType{_parent->ar}; + } + + OutputUnionType add_union_to_union(const size_t _index, + OutputUnionType* _parent) const noexcept; + + template + OutputVarType add_value_to_array(const T& _var, OutputArrayType*) const { + add_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_map(const std::string_view& _name, const T& _var, + OutputMapType* _parent) const { + add_string_view(_name); + add_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_object(const std::string_view& _name, + const T& _var, OutputObjectType*) const { + add_value(_var); + return OutputVarType{}; + } + + template + OutputVarType add_value_to_union(const size_t _index, const T& _var, + OutputUnionType* _parent) const { + (*ar_) & _index; + add_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 { + 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 noexcept; + + void end_array(OutputArrayType* /*_arr*/) const noexcept {} + + void end_map(OutputMapType* /*_map*/) const noexcept {} + + void end_object(OutputObjectType* /*_obj*/) const noexcept {} + + void add_string_view(const std::string_view& _str) const { + ar_->write_seq_size(_str.length()); + ar_->write(_str.data(), _str.length()); + } + + template + void add_value(const T& _var) const { + if constexpr (internal::is_literal_v) { + (*ar_) & _var.str(); + } else { + (*ar_) & _var; + } + } + + OArchive* ar_; +}; + +static_assert(parsing::schemaful::IsSchemafulWriter); + +} // namespace rfl::yas + +#endif diff --git a/include/rfl/yas/load.hpp b/include/rfl/yas/load.hpp new file mode 100644 index 00000000..5771f882 --- /dev/null +++ b/include/rfl/yas/load.hpp @@ -0,0 +1,22 @@ +#ifndef RFL_YAS_LOAD_HPP_ +#define RFL_YAS_LOAD_HPP_ + +#include + +#include "../Result.hpp" +#include "../io/load_bytes.hpp" +#include "read.hpp" + +namespace rfl::yas { + +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 rfl::yas + +#endif diff --git a/include/rfl/yas/read.hpp b/include/rfl/yas/read.hpp new file mode 100644 index 00000000..46f98747 --- /dev/null +++ b/include/rfl/yas/read.hpp @@ -0,0 +1,50 @@ +#ifndef RFL_YAS_READ_HPP_ +#define RFL_YAS_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::yas { + +using IArchive = Reader::IArchive; + +/// Reads from an existing yas input archive. +template +auto read_from_archive(IArchive& _ar) { + auto r = Reader(); + auto var = Reader::InputVarType{&_ar}; + return Parser>::read(r, var); +} + +/// Parses an object from bytes using a binary archive. +template +Result> read( + const concepts::ByteLike auto* _bytes, const size_t _size) { + try { + ::yas::mem_istream is(_bytes, _size); + ::yas::binary_iarchive<::yas::mem_istream> ar(is); + return read_from_archive(ar); + } catch (std::exception& e) { + return error(std::string("yas read 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()); +} + +} // namespace rfl::yas + +#endif diff --git a/include/rfl/yas/save.hpp b/include/rfl/yas/save.hpp new file mode 100644 index 00000000..93c1e1e5 --- /dev/null +++ b/include/rfl/yas/save.hpp @@ -0,0 +1,23 @@ +#ifndef RFL_YAS_SAVE_HPP_ +#define RFL_YAS_SAVE_HPP_ + +#include +#include + +#include "../Result.hpp" +#include "../io/save_bytes.hpp" +#include "write.hpp" + +namespace rfl::yas { + +template +Result save(const std::string& _fname, const auto& _obj) { + const auto write_func = [](const auto& _obj, auto& _stream) -> auto& { + return write(_obj, _stream); + }; + return rfl::io::save_bytes(_fname, _obj, write_func); +} + +} // namespace rfl::yas + +#endif diff --git a/include/rfl/yas/write.hpp b/include/rfl/yas/write.hpp new file mode 100644 index 00000000..f233fbc8 --- /dev/null +++ b/include/rfl/yas/write.hpp @@ -0,0 +1,53 @@ +#ifndef RFL_YAS_WRITE_HPP_ +#define RFL_YAS_WRITE_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../Result.hpp" +#include "../parsing/Parent.hpp" +#include "Parser.hpp" +#include "Writer.hpp" + +namespace rfl::yas { + +using OArchive = Writer::OArchive; + +/// Writes an object to a yas OutputArchive. +template +void write_to_archive(OArchive& _archive, const auto& _obj) { + using T = std::remove_cvref_t; + using ParentType = parsing::Parent; + auto w = Writer(&_archive); + Parser>::write(w, _obj, typename ParentType::Root{}); +} + +/// Returns yas binary bytes. +template +std::vector write(const auto& _obj) { + ::yas::mem_ostream os; + ::yas::binary_oarchive<::yas::mem_ostream> ar(os); + write_to_archive(ar, _obj); + auto buf = os.get_intrusive_buffer(); + return std::vector(buf.data, buf.data + buf.size); +} + +/// Writes yas binary format into an ostream. +template +std::ostream& write(const auto& _obj, std::ostream& _stream) { + ::yas::mem_ostream os; + ::yas::binary_oarchive<::yas::mem_ostream> ar(os); + write_to_archive(ar, _obj); + auto buf = os.get_intrusive_buffer(); + _stream.write(buf.data, buf.size); + return _stream; +} + +} // namespace rfl::yas + +#endif diff --git a/mkdocs.yaml b/mkdocs.yaml index 00375ba2..cc7bdbf4 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -6,7 +6,7 @@ site_url: https://rfl.getml.com/ site_author: Code17 GmbH site_description: >- A C++20 library for fast serialization, deserialization and validation using reflection. - Supports JSON, AVRO, BSON, Cap'n Proto, CBOR, Cereal, CSV, flexbuffers, msgpack, parquet, TOML, UBJSON, XML, YAML + Supports JSON, AVRO, BSON, Cap'n Proto, CBOR, Cereal, CSV, flexbuffers, msgpack, parquet, TOML, UBJSON, XML, YAML, yas theme: name: "material" @@ -106,6 +106,7 @@ nav: - UBJSON: supported_formats/ubjson.md - XML: supported_formats/xml.md - YAML: supported_formats/yaml.md + - yas: supported_formats/yas.md - Custom Format: supported_formats/supporting_your_own_format.md # - Reflective Programming: ./reflective_programming.md - Installation: install.md diff --git a/src/reflectcpp_yas.cpp b/src/reflectcpp_yas.cpp new file mode 100644 index 00000000..8b0a072e --- /dev/null +++ b/src/reflectcpp_yas.cpp @@ -0,0 +1,32 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// This file include all other source files, so that the user of the library +// don't need to add multiple source files into their build. +// Also, this speeds up compile time, compared to multiple separate .cpp files +// compilation. + +#include "rfl/yas/Writer.cpp" diff --git a/src/rfl/yas/Writer.cpp b/src/rfl/yas/Writer.cpp new file mode 100644 index 00000000..4bebf6e4 --- /dev/null +++ b/src/rfl/yas/Writer.cpp @@ -0,0 +1,95 @@ +/* + +MIT License + +Copyright (c) 2023-2024 Code17 GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "rfl/yas/Writer.hpp" + +namespace rfl::yas { + +Writer::OutputArrayType Writer::add_array_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const noexcept { + add_string_view(_name); + (*_parent->ar) & _size; + return OutputArrayType{_parent->ar}; +} + +Writer::OutputArrayType Writer::add_array_to_union( + const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept { + (*_parent->ar) & _index; + (*_parent->ar) & _size; + return OutputArrayType{_parent->ar}; +} + +Writer::OutputMapType Writer::add_map_to_map( + const std::string_view& _name, const size_t _size, + OutputMapType* _parent) const noexcept { + add_string_view(_name); + (*_parent->ar) & _size; + return OutputMapType{_parent->ar}; +} + +Writer::OutputMapType Writer::add_map_to_union( + const size_t _index, const size_t _size, + OutputUnionType* _parent) const noexcept { + (*_parent->ar) & _index; + (*_parent->ar) & _size; + return OutputMapType{_parent->ar}; +} + +Writer::OutputObjectType Writer::add_object_to_map( + const std::string_view& _name, const size_t, + OutputMapType* _parent) const noexcept { + add_string_view(_name); + return OutputObjectType{_parent->ar}; +} + +Writer::OutputObjectType Writer::add_object_to_union( + const size_t _index, const size_t, + OutputUnionType* _parent) const noexcept { + (*_parent->ar) & _index; + return OutputObjectType{_parent->ar}; +} + +Writer::OutputUnionType Writer::add_union_to_map( + const std::string_view& _name, OutputMapType* _parent) const noexcept { + add_string_view(_name); + return OutputUnionType{_parent->ar}; +} + +Writer::OutputUnionType Writer::add_union_to_union( + const size_t _index, OutputUnionType* _parent) const noexcept { + (*_parent->ar) & _index; + return OutputUnionType{_parent->ar}; +} + +Writer::OutputVarType Writer::add_null_to_union( + const size_t _index, OutputUnionType* _parent) const noexcept { + (*_parent->ar) & _index; + return OutputVarType{}; +} + +} // namespace rfl::yas diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bedcec41..501264b0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -66,6 +66,10 @@ if (REFLECTCPP_YAML) add_subdirectory(yaml) endif() +if (REFLECTCPP_YAS) + add_subdirectory(yas) +endif() + if (REFLECTCPP_BOOST_SERIALIZATION) add_subdirectory(boost_serialization) endif() diff --git a/tests/yas/CMakeLists.txt b/tests/yas/CMakeLists.txt new file mode 100644 index 00000000..63f10fad --- /dev/null +++ b/tests/yas/CMakeLists.txt @@ -0,0 +1,19 @@ +project(reflect-cpp-yas-tests) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") + +add_executable( + reflect-cpp-yas-tests + ${SOURCES} +) +target_precompile_headers(reflect-cpp-yas-tests PRIVATE [["rfl.hpp"]] ) + +target_link_libraries(reflect-cpp-yas-tests PRIVATE reflectcpp_tests_crt) + +add_custom_command(TARGET reflect-cpp-yas-tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy -t $ $ + COMMAND_EXPAND_LISTS +) + +find_package(GTest) +gtest_discover_tests(reflect-cpp-yas-tests) diff --git a/tests/yas/test_add_struct_name.cpp b/tests/yas/test_add_struct_name.cpp new file mode 100644 index 00000000..e96e996d --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_array.cpp b/tests/yas/test_array.cpp new file mode 100644 index 00000000..3d2fc779 --- /dev/null +++ b/tests/yas/test_array.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#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(yas, 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/yas/test_box.cpp b/tests/yas/test_box.cpp new file mode 100644 index 00000000..be52e9f6 --- /dev/null +++ b/tests/yas/test_box.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#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(yas, 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::LeafOrNode(leaf1)), + .greater = rfl::make_box(DecisionTree::LeafOrNode(leaf2))}; + + const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_box diff --git a/tests/yas/test_bytestring.cpp b/tests/yas/test_bytestring.cpp new file mode 100644 index 00000000..07ee527a --- /dev/null +++ b/tests/yas/test_bytestring.cpp @@ -0,0 +1,18 @@ +#include + +#include "write_and_read.hpp" + +namespace test_bytestring { + +struct TestStruct { + rfl::Bytestring bytestring; +}; + +TEST(yas, test_bytestring) { + const auto test = + TestStruct{.bytestring = rfl::Bytestring({std::byte{13}, std::byte{14}, + std::byte{15}, std::byte{16}})}; + + write_and_read(test); +} +} // namespace test_bytestring diff --git a/tests/yas/test_custom_class1.cpp b/tests/yas/test_custom_class1.cpp new file mode 100644 index 00000000..99723a9a --- /dev/null +++ b/tests/yas/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(yas, test_custom_class1) { + const auto bart = Person("Bart"); + + write_and_read(bart); +} +} // namespace test_custom_class1 diff --git a/tests/yas/test_custom_class3.cpp b/tests/yas/test_custom_class3.cpp new file mode 100644 index 00000000..074239f8 --- /dev/null +++ b/tests/yas/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(yas, test_custom_class3) { + const auto bart = Person("Bart", "Simpson", 10); + + write_and_read(bart); +} + +} // namespace test_custom_class3 diff --git a/tests/yas/test_custom_class4.cpp b/tests/yas/test_custom_class4.cpp new file mode 100644 index 00000000..60d59537 --- /dev/null +++ b/tests/yas/test_custom_class4.cpp @@ -0,0 +1,68 @@ +#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()}; + } + + Person to_class() const { + return Person(first_name.value(), std::move(last_name.value()), + age.value()); + } +}; + +} // namespace test_custom_class4 + +namespace rfl { +namespace parsing { + +template +struct Parser + : public CustomParser {}; + +} // namespace parsing +} // namespace rfl + +namespace test_custom_class4 { + +TEST(yas, 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/yas/test_default_values.cpp b/tests/yas/test_default_values.cpp new file mode 100644 index 00000000..aebdf5b9 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_deque.cpp b/tests/yas/test_deque.cpp new file mode 100644 index 00000000..f7603ad8 --- /dev/null +++ b/tests/yas/test_deque.cpp @@ -0,0 +1,26 @@ +#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(yas, 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/yas/test_enum.cpp b/tests/yas/test_enum.cpp new file mode 100644 index 00000000..7e307245 --- /dev/null +++ b/tests/yas/test_enum.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_enum { + +enum class Color { red, green, blue, yellow }; + +struct Circle { + double radius; + Color color; +}; + +TEST(yas, test_enum) { + const auto circle = Circle{.radius = 5.0, .color = Color::green}; + write_and_read(circle); +} +} // namespace test_enum diff --git a/tests/yas/test_field_variant.cpp b/tests/yas/test_field_variant.cpp new file mode 100644 index 00000000..ebc40b10 --- /dev/null +++ b/tests/yas/test_field_variant.cpp @@ -0,0 +1,33 @@ +#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; +}; + +struct Shapes { + rfl::Variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(yas, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant diff --git a/tests/yas/test_field_variant_std.cpp b/tests/yas/test_field_variant_std.cpp new file mode 100644 index 00000000..97631f8a --- /dev/null +++ b/tests/yas/test_field_variant_std.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_field_variant_std { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + std::variant, rfl::Field<"rectangle", Rectangle>, + rfl::Field<"square", rfl::Box>> + root; +}; + +TEST(yas, test_field_variant_std) { + const auto r = + Shapes{rfl::make_field<"rectangle">(Rectangle{.height = 10, .width = 5})}; + + write_and_read(r); +} +} // namespace test_field_variant_std diff --git a/tests/yas/test_flag_enum.cpp b/tests/yas/test_flag_enum.cpp new file mode 100644 index 00000000..2bb5e376 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_flag_enum_with_int.cpp b/tests/yas/test_flag_enum_with_int.cpp new file mode 100644 index 00000000..e1abc18c --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_flatten.cpp b/tests/yas/test_flatten.cpp new file mode 100644 index 00000000..050979a9 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_flatten_anonymous.cpp b/tests/yas/test_flatten_anonymous.cpp new file mode 100644 index 00000000..209f3491 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_forward_list.cpp b/tests/yas/test_forward_list.cpp new file mode 100644 index 00000000..300585ff --- /dev/null +++ b/tests/yas/test_forward_list.cpp @@ -0,0 +1,26 @@ +#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(yas, 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/yas/test_generic.cpp b/tests/yas/test_generic.cpp new file mode 100644 index 00000000..738cc833 --- /dev/null +++ b/tests/yas/test_generic.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_generic { + +TEST(yas, test_generic) { + auto bart = rfl::Generic::Object(); + bart["first_name"] = "Bart"; + bart["last_name"] = "Simpson"; + bart["age"] = 10; + + auto lisa = rfl::Generic::Object(); + lisa["first_name"] = "Lisa"; + lisa["last_name"] = "Simpson"; + lisa["age"] = 8; + + auto maggie = rfl::Generic::Object(); + maggie["first_name"] = "Lisa"; + maggie["last_name"] = "Simpson"; + maggie["age"] = 0; + + auto homer = rfl::Generic::Object(); + homer["first_name"] = "Lisa"; + homer["last_name"] = "Simpson"; + homer["age"] = 45; + homer["children"] = rfl::Generic::Array({bart, lisa, maggie}); + + write_and_read(homer); +} +} // namespace test_generic diff --git a/tests/yas/test_literal.cpp b/tests/yas/test_literal.cpp new file mode 100644 index 00000000..f3a0f137 --- /dev/null +++ b/tests/yas/test_literal.cpp @@ -0,0 +1,24 @@ +#include +#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(yas, test_literal) { + const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + + write_and_read(bart); +} +} // namespace test_literal diff --git a/tests/yas/test_literal_map.cpp b/tests/yas/test_literal_map.cpp new file mode 100644 index 00000000..88f456bc --- /dev/null +++ b/tests/yas/test_literal_map.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal_map { + +using FieldName = rfl::Literal<"firstName", "lastName">; + +struct RootStruct { + std::map> root; +}; + +TEST(yas, test_literal_map) { + RootStruct homer; + homer.root.insert(std::make_pair(FieldName::make<"firstName">(), + std::make_unique("Homer"))); + homer.root.insert(std::make_pair(FieldName::make<"lastName">(), + std::make_unique("Simpson"))); + write_and_read(homer); +} +} // namespace test_literal_map diff --git a/tests/yas/test_map.cpp b/tests/yas/test_map.cpp new file mode 100644 index 00000000..5ab127b2 --- /dev/null +++ b/tests/yas/test_map.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#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::unique_ptr> children; +}; + +TEST(yas, test_map) { + 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 diff --git a/tests/yas/test_map2.cpp b/tests/yas/test_map2.cpp new file mode 100644 index 00000000..0a64e7f9 --- /dev/null +++ b/tests/yas/test_map2.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map2 { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::map children; +}; + +TEST(yas, test_map2) { + auto children = std::map(); + 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_map2 diff --git a/tests/yas/test_monster_example.cpp b/tests/yas/test_monster_example.cpp new file mode 100644 index 00000000..da129f2a --- /dev/null +++ b/tests/yas/test_monster_example.cpp @@ -0,0 +1,59 @@ +#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, rfl::Field<"None", int>>; + +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(yas, 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/yas/test_optional_fields.cpp b/tests/yas/test_optional_fields.cpp new file mode 100644 index 00000000..63f6ac13 --- /dev/null +++ b/tests/yas/test_optional_fields.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optional_fields { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + std::optional town = std::nullopt; +}; + +TEST(yas, test_optional_fields) { + const auto person1 = Person{.first_name = "Homer", .last_name = "Simpson"}; + write_and_read(person1); + + const auto person2 = Person{ + .first_name = "Homer", .last_name = "Simpson", .town = "Springfield"}; + write_and_read(person2); +} +} // namespace test_optional_fields diff --git a/tests/yas/test_optionals_in_vectors.cpp b/tests/yas/test_optionals_in_vectors.cpp new file mode 100644 index 00000000..5a32ce1c --- /dev/null +++ b/tests/yas/test_optionals_in_vectors.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_optionals_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(yas, test_optionals_in_vectors) { + const std::optional bart = Person{.first_name = "Bart"}; + + const std::optional lisa = Person{.first_name = "Lisa"}; + + const std::optional maggie = Person{.first_name = "Maggie"}; + + const auto homer = Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, std::nullopt})}; + + write_and_read(homer); +} +} // namespace test_optionals_in_vectors diff --git a/tests/yas/test_person.cpp b/tests/yas/test_person.cpp new file mode 100644 index 00000000..600ed688 --- /dev/null +++ b/tests/yas/test_person.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_person { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector children; +}; + +TEST(yas, test_person) { + 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_person diff --git a/tests/yas/test_readme_example.cpp b/tests/yas/test_readme_example.cpp new file mode 100644 index 00000000..f37c7a64 --- /dev/null +++ b/tests/yas/test_readme_example.cpp @@ -0,0 +1,45 @@ +#include +#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(yas, 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", + .email = "homer@simpson.com", + .child = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_readme_example diff --git a/tests/yas/test_readme_example2.cpp b/tests/yas/test_readme_example2.cpp new file mode 100644 index 00000000..dee13a10 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_readme_example3.cpp b/tests/yas/test_readme_example3.cpp new file mode 100644 index 00000000..12d66837 --- /dev/null +++ b/tests/yas/test_readme_example3.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example3 { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + std::vector children; +}; + +TEST(yas, test_readme_example3) { + const auto bart = Person{.first_name = "Bart", .birthday = "1987-04-19"}; + + const auto lisa = Person{.first_name = "Lisa", .birthday = "1987-04-19"}; + + const auto maggie = Person{.first_name = "Maggie", .birthday = "1987-04-19"}; + + const auto homer = + Person{.first_name = "Homer", + .birthday = "1987-04-19", + .children = std::vector({bart, lisa, maggie})}; + + write_and_read(homer); +} +} // namespace test_readme_example3 diff --git a/tests/yas/test_ref.cpp b/tests/yas/test_ref.cpp new file mode 100644 index 00000000..4835530f --- /dev/null +++ b/tests/yas/test_ref.cpp @@ -0,0 +1,41 @@ +#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(yas, 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/yas/test_rfl_tuple.cpp b/tests/yas/test_rfl_tuple.cpp new file mode 100644 index 00000000..1e325e6d --- /dev/null +++ b/tests/yas/test_rfl_tuple.cpp @@ -0,0 +1,32 @@ + +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_rfl_tuple { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + std::unique_ptr> children = nullptr; +}; + +TEST(yas, test_rfl_tuple) { + 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>( + rfl::Tuple{ + std::move(bart), std::move(lisa), std::move(maggie)})}; + + write_and_read(homer); +} +} // namespace test_rfl_tuple diff --git a/tests/yas/test_rfl_variant.cpp b/tests/yas/test_rfl_variant.cpp new file mode 100644 index 00000000..a341022d --- /dev/null +++ b/tests/yas/test_rfl_variant.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "write_and_read.hpp" + +namespace test_variant { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +struct Shapes { + rfl::Variant> root; +}; + +TEST(yas, test_rfl_variant) { + const auto r = Shapes{Rectangle{.height = 10, .width = 5}}; + + write_and_read(r); +} +} // namespace test_variant diff --git a/tests/yas/test_save_load.cpp b/tests/yas/test_save_load.cpp new file mode 100644 index 00000000..8c26cc6b --- /dev/null +++ b/tests/yas/test_save_load.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_save_load { +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Email email; + int age; +}; + +TEST(yas, test_save_load) { + const auto homer = Person{.first_name = "Homer", + .last_name = "Simpson", + .email = "homer@simpson.com", + .age = 45}; + + rfl::yas::save("homer.yas", homer); + + const auto homer2 = rfl::yas::load("homer.yas").value(); + + write_and_read(homer2); +} +} // namespace test_save_load diff --git a/tests/yas/test_set.cpp b/tests/yas/test_set.cpp new file mode 100644 index 00000000..202c5d19 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_shared_ptr.cpp b/tests/yas/test_shared_ptr.cpp new file mode 100644 index 00000000..72fdaf4b --- /dev/null +++ b/tests/yas/test_shared_ptr.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_shared_ptr { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::shared_ptr> children; +}; + +TEST(yas, test_shared_ptr) { + auto children = std::make_shared>(); + 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_shared_ptr diff --git a/tests/yas/test_size.cpp b/tests/yas/test_size.cpp new file mode 100644 index 00000000..8ef4c40d --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_string_map.cpp b/tests/yas/test_string_map.cpp new file mode 100644 index 00000000..e2ef2d51 --- /dev/null +++ b/tests/yas/test_string_map.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_string_map { + +struct RootStruct { + std::map> root; +}; + +TEST(yas, test_string_map) { + RootStruct homer; + homer.root.insert( + std::make_pair("firstName", std::make_unique("Homer"))); + homer.root.insert( + std::make_pair("lastName", std::make_unique("Simpson"))); + + write_and_read(homer); +} +} // namespace test_string_map diff --git a/tests/yas/test_tagged_union.cpp b/tests/yas/test_tagged_union.cpp new file mode 100644 index 00000000..43e55b39 --- /dev/null +++ b/tests/yas/test_tagged_union.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union { + +struct Circle { + using Tag = rfl::Literal<"circle">; + double radius; +}; + +struct Rectangle { + using Tag = rfl::Literal<"rectangle">; + double height; + double width; +}; + +struct Square { + using Tag = rfl::Literal<"square">; + double width; +}; + +using Shapes = rfl::TaggedUnion<"shape", Circle, Rectangle, Square>; + +TEST(yas, test_tagged_union) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} + +} // namespace test_tagged_union diff --git a/tests/yas/test_timestamp.cpp b/tests/yas/test_timestamp.cpp new file mode 100644 index 00000000..8a594f93 --- /dev/null +++ b/tests/yas/test_timestamp.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_timestamp { + +struct Person { + std::string first_name; + std::string last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; +}; + +TEST(yas, test_timestamp) { + const auto person = Person{.first_name = "Homer", + .last_name = "Simpson", + .birthday = "1987-04-19"}; + + write_and_read(person); +} +} // namespace test_timestamp diff --git a/tests/yas/test_tuple.cpp b/tests/yas/test_tuple.cpp new file mode 100644 index 00000000..e95efb98 --- /dev/null +++ b/tests/yas/test_tuple.cpp @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tuple { + +TEST(yas, test_tuple) { + const auto my_tuple = std::make_tuple(42, std::string("Hello"), true, 3.14); + write_and_read(my_tuple); +} + +} // namespace test_tuple diff --git a/tests/yas/test_unique_ptr.cpp b/tests/yas/test_unique_ptr.cpp new file mode 100644 index 00000000..f047a34d --- /dev/null +++ b/tests/yas/test_unique_ptr.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_unique_ptr { + +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(yas, test_unique_ptr) { + 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::LeafOrNode(leaf1)), + .greater = std::make_unique(DecisionTree::LeafOrNode(leaf2))}; + + const DecisionTree tree = DecisionTree{.leaf_or_node = std::move(node)}; + + write_and_read(tree); +} +} // namespace test_unique_ptr diff --git a/tests/yas/test_unique_ptr2.cpp b/tests/yas/test_unique_ptr2.cpp new file mode 100644 index 00000000..70ace884 --- /dev/null +++ b/tests/yas/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(yas, 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/yas/test_variant.cpp b/tests/yas/test_variant.cpp new file mode 100644 index 00000000..31f96dab --- /dev/null +++ b/tests/yas/test_variant.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#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(yas, test_variant) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + + write_and_read(r); +} + +} // namespace test_variant diff --git a/tests/yas/test_variants_in_vectors.cpp b/tests/yas/test_variants_in_vectors.cpp new file mode 100644 index 00000000..0ef49194 --- /dev/null +++ b/tests/yas/test_variants_in_vectors.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_variants_in_vectors { + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + std::vector> children; +}; + +TEST(yas, test_variants_in_vectors) { + const std::variant bart = Person{.first_name = "Bart"}; + + const std::variant lisa = Person{.first_name = "Lisa"}; + + const std::variant maggie = + Person{.first_name = "Maggie"}; + + const auto homer = + Person{.first_name = "Homer", + .children = std::vector>( + {bart, lisa, maggie, "Unknown"})}; + + write_and_read(homer); +} +} // namespace test_variants_in_vectors diff --git a/tests/yas/test_wstring.cpp b/tests/yas/test_wstring.cpp new file mode 100644 index 00000000..128123cc --- /dev/null +++ b/tests/yas/test_wstring.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +struct TestStruct { + std::string theNormalString; + std::wstring theWiderString; +}; + +namespace test_wstring { +TEST(yas, test_wstring) { + const auto test = TestStruct{.theNormalString = "The normal string", + .theWiderString = L"The wider string"}; + + write_and_read(test); +} +} // namespace test_wstring diff --git a/tests/yas/write_and_read.hpp b/tests/yas/write_and_read.hpp new file mode 100644 index 00000000..db8e4a08 --- /dev/null +++ b/tests/yas/write_and_read.hpp @@ -0,0 +1,19 @@ +#ifndef WRITE_AND_READ_ +#define WRITE_AND_READ_ + +#include + +#include +#include + +template +void write_and_read(const auto& _struct) { + using T = std::remove_cvref_t; + const auto serialized1 = rfl::yas::write(_struct); + const auto res = rfl::yas::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().what(); + const auto serialized2 = rfl::yas::write(res.value()); + EXPECT_EQ(serialized1, serialized2); +} +#endif diff --git a/vcpkg.json b/vcpkg.json index 02257f41..d676a811 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -179,6 +179,14 @@ } ] }, + "yas": { + "description": "Enable yas support", + "dependencies": [ + { + "name": "yas" + } + ] + }, "yaml": { "description": "Enable YAML support", "dependencies": [