diff --git a/.gitignore b/.gitignore index be3124d7..09cf71f1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ *.flexbuf *.msgpack *.toml +*.ubjson *.xml *.yml *.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index f20811d9..5375f7c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ option(REFLECTCPP_FLEXBUFFERS "Enable flexbuffers support" OFF) option(REFLECTCPP_MSGPACK "Enable msgpack support" OFF) option(REFLECTCPP_XML "Enable XML support" OFF) option(REFLECTCPP_TOML "Enable TOML support" OFF) +option(REFLECTCPP_UBJSON "Enable UBJSON support" OFF) option(REFLECTCPP_YAML "Enable YAML support" OFF) option(REFLECTCPP_BUILD_BENCHMARKS "Build benchmarks" OFF) @@ -25,11 +26,12 @@ if(REFLECTCPP_BUILD_BENCHMARKS) set(REFLECTCPP_MSGPACK ON CACHE BOOL "" FORCE) set(REFLECTCPP_XML ON CACHE BOOL "" FORCE) set(REFLECTCPP_TOML ON CACHE BOOL "" FORCE) + set(REFLECTCPP_UBJSON ON CACHE BOOL "" FORCE) set(REFLECTCPP_YAML ON CACHE BOOL "" FORCE) endif() if (REFLECTCPP_BUILD_TESTS OR REFLECTCPP_BUILD_BENCHMARKS OR (REFLECTCPP_JSON AND NOT REFLECTCPP_USE_BUNDLED_DEPENDENCIES) OR - REFLECTCPP_BSON OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_XML OR REFLECTCPP_TOML OR REFLECTCPP_YAML) + REFLECTCPP_BSON OR REFLECTCPP_CBOR OR REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_MSGPACK OR REFLECTCPP_XML OR REFLECTCPP_TOML OR REFLECTCPP_UBJSON OR REFLECTCPP_YAML) # enable vcpkg per default if require features other than JSON set(REFLECTCPP_USE_VCPKG_DEFAULT ON) endif() @@ -147,6 +149,14 @@ if (REFLECTCPP_TOML) endif () endif() +if (REFLECTCPP_UBJSON) + list(APPEND REFLECT_CPP_SOURCES + src/reflectcpp_ubjson.cpp + ) + target_include_directories(reflectcpp SYSTEM PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include") + find_package(jsoncons CONFIG REQUIRED) +endif () + if (REFLECTCPP_XML) list(APPEND REFLECT_CPP_SOURCES src/reflectcpp_xml.cpp diff --git a/README.md b/README.md index 36a153bf..9fe05b31 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The following table lists the serialization formats currently supported by refle | flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format | | msgpack | [msgpack-c](https://github.com/msgpack/msgpack-c) | >= 6.0.0 | BSL 1.0 | JSON-like binary format | | TOML | [toml++](https://github.com/marzer/tomlplusplus) | >= 3.4.0 | MIT | Textual format with an emphasis on readability | +| 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 | @@ -471,7 +472,7 @@ In addition, it supports the following custom containers: - `rfl::Binary`: Used to express numbers in binary format. - `rfl::Box`: Similar to `std::unique_ptr`, but (almost) guaranteed to never be null. -- `rfl::Bytestring`: An alias for `std::basic_string`. Supported by BSON, CBOR, flexbuffers and msgpack. +- `rfl::Bytestring`: An alias for `std::basic_string`. Supported by BSON, CBOR, flexbuffers, msgpack and UBJSON. - `rfl::Generic`: A catch-all type that can represent (almost) anything. - `rfl::Hex`: Used to express numbers in hex format. - `rfl::Literal`: An explicitly enumerated string. @@ -569,6 +570,7 @@ set(REFLECTCPP_CBOR ON) # Optional set(REFLECTCPP_FLEXBUFFERS ON) # Optional set(REFLECTCPP_MSGPACK ON) # Optional set(REFLECTCPP_TOML ON) # Optional +set(REFLECTCPP_UBJSON ON) # Optional set(REFLECTCPP_XML ON) # Optional set(REFLECTCPP_YAML ON) # Optional @@ -619,7 +621,7 @@ To run the tests, do the following: To compile the tests with serialization formats other than JSON, do the following: ```bash -cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release +cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_BSON=ON -DREFLECTCPP_CBOR=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_MSGPACK=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_TOML=ON -DREFLECTCPP_UBJSON=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release cmake --build build -j 4 # gcc, clang cmake --build build --config Release -j 4 # MSVC ``` @@ -633,6 +635,7 @@ To run the tests, do the following: ./build/tests/msgpack/reflect-cpp-msgpack-tests ./build/tests/json/reflect-cpp-json-tests ./build/tests/toml/reflect-cpp-toml-tests +./build/tests/toml/reflect-cpp-ubjson-tests ./build/tests/xml/reflect-cpp-xml-tests ./build/tests/yaml/reflect-cpp-yaml-tests ``` diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index efaf2471..f3fb2158 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,30 @@ static void BM_canada_read_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_toml); +static void BM_canada_read_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_ubjson); + +static void BM_canada_read_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = + rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_ubjson_without_field_names); + static void BM_canada_read_reflect_cpp_yaml(benchmark::State &state) { const auto data = rfl::yaml::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 4e7bdb11..bb5a9c3e 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,29 @@ static void BM_canada_write_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_toml); +static void BM_canada_write_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_ubjson); + +static void BM_canada_write_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_ubjson_without_field_names); + static void BM_canada_write_reflect_cpp_yaml(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/benchmarks/all/licenses_read.cpp b/benchmarks/all/licenses_read.cpp index 9926c3b4..3606aa41 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,29 @@ static void BM_licenses_read_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_toml); +static void BM_licenses_read_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_ubjson); + +static void BM_licenses_read_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_ubjson_without_field_names); + static void BM_licenses_read_reflect_cpp_yaml(benchmark::State &state) { const auto data = rfl::yaml::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index db6bef5f..282af309 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,29 @@ static void BM_licenses_write_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_toml); +static void BM_licenses_write_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_ubjson); + +static void BM_licenses_write_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_ubjson_without_field_names); + static void BM_licenses_write_reflect_cpp_xml(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/benchmarks/all/person_read.cpp b/benchmarks/all/person_read.cpp index 7955b586..13d02e00 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -9,11 +9,11 @@ #include #include #include +#include #include #include #include #include - namespace person_read { // ---------------------------------------------------------------------------- @@ -156,6 +156,29 @@ static void BM_person_read_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_toml); +static void BM_person_read_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_ubjson); + +static void BM_person_read_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = rfl::ubjson::write(load_data()); + for (auto _ : state) { + const auto res = rfl::ubjson::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_ubjson_without_field_names); + static void BM_person_read_reflect_cpp_xml(benchmark::State &state) { const auto data = rfl::xml::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index 2e34e8f3..73110db8 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,29 @@ static void BM_person_write_reflect_cpp_toml(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_toml); +static void BM_person_write_reflect_cpp_ubjson(benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_ubjson); + +static void BM_person_write_reflect_cpp_ubjson_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::ubjson::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_ubjson_without_field_names); + static void BM_person_write_reflect_cpp_xml(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/docs/README.md b/docs/README.md index a115311f..e8bf2050 100644 --- a/docs/README.md +++ b/docs/README.md @@ -88,9 +88,11 @@ 6.6) [TOML](https://github.com/getml/reflect-cpp/blob/main/docs/toml.md) -6.7) [XML](https://github.com/getml/reflect-cpp/blob/main/docs/xml.md) +6.7) [UBJSON](https://github.com/getml/reflect-cpp/blob/main/docs/ubjson.md) -6.8) [YAML](https://github.com/getml/reflect-cpp/blob/main/docs/yaml.md) +6.8) [XML](https://github.com/getml/reflect-cpp/blob/main/docs/xml.md) + +6.9) [YAML](https://github.com/getml/reflect-cpp/blob/main/docs/yaml.md) ## 7) Advanced topics diff --git a/docs/cbor.md b/docs/cbor.md index c64a1d91..dbd3239c 100644 --- a/docs/cbor.md +++ b/docs/cbor.md @@ -17,7 +17,7 @@ struct Person { }; ``` -A `Person` can be turned into a bytes vector like this: +A `Person` can be serialized to a bytes vector like this: ```cpp const auto person = Person{...}; diff --git a/docs/ubjson.md b/docs/ubjson.md new file mode 100644 index 00000000..8620596d --- /dev/null +++ b/docs/ubjson.md @@ -0,0 +1,100 @@ +# UBJSON + +For UBJSON support, you must also include the header `` and include the jsoncons library (https://github.com/danielaparker/jsoncons). Note that it is header-only. + +UBJSON is a JSON-like binary format. + +## 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::ubjson::write(person); +``` + +You can parse bytes like this: + +```cpp +const rfl::Result result = rfl::ubjson::read(bytes); +``` + +## Loading and saving + +You can also load and save to disc using a very similar syntax: + +```cpp +const rfl::Result result = rfl::ubjson::load("/path/to/file.ubjson"); + +const auto person = Person{...}; +rfl::ubjson::save("/path/to/file.ubjson", 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::ubjson::read(my_istream); + +const auto person = Person{...}; +rfl::ubjson::write(person, my_ostream); +``` + +Note that `std::cout` is also an ostream, so this works as well: + +```cpp +rfl::ubjson::write(person, std::cout) << std::endl; +``` + +(Since UBJSON is a binary format, the readability of this will be limited, but it might be useful for debugging). + +## Custom constructors + +One of the great things about C++ is that it gives you control over +when and how you code is compiled. + +For large and complex systems of structs, it is often a good idea to split up +your code into smaller compilation units. You can do so using custom constructors. + +For the UBJSON format, these must be a static function on your struct or class called +`from_ubjson` that take a `rfl::ubjson::Reader::InputVarType` as input and return +the class or the class wrapped in `rfl::Result`. + +In your header file you can write something like this: + +```cpp +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name; + rfl::Timestamp<"%Y-%m-%d"> birthday; + + using InputVarType = typename rfl::ubjson::Reader::InputVarType; + static rfl::Result from_ubjson(const InputVarType& _obj); +}; +``` + +And in your source file, you implement `from_ubjson` as follows: + +```cpp +rfl::Result Person::from_ubjson(const InputVarType& _obj) { + const auto from_nt = [](auto&& _nt) { + return rfl::from_named_tuple(std::move(_nt)); + }; + return rfl::ubjson::read>(_obj) + .transform(from_nt); +} +``` + +This will force the compiler to only compile the UBJSON parsing when the source file is compiled. diff --git a/include/rfl/ubjson.hpp b/include/rfl/ubjson.hpp new file mode 100644 index 00000000..2bbe86ed --- /dev/null +++ b/include/rfl/ubjson.hpp @@ -0,0 +1,13 @@ +#ifndef RFL_UBJSON_HPP_ +#define RFL_UBJSON_HPP_ + +#include "../rfl.hpp" +#include "ubjson/Parser.hpp" +#include "ubjson/Reader.hpp" +#include "ubjson/Writer.hpp" +#include "ubjson/load.hpp" +#include "ubjson/read.hpp" +#include "ubjson/save.hpp" +#include "ubjson/write.hpp" + +#endif diff --git a/include/rfl/ubjson/Parser.hpp b/include/rfl/ubjson/Parser.hpp new file mode 100644 index 00000000..061b51c3 --- /dev/null +++ b/include/rfl/ubjson/Parser.hpp @@ -0,0 +1,54 @@ +#ifndef RFL_UBJSON_PARSER_HPP_ +#define RFL_UBJSON_PARSER_HPP_ + +#include "../Tuple.hpp" +#include "../parsing/Parser.hpp" +#include "Reader.hpp" +#include "Writer.hpp" + +namespace rfl::parsing { + +/// UBJSON 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< + ubjson::Reader, ubjson::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 rfl::parsing + +namespace rfl::ubjson { + +template +using Parser = parsing::Parser; + +} // namespace rfl::ubjson + +#endif diff --git a/include/rfl/ubjson/Reader.hpp b/include/rfl/ubjson/Reader.hpp new file mode 100644 index 00000000..64d7656d --- /dev/null +++ b/include/rfl/ubjson/Reader.hpp @@ -0,0 +1,131 @@ +#ifndef RFL_UBJSON_READER_HPP_ +#define RFL_UBJSON_READER_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../Bytestring.hpp" +#include "../Result.hpp" +#include "../always_false.hpp" + +namespace rfl::ubjson { + +class Reader { + public: + struct UBJSONInputArray { + jsoncons::json* val_; + }; + + struct UBJSONInputObject { + jsoncons::json* val_; + }; + + struct UBJSONInputVar { + jsoncons::json* val_; + }; + + using InputArrayType = UBJSONInputArray; + using InputObjectType = UBJSONInputObject; + using InputVarType = UBJSONInputVar; + + Reader() {} + + ~Reader() = default; + + template + static constexpr bool has_custom_constructor = (requires(InputVarType var) { + T::from_ubjson_obj(var); + }); + + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept; + + rfl::Result get_field_from_object( + const std::string& _name, const InputObjectType& _obj) const noexcept; + + bool is_empty(const InputVarType& _var) const noexcept; + + template + rfl::Result to_basic_type(const InputVarType& _var) const noexcept { + if constexpr (std::is_same, std::string>()) { + if (!_var.val_->is_string()) { + return Error("Could not cast to string."); + } + return _var.val_->as(); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + if (!_var.val_->is>()) { + return Error("Could not cast to bytestring."); + } + const auto vec = _var.val_->as>(); + return rfl::Bytestring(std::bit_cast(vec.data()), + vec.size()); + } else if constexpr (std::is_same, bool>()) { + if (!_var.val_->is_bool()) { + return rfl::Error("Could not cast to boolean."); + } + return _var.val_->as(); + } else if constexpr (std::is_floating_point>() || + std::is_integral>()) { + if (_var.val_->is_double()) { + return static_cast(_var.val_->as()); + } + if (_var.val_->is_int64()) { + return static_cast(_var.val_->as()); + } + if (_var.val_->is_uint64()) { + return static_cast(_var.val_->as()); + } + return rfl::Error( + "Could not cast to numeric value. The type must be integral, " + "float or double."); + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + } + + rfl::Result to_array(const InputVarType& _var) const noexcept; + + rfl::Result to_object( + const InputVarType& _var) const noexcept; + + template + std::optional read_array(const ArrayReader& _array_reader, + const InputArrayType& _arr) const noexcept { + for (auto& val : _arr.val_->array_range()) { + const auto err = _array_reader.read(InputVarType{&val}); + if (err) { + return err; + } + } + return std::nullopt; + } + + template + std::optional read_object(const ObjectReader& _object_reader, + const InputObjectType& _obj) const noexcept { + for (auto& kv : _obj.val_->object_range()) { + _object_reader.read(kv.key(), InputVarType{&kv.value()}); + } + return std::nullopt; + } + + template + rfl::Result use_custom_constructor( + const InputVarType& _var) const noexcept { + try { + return T::from_ubjson_obj(_var); + } catch (std::exception& e) { + return rfl::Error(e.what()); + } + } +}; + +} // namespace rfl::ubjson + +#endif diff --git a/include/rfl/ubjson/Writer.hpp b/include/rfl/ubjson/Writer.hpp new file mode 100644 index 00000000..9c5905e8 --- /dev/null +++ b/include/rfl/ubjson/Writer.hpp @@ -0,0 +1,120 @@ +#ifndef RFL_UBJSON_WRITER_HPP_ +#define RFL_UBJSON_WRITER_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../Box.hpp" +#include "../Bytestring.hpp" +#include "../Ref.hpp" +#include "../Result.hpp" +#include "../always_false.hpp" + +namespace rfl::ubjson { + +class Writer { + using Encoder = jsoncons::ubjson::ubjson_bytes_encoder; + + public: + struct UBJSONOutputArray {}; + + struct UBJSONOutputObject {}; + + struct UBJSONOutputVar {}; + + using OutputArrayType = UBJSONOutputArray; + using OutputObjectType = UBJSONOutputObject; + using OutputVarType = UBJSONOutputVar; + + Writer(Encoder* _encoder); + + ~Writer(); + + OutputArrayType array_as_root(const size_t _size) const noexcept; + + OutputObjectType object_as_root(const size_t _size) const noexcept; + + OutputVarType null_as_root() const noexcept; + + template + OutputVarType value_as_root(const T& _var) const noexcept { + return new_value(_var); + } + + OutputArrayType add_array_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept; + + OutputArrayType add_array_to_object(const std::string_view& _name, + const size_t _size, + OutputObjectType* _parent) const noexcept; + + OutputObjectType add_object_to_array(const size_t _size, + OutputArrayType* _parent) const noexcept; + + OutputObjectType add_object_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept; + + template + OutputVarType add_value_to_array(const T& _var, + OutputArrayType* _parent) const noexcept { + return new_value(_var); + } + + template + OutputVarType add_value_to_object(const std::string_view& _name, + const T& _var, + OutputObjectType* _parent) const noexcept { + encoder_->key(_name); + return new_value(_var); + } + + OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept; + + OutputVarType add_null_to_object(const std::string_view& _name, + OutputObjectType* _parent) const noexcept; + + void end_array(OutputArrayType* _arr) const noexcept; + + void end_object(OutputObjectType* _obj) const noexcept; + + private: + OutputArrayType new_array(const size_t _size) const noexcept; + + OutputObjectType new_object(const size_t _size) const noexcept; + + template + OutputVarType new_value(const T& _var) const noexcept { + if constexpr (std::is_same, std::string>()) { + encoder_->string_value(_var); + } else if constexpr (std::is_same, + rfl::Bytestring>()) { + encoder_->byte_string_value(_var); + } else if constexpr (std::is_same, bool>()) { + encoder_->bool_value(_var); + } else if constexpr (std::is_floating_point>()) { + encoder_->double_value(static_cast(_var)); + } else if constexpr (std::is_integral>()) { + encoder_->int64_value(static_cast(_var)); + } else { + static_assert(rfl::always_false_v, "Unsupported type."); + } + return OutputVarType{}; + } + + private: + /// The underlying TinyUBJSON encoder. + Encoder* const encoder_; +}; +} // namespace rfl::ubjson + +#endif diff --git a/include/rfl/ubjson/load.hpp b/include/rfl/ubjson/load.hpp new file mode 100644 index 00000000..f344c17f --- /dev/null +++ b/include/rfl/ubjson/load.hpp @@ -0,0 +1,21 @@ +#ifndef RFL_UBJSON_LOAD_HPP_ +#define RFL_UBJSON_LOAD_HPP_ + +#include "../Processors.hpp" +#include "../Result.hpp" +#include "../io/load_bytes.hpp" +#include "read.hpp" + +namespace rfl::ubjson { + +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::ubjson + +#endif diff --git a/include/rfl/ubjson/read.hpp b/include/rfl/ubjson/read.hpp new file mode 100644 index 00000000..f55c4432 --- /dev/null +++ b/include/rfl/ubjson/read.hpp @@ -0,0 +1,38 @@ +#ifndef RFL_UBJSON_READ_HPP_ +#define RFL_UBJSON_READ_HPP_ + +#include +#include +#include +#include +#include + +#include "../Processors.hpp" +#include "../internal/wrap_in_rfl_array_t.hpp" +#include "Parser.hpp" +#include "Reader.hpp" + +namespace rfl::ubjson { + +using InputObjectType = typename Reader::InputObjectType; +using InputVarType = typename Reader::InputVarType; + +/// Parses an object from UBJSON using reflection. +template +Result> read(const std::vector& _bytes) { + auto val = jsoncons::ubjson::decode_ubjson(_bytes); + auto r = Reader(); + return Parser>::read(r, InputVarType{&val}); +} + +/// Parses an object from a stream. +template +Result> read(std::istream& _stream) { + auto val = jsoncons::ubjson::decode_ubjson(_stream); + auto r = Reader(); + return Parser>::read(r, InputVarType{&val}); +} + +} // namespace rfl::ubjson + +#endif diff --git a/include/rfl/ubjson/save.hpp b/include/rfl/ubjson/save.hpp new file mode 100644 index 00000000..453fb52e --- /dev/null +++ b/include/rfl/ubjson/save.hpp @@ -0,0 +1,24 @@ +#ifndef RFL_UBJSON_SAVE_HPP_ +#define RFL_UBJSON_SAVE_HPP_ + +#include +#include +#include + +#include "../Result.hpp" +#include "../io/save_bytes.hpp" +#include "write.hpp" + +namespace rfl::ubjson { + +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::ubjson + +#endif diff --git a/include/rfl/ubjson/write.hpp b/include/rfl/ubjson/write.hpp new file mode 100644 index 00000000..7f5d5ea2 --- /dev/null +++ b/include/rfl/ubjson/write.hpp @@ -0,0 +1,41 @@ +#ifndef RFL_UBJSON_WRITE_HPP_ +#define RFL_UBJSON_WRITE_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "../parsing/Parent.hpp" +#include "Parser.hpp" + +namespace rfl::ubjson { + +/// Returns UBJSON bytes. +template +std::vector write(const auto& _obj) noexcept { + using T = std::remove_cvref_t; + using ParentType = parsing::Parent; + std::vector buffer; + jsoncons::ubjson::ubjson_bytes_encoder encoder(buffer); + const auto writer = Writer(&encoder); + Parser>::write(writer, _obj, + typename ParentType::Root{}); + return std::vector(std::bit_cast(buffer.data()), + std::bit_cast(buffer.data() + buffer.size())); +} + +/// Writes a UBJSON into an ostream. +template +std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept { + auto buffer = write(_obj); + _stream.write(buffer.data(), buffer.size()); + return _stream; +} + +} // namespace rfl::ubjson + +#endif diff --git a/src/reflectcpp_ubjson.cpp b/src/reflectcpp_ubjson.cpp new file mode 100644 index 00000000..b3dc8db7 --- /dev/null +++ b/src/reflectcpp_ubjson.cpp @@ -0,0 +1,33 @@ +/* + +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/ubjson/Reader.cpp" +#include "rfl/ubjson/Writer.cpp" diff --git a/src/rfl/ubjson/Reader.cpp b/src/rfl/ubjson/Reader.cpp new file mode 100644 index 00000000..17d82e71 --- /dev/null +++ b/src/rfl/ubjson/Reader.cpp @@ -0,0 +1,43 @@ +#include "rfl/ubjson/Reader.hpp" + +namespace rfl::ubjson { + +rfl::Result Reader::get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + if (_idx >= _arr.val_->size()) { + return Error("Index out of range."); + } + return InputVarType{&_arr.val_->at(_idx)}; +} + +rfl::Result Reader::get_field_from_object( + const std::string& _name, const InputObjectType& _obj) const noexcept { + for (auto& kv : _obj.val_->object_range()) { + if (kv.key() == _name) { + return InputVarType{&kv.value()}; + }; + } + return Error("Field name '" + _name + "' not found."); +} + +bool Reader::is_empty(const InputVarType& _var) const noexcept { + return _var.val_->is_null(); +} + +rfl::Result Reader::to_array( + const InputVarType& _var) const noexcept { + if (!_var.val_->is_array()) { + return Error("Could not cast to an array."); + } + return InputArrayType{_var.val_}; +} + +rfl::Result Reader::to_object( + const InputVarType& _var) const noexcept { + if (!_var.val_->is_object()) { + return Error("Could not cast to an object."); + } + return InputObjectType{_var.val_}; +} + +} // namespace rfl::ubjson diff --git a/src/rfl/ubjson/Writer.cpp b/src/rfl/ubjson/Writer.cpp new file mode 100644 index 00000000..278edca2 --- /dev/null +++ b/src/rfl/ubjson/Writer.cpp @@ -0,0 +1,79 @@ +#include "rfl/ubjson/Writer.hpp" + +namespace rfl::ubjson { + +Writer::Writer(Encoder* _encoder) : encoder_(_encoder) {} + +Writer::~Writer() = default; + +Writer::OutputArrayType Writer::array_as_root( + const size_t _size) const noexcept { + return new_array(_size); +} + +Writer::OutputObjectType Writer::object_as_root( + const size_t _size) const noexcept { + return new_object(_size); +} + +Writer::OutputVarType Writer::null_as_root() const noexcept { + encoder_->null_value(); + return OutputVarType{}; +} + +Writer::OutputArrayType Writer::add_array_to_array( + const size_t _size, OutputArrayType* _parent) const noexcept { + return new_array(_size); +} + +Writer::OutputArrayType Writer::add_array_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept { + encoder_->key(_name); + return new_array(_size); +} + +Writer::OutputObjectType Writer::add_object_to_array( + const size_t _size, OutputArrayType* _parent) const noexcept { + return new_object(_size); +} + +Writer::OutputObjectType Writer::add_object_to_object( + const std::string_view& _name, const size_t _size, + OutputObjectType* _parent) const noexcept { + encoder_->key(_name); + return new_object(_size); +} + +Writer::OutputVarType Writer::add_null_to_array( + OutputArrayType* _parent) const noexcept { + encoder_->null_value(); + return OutputVarType{}; +} + +Writer::OutputVarType Writer::add_null_to_object( + const std::string_view& _name, OutputObjectType* _parent) const noexcept { + encoder_->key(_name); + encoder_->null_value(); + return OutputVarType{}; +} + +void Writer::end_array(OutputArrayType* _arr) const noexcept { + encoder_->end_array(); +} + +void Writer::end_object(OutputObjectType* _obj) const noexcept { + encoder_->end_object(); +} + +Writer::OutputArrayType Writer::new_array(const size_t _size) const noexcept { + encoder_->begin_array(_size); + return OutputArrayType{}; +} + +Writer::OutputObjectType Writer::new_object(const size_t _size) const noexcept { + encoder_->begin_object(_size); + return OutputObjectType{}; +} + +} // namespace rfl::ubjson diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 867262ee..0ccde04c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,14 +28,18 @@ if (REFLECTCPP_MSGPACK) add_subdirectory(msgpack) endif() -if (REFLECTCPP_XML) - add_subdirectory(xml) -endif() - if (REFLECTCPP_TOML) add_subdirectory(toml) endif() +if (REFLECTCPP_UBJSON) + add_subdirectory(ubjson) +endif() + +if (REFLECTCPP_XML) + add_subdirectory(xml) +endif() + if (REFLECTCPP_YAML) add_subdirectory(yaml) endif() diff --git a/tests/ubjson/CMakeLists.txt b/tests/ubjson/CMakeLists.txt new file mode 100644 index 00000000..160ade8e --- /dev/null +++ b/tests/ubjson/CMakeLists.txt @@ -0,0 +1,20 @@ +project(reflect-cpp-ubjson-tests) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp") + +add_executable( + reflect-cpp-ubjson-tests + ${SOURCES} +) +target_precompile_headers(reflect-cpp-ubjson-tests PRIVATE [["rfl.hpp"]] ) + +target_include_directories(reflect-cpp-ubjson-tests SYSTEM PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include") + +target_link_libraries( + reflect-cpp-ubjson-tests + PRIVATE + "${REFLECT_CPP_GTEST_LIB}" +) + +find_package(GTest) +gtest_discover_tests(reflect-cpp-ubjson-tests) diff --git a/tests/ubjson/test_add_struct_name.cpp b/tests/ubjson/test_add_struct_name.cpp new file mode 100644 index 00000000..3e5238b9 --- /dev/null +++ b/tests/ubjson/test_add_struct_name.cpp @@ -0,0 +1,46 @@ +#include +#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(ubjson, 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/ubjson/test_array.cpp b/tests/ubjson/test_array.cpp new file mode 100644 index 00000000..08308e73 --- /dev/null +++ b/tests/ubjson/test_array.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +// Make sure things still compile when +// rfl.hpp is included after rfl/ubjson.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(ubjson, 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/ubjson/test_box.cpp b/tests/ubjson/test_box.cpp new file mode 100644 index 00000000..2388f964 --- /dev/null +++ b/tests/ubjson/test_box.cpp @@ -0,0 +1,44 @@ +#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(ubjson, 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/ubjson/test_bytestring.cpp b/tests/ubjson/test_bytestring.cpp new file mode 100644 index 00000000..c1c2c4a5 --- /dev/null +++ b/tests/ubjson/test_bytestring.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_bytestring { + +struct TestStruct { + rfl::Bytestring bytestring; +}; + +TEST(ubjson, 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/ubjson/test_combined_processors.cpp b/tests/ubjson/test_combined_processors.cpp new file mode 100644 index 00000000..73224238 --- /dev/null +++ b/tests/ubjson/test_combined_processors.cpp @@ -0,0 +1,49 @@ +#include +#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(ubjson, 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/ubjson/test_custom_class1.cpp b/tests/ubjson/test_custom_class1.cpp new file mode 100644 index 00000000..290abb1a --- /dev/null +++ b/tests/ubjson/test_custom_class1.cpp @@ -0,0 +1,36 @@ +#include +#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(ubjson, test_custom_class1) { + const auto bart = Person("Bart"); + + write_and_read(bart); +} +} // namespace test_custom_class1 diff --git a/tests/ubjson/test_custom_class3.cpp b/tests/ubjson/test_custom_class3.cpp new file mode 100644 index 00000000..380791fd --- /dev/null +++ b/tests/ubjson/test_custom_class3.cpp @@ -0,0 +1,64 @@ +#include +#include +#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(ubjson, test_custom_class3) { + const auto bart = Person("Bart", "Simpson", 10); + + write_and_read(bart); +} + +} // namespace test_custom_class3 diff --git a/tests/ubjson/test_custom_class4.cpp b/tests/ubjson/test_custom_class4.cpp new file mode 100644 index 00000000..74240ec9 --- /dev/null +++ b/tests/ubjson/test_custom_class4.cpp @@ -0,0 +1,65 @@ +#include +#include +#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(ubjson, 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/ubjson/test_default_values.cpp b/tests/ubjson/test_default_values.cpp new file mode 100644 index 00000000..a2e18bb9 --- /dev/null +++ b/tests/ubjson/test_default_values.cpp @@ -0,0 +1,27 @@ +#include +#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(ubjson, 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/ubjson/test_deque.cpp b/tests/ubjson/test_deque.cpp new file mode 100644 index 00000000..0ea6b0fc --- /dev/null +++ b/tests/ubjson/test_deque.cpp @@ -0,0 +1,28 @@ +#include +#include +#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(ubjson, 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/ubjson/test_enum.cpp b/tests/ubjson/test_enum.cpp new file mode 100644 index 00000000..c383f3c7 --- /dev/null +++ b/tests/ubjson/test_enum.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_enum { + +enum class Color { red, green, blue, yellow }; + +struct Circle { + float radius; + Color color; +}; + +TEST(ubjson, test_enum) { + const auto circle = Circle{.radius = 2.0, .color = Color::green}; + + write_and_read(circle); +} + +} // namespace test_enum diff --git a/tests/ubjson/test_extra_fields.cpp b/tests/ubjson/test_extra_fields.cpp new file mode 100644 index 00000000..a8e203c7 --- /dev/null +++ b/tests/ubjson/test_extra_fields.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_extra_fields { + +struct Person { + rfl::Rename<"firstName", std::string> first_name; + rfl::Rename<"lastName", std::string> last_name = "Simpson"; + rfl::ExtraFields extra_fields; +}; + +TEST(ubjson, test_extra_fields) { + auto homer = Person{.first_name = "Homer"}; + + homer.extra_fields["age"] = 45; + homer.extra_fields["email"] = "homer@simpson.com"; + homer.extra_fields["town"] = "Springfield"; + + write_and_read(homer); +} +} // namespace test_extra_fields diff --git a/tests/ubjson/test_field_variant.cpp b/tests/ubjson/test_field_variant.cpp new file mode 100644 index 00000000..496f171b --- /dev/null +++ b/tests/ubjson/test_field_variant.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#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(ubjson, 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/ubjson/test_flag_enum.cpp b/tests/ubjson/test_flag_enum.cpp new file mode 100644 index 00000000..75806f80 --- /dev/null +++ b/tests/ubjson/test_flag_enum.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#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(ubjson, 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/ubjson/test_flag_enum_with_int.cpp b/tests/ubjson/test_flag_enum_with_int.cpp new file mode 100644 index 00000000..ed461e69 --- /dev/null +++ b/tests/ubjson/test_flag_enum_with_int.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#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(ubjson, 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/ubjson/test_flatten.cpp b/tests/ubjson/test_flatten.cpp new file mode 100644 index 00000000..17011232 --- /dev/null +++ b/tests/ubjson/test_flatten.cpp @@ -0,0 +1,33 @@ +#include +#include +#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(ubjson, 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/ubjson/test_flatten_anonymous.cpp b/tests/ubjson/test_flatten_anonymous.cpp new file mode 100644 index 00000000..2211045a --- /dev/null +++ b/tests/ubjson/test_flatten_anonymous.cpp @@ -0,0 +1,34 @@ +#include +#include +#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(ubjson, 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/ubjson/test_forward_list.cpp b/tests/ubjson/test_forward_list.cpp new file mode 100644 index 00000000..86546394 --- /dev/null +++ b/tests/ubjson/test_forward_list.cpp @@ -0,0 +1,28 @@ +#include +#include +#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(ubjson, 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/ubjson/test_literal.cpp b/tests/ubjson/test_literal.cpp new file mode 100644 index 00000000..84b3e499 --- /dev/null +++ b/tests/ubjson/test_literal.cpp @@ -0,0 +1,25 @@ +#include +#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(ubjson, test_literal) { + const auto bart = Person{.first_name = FirstName::make<"Bart">()}; + + write_and_read(bart); +} +} // namespace test_literal diff --git a/tests/ubjson/test_literal_map.cpp b/tests/ubjson/test_literal_map.cpp new file mode 100644 index 00000000..65cd54cc --- /dev/null +++ b/tests/ubjson/test_literal_map.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_literal_map { + +using FieldName = rfl::Literal<"firstName", "lastName">; + +TEST(ubjson, 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/ubjson/test_map.cpp b/tests/ubjson/test_map.cpp new file mode 100644 index 00000000..63533005 --- /dev/null +++ b/tests/ubjson/test_map.cpp @@ -0,0 +1,27 @@ +#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::map children; +}; + +TEST(ubjson, 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/ubjson/test_map_with_key_validation.cpp b/tests/ubjson/test_map_with_key_validation.cpp new file mode 100644 index 00000000..ee9f08e6 --- /dev/null +++ b/tests/ubjson/test_map_with_key_validation.cpp @@ -0,0 +1,28 @@ +#include +#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(ubjson, 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/ubjson/test_monster_example.cpp b/tests/ubjson/test_monster_example.cpp new file mode 100644 index 00000000..09c19dec --- /dev/null +++ b/tests/ubjson/test_monster_example.cpp @@ -0,0 +1,59 @@ +#include +#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(ubjson, 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/ubjson/test_readme_example.cpp b/tests/ubjson/test_readme_example.cpp new file mode 100644 index 00000000..f8fc7a58 --- /dev/null +++ b/tests/ubjson/test_readme_example.cpp @@ -0,0 +1,46 @@ +#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(ubjson, 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/ubjson/test_readme_example2.cpp b/tests/ubjson/test_readme_example2.cpp new file mode 100644 index 00000000..80ddc01f --- /dev/null +++ b/tests/ubjson/test_readme_example2.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_readme_example2 { + +struct Person { + std::string first_name; + std::string last_name; + int age; +}; + +TEST(ubjson, 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/ubjson/test_ref.cpp b/tests/ubjson/test_ref.cpp new file mode 100644 index 00000000..254d0743 --- /dev/null +++ b/tests/ubjson/test_ref.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#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(ubjson, 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/ubjson/test_save_load.cpp b/tests/ubjson/test_save_load.cpp new file mode 100644 index 00000000..4fff2b24 --- /dev/null +++ b/tests/ubjson/test_save_load.cpp @@ -0,0 +1,61 @@ +#include +#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(ubjson, 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::ubjson::save("homer.ubjson", homer1); + + const auto homer2 = rfl::ubjson::load("homer.ubjson").value(); + + const auto string1 = rfl::ubjson::write(homer1); + const auto string2 = rfl::ubjson::write(homer2); + + EXPECT_EQ(string1, string2); +} +} // namespace test_save_load diff --git a/tests/ubjson/test_set.cpp b/tests/ubjson/test_set.cpp new file mode 100644 index 00000000..f0e94e73 --- /dev/null +++ b/tests/ubjson/test_set.cpp @@ -0,0 +1,25 @@ +#include +#include +#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(ubjson, 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/ubjson/test_size.cpp b/tests/ubjson/test_size.cpp new file mode 100644 index 00000000..4c0e46c0 --- /dev/null +++ b/tests/ubjson/test_size.cpp @@ -0,0 +1,37 @@ +#include +#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(ubjson, 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/ubjson/test_snake_case_to_camel_case.cpp b/tests/ubjson/test_snake_case_to_camel_case.cpp new file mode 100644 index 00000000..9a5edbfa --- /dev/null +++ b/tests/ubjson/test_snake_case_to_camel_case.cpp @@ -0,0 +1,34 @@ +#include +#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(ubjson, 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/ubjson/test_snake_case_to_pascal_case.cpp b/tests/ubjson/test_snake_case_to_pascal_case.cpp new file mode 100644 index 00000000..e56baf30 --- /dev/null +++ b/tests/ubjson/test_snake_case_to_pascal_case.cpp @@ -0,0 +1,34 @@ +#include +#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(ubjson, 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/ubjson/test_string_map.cpp b/tests/ubjson/test_string_map.cpp new file mode 100644 index 00000000..3a07a821 --- /dev/null +++ b/tests/ubjson/test_string_map.cpp @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_string_map { +TEST(ubjson, 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/ubjson/test_tagged_union.cpp b/tests/ubjson/test_tagged_union.cpp new file mode 100644 index 00000000..9af0678c --- /dev/null +++ b/tests/ubjson/test_tagged_union.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#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(ubjson, test_tagged_union) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union diff --git a/tests/ubjson/test_tagged_union2.cpp b/tests/ubjson/test_tagged_union2.cpp new file mode 100644 index 00000000..779a58fb --- /dev/null +++ b/tests/ubjson/test_tagged_union2.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union2 { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = rfl::TaggedUnion<"shape", Circle, Square, Rectangle>; + +TEST(ubjson, test_tagged_union2) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union2 diff --git a/tests/ubjson/test_timestamp.cpp b/tests/ubjson/test_timestamp.cpp new file mode 100644 index 00000000..7805e30e --- /dev/null +++ b/tests/ubjson/test_timestamp.cpp @@ -0,0 +1,31 @@ +#include +#include +#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(ubjson, 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/ubjson/test_unique_ptr.cpp b/tests/ubjson/test_unique_ptr.cpp new file mode 100644 index 00000000..0e4ca7dc --- /dev/null +++ b/tests/ubjson/test_unique_ptr.cpp @@ -0,0 +1,28 @@ +#include +#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(ubjson, 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/ubjson/test_unique_ptr2.cpp b/tests/ubjson/test_unique_ptr2.cpp new file mode 100644 index 00000000..a23c8b78 --- /dev/null +++ b/tests/ubjson/test_unique_ptr2.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#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(ubjson, 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/ubjson/test_variant.cpp b/tests/ubjson/test_variant.cpp new file mode 100644 index 00000000..763b1803 --- /dev/null +++ b/tests/ubjson/test_variant.cpp @@ -0,0 +1,31 @@ +#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(ubjson, test_variant) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + + write_and_read(r); +} +} // namespace test_variant diff --git a/tests/ubjson/test_wstring.cpp b/tests/ubjson/test_wstring.cpp new file mode 100644 index 00000000..2c3bb625 --- /dev/null +++ b/tests/ubjson/test_wstring.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +struct TestStruct { + std::string theNormalString; + std::wstring theWiderString; +}; + +namespace test_wstring { +TEST(ubjson, 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/ubjson/write_and_read.hpp b/tests/ubjson/write_and_read.hpp new file mode 100644 index 00000000..a7f0c84f --- /dev/null +++ b/tests/ubjson/write_and_read.hpp @@ -0,0 +1,20 @@ +#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::ubjson::write(_struct); + const auto res = rfl::ubjson::read(serialized1); + EXPECT_TRUE(res && true) << "Test failed on read. Error: " + << res.error().value().what(); + const auto serialized2 = rfl::ubjson::write(res.value()); + EXPECT_EQ(serialized1, serialized2); +} +#endif diff --git a/vcpkg.json b/vcpkg.json index b5dd7cb3..c50d0041 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,6 +15,10 @@ "name": "libbson", "version>=": "1.25.1" }, + { + "name": "jsoncons", + "version>=": "0.176.0" + }, { "name": "msgpack-c", "version>=": "6.0.0"