diff --git a/benchmarks/all/canada_read.cpp b/benchmarks/all/canada_read.cpp index a412a2b6..efaf2471 100644 --- a/benchmarks/all/canada_read.cpp +++ b/benchmarks/all/canada_read.cpp @@ -68,6 +68,19 @@ static void BM_canada_read_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_cbor); +static void BM_canada_read_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { + const auto data = rfl::cbor::write(load_data()); + for (auto _ : state) { + const auto res = + rfl::cbor::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_cbor_without_field_names); + static void BM_canada_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { @@ -79,6 +92,19 @@ static void BM_canada_read_reflect_cpp_flexbuf(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_flexbuf); +static void BM_canada_read_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = rfl::flexbuf::write(load_data()); + for (auto _ : state) { + const auto res = + rfl::flexbuf::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_flexbuf_without_field_names); + static void BM_canada_read_reflect_cpp_json(benchmark::State &state) { const auto data = rfl::json::write(load_data()); for (auto _ : state) { @@ -90,6 +116,19 @@ static void BM_canada_read_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_json); +static void BM_canada_read_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = rfl::json::write(load_data()); + for (auto _ : state) { + const auto res = + rfl::json::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_json_without_field_names); + static void BM_canada_read_reflect_cpp_msgpack(benchmark::State &state) { const auto data = rfl::msgpack::write(load_data()); for (auto _ : state) { @@ -101,6 +140,19 @@ static void BM_canada_read_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_canada_read_reflect_cpp_msgpack); +static void BM_canada_read_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = rfl::msgpack::write(load_data()); + for (auto _ : state) { + const auto res = + rfl::msgpack::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_canada_read_reflect_cpp_msgpack_without_field_names); + static void BM_canada_read_reflect_cpp_toml(benchmark::State &state) { const auto data = rfl::toml::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/canada_write.cpp b/benchmarks/all/canada_write.cpp index 95d0a007..4e7bdb11 100644 --- a/benchmarks/all/canada_write.cpp +++ b/benchmarks/all/canada_write.cpp @@ -68,16 +68,29 @@ static void BM_canada_write_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_cbor); -static void BM_canada_write_reflect_cpp_flexbuf(benchmark::State &state) { +static void BM_canada_write_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { - const auto output = rfl::flexbuf::write(data); + const auto output = rfl::cbor::write(data); if (output.size() == 0) { std::cout << "No output" << std::endl; } } } -BENCHMARK(BM_canada_write_reflect_cpp_flexbuf); +BENCHMARK(BM_canada_write_reflect_cpp_cbor_without_field_names); + +static void BM_canada_write_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::flexbuf::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_flexbuf_without_field_names); static void BM_canada_write_reflect_cpp_json(benchmark::State &state) { const auto data = load_data(); @@ -90,6 +103,18 @@ static void BM_canada_write_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_json); +static void BM_canada_write_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::json::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_json_without_field_names); + static void BM_canada_write_reflect_cpp_msgpack(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -101,6 +126,18 @@ static void BM_canada_write_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_canada_write_reflect_cpp_msgpack); +static void BM_canada_write_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::msgpack::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_canada_write_reflect_cpp_msgpack_without_field_names); + static void BM_canada_write_reflect_cpp_toml(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 77a32973..9926c3b4 100644 --- a/benchmarks/all/licenses_read.cpp +++ b/benchmarks/all/licenses_read.cpp @@ -84,6 +84,18 @@ static void BM_licenses_read_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_cbor); +static void BM_licenses_read_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { + const auto data = rfl::cbor::write(load_data()); + for (auto _ : state) { + const auto res = rfl::cbor::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_cbor_without_field_names); + static void BM_licenses_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { @@ -95,6 +107,18 @@ static void BM_licenses_read_reflect_cpp_flexbuf(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_flexbuf); +static void BM_licenses_read_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = rfl::flexbuf::write(load_data()); + for (auto _ : state) { + const auto res = rfl::flexbuf::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_flexbuf_without_field_names); + static void BM_licenses_read_reflect_cpp_json(benchmark::State &state) { const auto data = rfl::json::write(load_data()); for (auto _ : state) { @@ -106,6 +130,18 @@ static void BM_licenses_read_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_json); +static void BM_licenses_read_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = rfl::json::write(load_data()); + for (auto _ : state) { + const auto res = rfl::json::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_json_without_field_names); + static void BM_licenses_read_reflect_cpp_msgpack(benchmark::State &state) { const auto data = rfl::msgpack::write(load_data()); for (auto _ : state) { @@ -117,6 +153,18 @@ static void BM_licenses_read_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_licenses_read_reflect_cpp_msgpack); +static void BM_licenses_read_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = rfl::msgpack::write(load_data()); + for (auto _ : state) { + const auto res = rfl::msgpack::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_licenses_read_reflect_cpp_msgpack_without_field_names); + static void BM_licenses_read_reflect_cpp_xml(benchmark::State &state) { const auto data = rfl::xml::write<"license">(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/licenses_write.cpp b/benchmarks/all/licenses_write.cpp index 111ddcb3..db6bef5f 100644 --- a/benchmarks/all/licenses_write.cpp +++ b/benchmarks/all/licenses_write.cpp @@ -84,6 +84,18 @@ static void BM_licenses_write_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_cbor); +static void BM_licenses_write_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::cbor::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_cbor_without_field_names); + static void BM_licenses_write_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -95,6 +107,18 @@ static void BM_licenses_write_reflect_cpp_flexbuf(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_flexbuf); +static void BM_licenses_write_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::flexbuf::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_flexbuf_without_field_names); + static void BM_licenses_write_reflect_cpp_json(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -106,6 +130,18 @@ static void BM_licenses_write_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_json); +static void BM_licenses_write_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::json::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_json_without_field_names); + static void BM_licenses_write_reflect_cpp_msgpack(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -117,6 +153,18 @@ static void BM_licenses_write_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_licenses_write_reflect_cpp_msgpack); +static void BM_licenses_write_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::msgpack::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_licenses_write_reflect_cpp_msgpack_without_field_names); + static void BM_licenses_write_reflect_cpp_toml(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 3649bbd6..7955b586 100644 --- a/benchmarks/all/person_read.cpp +++ b/benchmarks/all/person_read.cpp @@ -64,6 +64,18 @@ static void BM_person_read_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_cbor); +static void BM_person_read_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { + const auto data = rfl::cbor::write(load_data()); + for (auto _ : state) { + const auto res = rfl::cbor::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_cbor_without_field_names); + static void BM_person_read_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = rfl::flexbuf::write(load_data()); for (auto _ : state) { @@ -75,6 +87,18 @@ static void BM_person_read_reflect_cpp_flexbuf(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_flexbuf); +static void BM_person_read_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = rfl::flexbuf::write(load_data()); + for (auto _ : state) { + const auto res = rfl::flexbuf::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_flexbuf_without_field_names); + static void BM_person_read_reflect_cpp_json(benchmark::State &state) { const auto data = rfl::json::write(load_data()); for (auto _ : state) { @@ -86,6 +110,18 @@ static void BM_person_read_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_json); +static void BM_person_read_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = rfl::json::write(load_data()); + for (auto _ : state) { + const auto res = rfl::json::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_json_without_field_names); + static void BM_person_read_reflect_cpp_msgpack(benchmark::State &state) { const auto data = rfl::msgpack::write(load_data()); for (auto _ : state) { @@ -97,6 +133,18 @@ static void BM_person_read_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_person_read_reflect_cpp_msgpack); +static void BM_person_read_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = rfl::msgpack::write(load_data()); + for (auto _ : state) { + const auto res = rfl::msgpack::read(data); + if (!res) { + std::cout << res.error()->what() << std::endl; + } + } +} +BENCHMARK(BM_person_read_reflect_cpp_msgpack_without_field_names); + static void BM_person_read_reflect_cpp_toml(benchmark::State &state) { const auto data = rfl::toml::write(load_data()); for (auto _ : state) { diff --git a/benchmarks/all/person_write.cpp b/benchmarks/all/person_write.cpp index d9405ce9..2e34e8f3 100644 --- a/benchmarks/all/person_write.cpp +++ b/benchmarks/all/person_write.cpp @@ -64,6 +64,18 @@ static void BM_person_write_reflect_cpp_cbor(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_cbor); +static void BM_person_write_reflect_cpp_cbor_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::cbor::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_cbor_without_field_names); + static void BM_person_write_reflect_cpp_flexbuf(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -75,6 +87,18 @@ static void BM_person_write_reflect_cpp_flexbuf(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_flexbuf); +static void BM_person_write_reflect_cpp_flexbuf_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::flexbuf::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_flexbuf_without_field_names); + static void BM_person_write_reflect_cpp_json(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -86,6 +110,18 @@ static void BM_person_write_reflect_cpp_json(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_json); +static void BM_person_write_reflect_cpp_json_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::json::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_json_without_field_names); + static void BM_person_write_reflect_cpp_msgpack(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { @@ -97,6 +133,18 @@ static void BM_person_write_reflect_cpp_msgpack(benchmark::State &state) { } BENCHMARK(BM_person_write_reflect_cpp_msgpack); +static void BM_person_write_reflect_cpp_msgpack_without_field_names( + benchmark::State &state) { + const auto data = load_data(); + for (auto _ : state) { + const auto output = rfl::msgpack::write(data); + if (output.size() == 0) { + std::cout << "No output" << std::endl; + } + } +} +BENCHMARK(BM_person_write_reflect_cpp_msgpack_without_field_names); + static void BM_person_write_reflect_cpp_toml(benchmark::State &state) { const auto data = load_data(); for (auto _ : state) { diff --git a/docs/processors.md b/docs/processors.md index 28650e6d..9f6447e8 100644 --- a/docs/processors.md +++ b/docs/processors.md @@ -56,6 +56,34 @@ The resulting JSON string looks like this: {"type":"Person","first_name":"Homer","last_name":"Simpson","age":45} ``` +### `rfl::NoFieldNames` + +We can also remove the field names altogether: + +```cpp +const auto json_string = + rfl::json::write(homer); + +const auto homer2 = + rfl::json::read(json_string).value(); +``` + +The resulting JSON string looks like this: + +```json +["Homer","Simpson",45] +``` + +This is particularly relevant for binary formats, which do not emphasize readability, +like msgpack or flexbuffers. Removing the field names can reduce the size of the +resulting bytestrings and significantly speed up read and write time, +depending on the dataset. + +However, it makes it more difficult to maintain backwards compatability. + +Note that `rfl::NoFieldNames` is not supported for BSON, TOML, XML, or YAML, due +to limitations of these formats. + ### `rfl::NoOptionals` As we have seen in the section on optional fields, when a `std::optional` is diff --git a/docs/supporting_your_own_format.md b/docs/supporting_your_own_format.md index 24deb053..39557f7a 100644 --- a/docs/supporting_your_own_format.md +++ b/docs/supporting_your_own_format.md @@ -179,9 +179,14 @@ struct Reader { template static constexpr bool has_custom_constructor = false; + /// Retrieves a particular field from an array. + /// Returns an rfl::Error if the index is out of bounds. + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType _arr) const noexcept {...} + /// Retrieves a particular field from an object. /// Returns an rfl::Error if the field cannot be found. - rfl::Result get_field( + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept {...} /// Determines whether a variable is empty (the NULL type). diff --git a/docs/variants_and_tagged_unions.md b/docs/variants_and_tagged_unions.md index 70987e50..dc878330 100644 --- a/docs/variants_and_tagged_unions.md +++ b/docs/variants_and_tagged_unions.md @@ -153,7 +153,9 @@ const auto r2 = rfl::json::read(json_string); ``` Note that in this case the type of the field `shape` MUST be `rfl::Literal`. -Also note that this is exactly how tagged unions work in Pydantic. +Also note that this is exactly how tagged unions work in Pydantic. When you +use the `rfl::NoFieldNames` processor, the tag MUST always be the first entry +of the array. ## `std::variant` or `rfl::Variant` (externally tagged) diff --git a/include/rfl.hpp b/include/rfl.hpp index 4ba2f7cc..fda238d7 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -17,6 +17,7 @@ #include "rfl/Flatten.hpp" #include "rfl/Literal.hpp" #include "rfl/NamedTuple.hpp" +#include "rfl/NoFieldNames.hpp" #include "rfl/NoOptionals.hpp" #include "rfl/OneOf.hpp" #include "rfl/Pattern.hpp" diff --git a/include/rfl/NoFieldNames.hpp b/include/rfl/NoFieldNames.hpp new file mode 100644 index 00000000..91677abe --- /dev/null +++ b/include/rfl/NoFieldNames.hpp @@ -0,0 +1,18 @@ +#ifndef RFL_NOFIELDNAMES_HPP_ +#define RFL_NOFIELDNAMES_HPP_ + +namespace rfl { + +/// This is a "fake" processor - it doesn't do much in itself, but its +/// inclusion instructs the parsers to strip field names. +struct NoFieldNames { + public: + template + static auto process(auto&& _named_tuple) { + return _named_tuple; + } +}; + +} // namespace rfl + +#endif diff --git a/include/rfl/Processors.hpp b/include/rfl/Processors.hpp index 91e08e5a..302e0c24 100644 --- a/include/rfl/Processors.hpp +++ b/include/rfl/Processors.hpp @@ -3,6 +3,7 @@ #include +#include "internal/is_no_field_names_v.hpp" #include "internal/is_no_optionals_v.hpp" namespace rfl { @@ -13,6 +14,7 @@ struct Processors; template <> struct Processors<> { static constexpr bool all_required_ = false; + static constexpr bool no_field_names_ = false; template static auto process(NamedTupleType&& _named_tuple) { @@ -26,6 +28,10 @@ struct Processors { std::disjunction_v, internal::is_no_optionals...>; + static constexpr bool no_field_names_ = + std::disjunction_v, + internal::is_no_field_names...>; + template static auto process(NamedTupleType&& _named_tuple) { return Processors::template process( diff --git a/include/rfl/bson/Reader.hpp b/include/rfl/bson/Reader.hpp index 993b6209..0203300c 100644 --- a/include/rfl/bson/Reader.hpp +++ b/include/rfl/bson/Reader.hpp @@ -47,7 +47,30 @@ struct Reader { T::from_bson_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + bson_t b; + bson_iter_t iter; + const auto doc = _arr.val_->value.v_doc; + if (bson_init_static(&b, doc.data, doc.data_len)) { + if (bson_iter_init(&iter, &b)) { + size_t i = 0; + while (bson_iter_next(&iter)) { + if (i == _idx) { + return to_input_var(&iter); + } + ++i; + } + } else { + return Error("Could not init the array iteration."); + } + } else { + return Error("Could not init array."); + } + return Error("Index " + std::to_string(_idx) + " of of bounds."); + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { bson_t b; bson_iter_t iter; diff --git a/include/rfl/bson/read.hpp b/include/rfl/bson/read.hpp index 8d738209..7ef58a94 100644 --- a/include/rfl/bson/read.hpp +++ b/include/rfl/bson/read.hpp @@ -21,7 +21,11 @@ using InputVarType = typename Reader::InputVarType; template Result> read(const InputVarType& _obj) { const auto r = Reader(); - return Parser>::read(r, _obj); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + return Parser::read(r, _obj); } /// Parses an BSON object using reflection. diff --git a/include/rfl/bson/write.hpp b/include/rfl/bson/write.hpp index a22e7503..db94cb88 100644 --- a/include/rfl/bson/write.hpp +++ b/include/rfl/bson/write.hpp @@ -28,8 +28,12 @@ std::pair to_buffer(const auto& _obj) noexcept { bson_writer_new(&buf, &buflen, 0, bson_realloc_ctx, NULL); bson_writer_begin(bson_writer, &doc); const auto rfl_writer = Writer(doc); - Parser>::write(rfl_writer, _obj, - typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(rfl_writer, _obj, + typename ParentType::Root{}); bson_writer_end(bson_writer); const auto len = bson_writer_get_length(bson_writer); bson_writer_destroy(bson_writer); diff --git a/include/rfl/cbor/Parser.hpp b/include/rfl/cbor/Parser.hpp index 811978d6..8ca2b68f 100644 --- a/include/rfl/cbor/Parser.hpp +++ b/include/rfl/cbor/Parser.hpp @@ -16,10 +16,12 @@ requires AreReaderAndWriter> struct Parser, ProcessorsType> - : public NamedTupleParser { + : public NamedTupleParser< + cbor::Reader, cbor::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> { }; template diff --git a/include/rfl/cbor/Reader.hpp b/include/rfl/cbor/Reader.hpp index 5b364d5d..417186dc 100644 --- a/include/rfl/cbor/Reader.hpp +++ b/include/rfl/cbor/Reader.hpp @@ -47,7 +47,31 @@ struct Reader { T::from_cbor_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + InputVarType var; + auto err = cbor_value_enter_container(&_arr.val_, &var.val_); + if (err != CborNoError && err != CborErrorOutOfMemory) { + return Error(cbor_error_string(err)); + } + size_t length = 0; + err = cbor_value_get_array_length(&_arr.val_, &length); + if (err != CborNoError && err != CborErrorOutOfMemory) { + return Error(cbor_error_string(err)); + } + if (_idx >= length) { + return Error("Index " + std::to_string(_idx) + " of of bounds."); + } + for (size_t i = 0; i < _idx; ++i) { + err = cbor_value_advance(&var.val_); + if (err != CborNoError && err != CborErrorOutOfMemory) { + return Error(cbor_error_string(err)); + } + } + return var; + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { InputVarType var; auto buffer = std::vector(); diff --git a/include/rfl/flexbuf/Reader.hpp b/include/rfl/flexbuf/Reader.hpp index 16a2de04..8c601403 100644 --- a/include/rfl/flexbuf/Reader.hpp +++ b/include/rfl/flexbuf/Reader.hpp @@ -41,7 +41,15 @@ struct Reader { template static constexpr bool has_custom_constructor = has_from_flexbuf::value; - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + if (_idx >= _arr.size()) { + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + return _arr[_idx]; + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { const auto keys = _obj.Keys(); for (size_t i = 0; i < keys.size(); ++i) { diff --git a/include/rfl/internal/is_no_field_names_v.hpp b/include/rfl/internal/is_no_field_names_v.hpp new file mode 100644 index 00000000..6fea2bcd --- /dev/null +++ b/include/rfl/internal/is_no_field_names_v.hpp @@ -0,0 +1,29 @@ +#ifndef RFL_INTERNAL_ISNOFIELDNAMES_HPP_ +#define RFL_INTERNAL_ISNOFIELDNAMES_HPP_ + +#include +#include +#include + +#include "../NoFieldNames.hpp" + +namespace rfl { +namespace internal { + +template +class is_no_field_names; + +template +class is_no_field_names : public std::false_type {}; + +template <> +class is_no_field_names : public std::true_type {}; + +template +constexpr bool is_no_field_names_v = + is_no_field_names>>::value; + +} // namespace internal +} // namespace rfl + +#endif diff --git a/include/rfl/json/Reader.hpp b/include/rfl/json/Reader.hpp index 4bf6c1a0..8d885b4f 100644 --- a/include/rfl/json/Reader.hpp +++ b/include/rfl/json/Reader.hpp @@ -52,7 +52,16 @@ struct Reader { T::from_json_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType _arr) const noexcept { + const auto var = InputVarType(yyjson_arr_get(_arr.val_, _idx)); + if (!var.val_) { + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + return var; + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType _obj) const noexcept { const auto var = InputVarType(yyjson_obj_get(_obj.val_, _name.c_str())); if (!var.val_) { diff --git a/include/rfl/msgpack/Parser.hpp b/include/rfl/msgpack/Parser.hpp index 79da146b..7caea76b 100644 --- a/include/rfl/msgpack/Parser.hpp +++ b/include/rfl/msgpack/Parser.hpp @@ -16,10 +16,12 @@ requires AreReaderAndWriter> struct Parser, ProcessorsType> - : public NamedTupleParser { + : public NamedTupleParser< + msgpack::Reader, msgpack::Writer, + /*_ignore_empty_containers=*/false, + /*_all_required=*/true, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> { }; template diff --git a/include/rfl/msgpack/Reader.hpp b/include/rfl/msgpack/Reader.hpp index 004251d8..eae50b10 100644 --- a/include/rfl/msgpack/Reader.hpp +++ b/include/rfl/msgpack/Reader.hpp @@ -34,7 +34,15 @@ struct Reader { T::from_msgpack_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType _arr) const noexcept { + if (_idx >= _arr.size) { + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + return _arr.ptr[_idx]; + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { for (uint32_t i = 0; i < _obj.size; ++i) { const auto& key = _obj.ptr[i].key; diff --git a/include/rfl/parsing/IsReader.hpp b/include/rfl/parsing/IsReader.hpp index 4d03b1c6..bdc80c53 100644 --- a/include/rfl/parsing/IsReader.hpp +++ b/include/rfl/parsing/IsReader.hpp @@ -36,7 +36,7 @@ concept IsReader = requires(R r, std::string name, MockObjectReader object_reader, typename R::InputArrayType arr, typename R::InputObjectType obj, - typename R::InputVarType var) { + typename R::InputVarType var, size_t idx) { /// Any Reader needs to define the following: /// /// 1) An InputArrayType, which must be an array-like data structure. @@ -48,9 +48,14 @@ concept IsReader = requires(R r, std::string name, /// whether the class in question as a custom constructor, which might /// be called something like from_json_obj(...). + /// Retrieves a particular field from an array. + { + r.get_field_from_array(idx, arr) + } -> std::same_as>; + /// Retrieves a particular field from an object. { - r.get_field(name, obj) + r.get_field_from_object(name, obj) } -> std::same_as>; /// Determines whether a variable is empty (the NULL type). diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index b38f2878..bc8841b3 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -22,6 +22,7 @@ #include "Parent.hpp" #include "Parser_base.hpp" #include "ViewReader.hpp" +#include "ViewReaderWithStrippedFieldNames.hpp" #include "is_empty.hpp" #include "is_required.hpp" #include "schema/Type.hpp" @@ -31,19 +32,31 @@ namespace rfl { namespace parsing { template + bool _no_field_names, class ProcessorsType, class... FieldTypes> requires AreReaderAndWriter> struct NamedTupleParser { - using InputObjectType = typename R::InputObjectType; using InputVarType = typename R::InputVarType; - - using OutputObjectType = typename W::OutputObjectType; using OutputVarType = typename W::OutputVarType; using ParentType = Parent; using NamedTupleType = NamedTuple; + using ViewReaderType = std::conditional_t< + _no_field_names, + ViewReaderWithStrippedFieldNames, + ViewReader>; + + using InputObjectOrArrayType = + std::conditional_t<_no_field_names, typename R::InputArrayType, + typename R::InputObjectType>; + using OutputObjectOrArrayType = + std::conditional_t<_no_field_names, typename W::OutputArrayType, + typename W::OutputObjectType>; + + using SchemaType = std::conditional_t<_no_field_names, schema::Type::Tuple, + schema::Type::Object>; + static constexpr size_t size_ = NamedTupleType::size(); public: @@ -70,58 +83,55 @@ struct NamedTupleParser { static std::optional read_view( const R& _r, const InputVarType& _var, NamedTuple* _view) noexcept { - auto obj = _r.to_object(_var); - if (!obj) [[unlikely]] { - return obj.error(); + if constexpr (_no_field_names) { + auto arr = _r.to_array(_var); + if (!arr) [[unlikely]] { + return arr.error(); + } + return read_object_or_array(_r, *arr, _view); + } else { + auto obj = _r.to_object(_var); + if (!obj) [[unlikely]] { + return obj.error(); + } + return read_object_or_array(_r, *obj, _view); } - return read_object(_r, *obj, _view); } - /// For writing, we do not need to make the distinction between - /// default-constructible and non-default constructible fields. template static void write(const W& _w, const NamedTuple& _tup, const P& _parent) noexcept { - auto obj = ParentType::add_object(_w, _tup.size(), _parent); - build_object(_w, _tup, &obj, std::make_integer_sequence()); - _w.end_object(&obj); + if constexpr (_no_field_names) { + auto arr = ParentType::add_array(_w, _tup.size(), _parent); + build_object(_w, _tup, &arr, std::make_integer_sequence()); + _w.end_array(&arr); + } else { + auto obj = ParentType::add_object(_w, _tup.size(), _parent); + build_object(_w, _tup, &obj, std::make_integer_sequence()); + _w.end_object(&obj); + } } - /// For generating the schema, we also do not need to make the distinction - /// between default-constructible and non-default constructible fields. - template static schema::Type to_schema( - std::map* _definitions, - std::map _values = {}) { - using Type = schema::Type; - using T = NamedTuple; - constexpr size_t size = T::size(); - if constexpr (_i == size) { - return Type{Type::Object{_values}}; - } else { - using F = - std::tuple_element_t<_i, typename NamedTuple::Fields>; - using U = typename F::Type; - if constexpr (!internal::is_skip_v) { - _values[std::string(F::name())] = - Parser::to_schema(_definitions); - } - return to_schema<_i + 1>(_definitions, _values); - } - }; + std::map* _definitions) noexcept { + SchemaType schema; + build_schema(_definitions, &schema, + std::make_integer_sequence()); + return schema::Type{schema}; + } private: template static void add_field_to_object(const W& _w, const NamedTuple& _tup, - OutputObjectType* _ptr) noexcept { + OutputObjectOrArrayType* _ptr) noexcept { using FieldType = typename std::tuple_element<_i, std::tuple>::type; using ValueType = std::remove_cvref_t; const auto& value = rfl::get<_i>(_tup); constexpr auto name = FieldType::name_.string_view(); - const auto new_parent = typename ParentType::Object{name, _ptr}; - if constexpr (!_all_required && + const auto new_parent = make_parent(name, _ptr); + if constexpr (!_all_required && !_no_field_names && !is_required()) { if (!is_empty(value)) { if constexpr (internal::is_attribute_v) { @@ -141,13 +151,37 @@ struct NamedTupleParser { } } + template + static void add_field_to_schema( + std::map* _definitions, + SchemaType* _schema) noexcept { + using F = + std::tuple_element_t<_i, typename NamedTuple::Fields>; + using U = typename F::Type; + if constexpr (!internal::is_skip_v) { + auto s = Parser::to_schema(_definitions); + if constexpr (_no_field_names) { + _schema->types_.emplace_back(std::move(s)); + } else { + _schema->types_[std::string(F::name())] = std::move(s); + } + } + }; + template static void build_object(const W& _w, const NamedTuple& _tup, - OutputObjectType* _ptr, + OutputObjectOrArrayType* _ptr, std::integer_sequence) noexcept { (add_field_to_object<_is>(_w, _tup, _ptr), ...); } + template + static void build_schema(std::map* _definitions, + SchemaType* _schema, + std::integer_sequence) noexcept { + (add_field_to_schema<_is>(_definitions, _schema), ...); + } + /// Generates error messages for when fields are missing. template static void handle_one_missing_field(const std::array& _found, @@ -186,24 +220,37 @@ struct NamedTupleParser { (handle_one_missing_field<_is>(_found, _view, _set, _errors), ...); } - static std::optional read_object(const R& _r, - const InputObjectType& _obj, - NamedTupleType* _view) noexcept { + static auto make_parent(const std::string_view& _name, + OutputObjectOrArrayType* _ptr) { + if constexpr (_no_field_names) { + return typename ParentType::Array{_ptr}; + } else { + return typename ParentType::Object{_name, _ptr}; + } + } + + static std::optional read_object_or_array( + const R& _r, const InputObjectOrArrayType& _obj_or_arr, + NamedTupleType* _view) noexcept { auto found = std::array(); found.fill(false); auto set = std::array(); set.fill(false); std::vector errors; - const auto object_reader = ViewReader( - &_r, _view, &found, &set, &errors); - const auto err = _r.read_object(object_reader, _obj); + const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); + std::optional err; + if constexpr (_no_field_names) { + err = _r.read_array(reader, _obj_or_arr); + } else { + err = _r.read_object(reader, _obj_or_arr); + } if (err) { - return *err; + return err; } handle_missing_fields(found, *_view, &set, &errors, std::make_integer_sequence()); if (errors.size() != 0) { - object_reader.call_destructors_where_necessary(); + reader.call_destructors_where_necessary(); return to_single_error_message(errors); } return std::nullopt; diff --git a/include/rfl/parsing/Parser_named_tuple.hpp b/include/rfl/parsing/Parser_named_tuple.hpp index 0251fe0c..3041e45f 100644 --- a/include/rfl/parsing/Parser_named_tuple.hpp +++ b/include/rfl/parsing/Parser_named_tuple.hpp @@ -10,9 +10,11 @@ namespace parsing { template requires AreReaderAndWriter> struct Parser, ProcessorsType> - : public NamedTupleParser { + : public NamedTupleParser< + R, W, /*_ignore_empty_containers=*/false, + /*_all_required=*/ProcessorsType::all_required_, + /*_no_field_names=*/ProcessorsType::no_field_names_, ProcessorsType, + FieldTypes...> { }; } // namespace parsing diff --git a/include/rfl/parsing/Parser_tagged_union.hpp b/include/rfl/parsing/Parser_tagged_union.hpp index 5aa5ea0e..9fbc889c 100644 --- a/include/rfl/parsing/Parser_tagged_union.hpp +++ b/include/rfl/parsing/Parser_tagged_union.hpp @@ -8,9 +8,12 @@ #include "../TaggedUnion.hpp" #include "../always_false.hpp" #include "../internal/strings/join.hpp" +#include "../named_tuple_t.hpp" #include "Parser_base.hpp" #include "TaggedUnionWrapper.hpp" +#include "is_tagged_union_wrapper.hpp" #include "schema/Type.hpp" +#include "tagged_union_wrapper_no_ptr.hpp" namespace rfl { namespace parsing { @@ -30,16 +33,30 @@ struct Parser, using OutputObjectType = typename W::OutputObjectType; using OutputVarType = typename W::OutputVarType; + constexpr static bool no_field_names_ = ProcessorsType::no_field_names_; + + using InputObjectOrArrayType = + std::conditional_t; + static ResultType read(const R& _r, const InputVarType& _var) noexcept { - const auto get_disc = [&_r](auto _obj) { - return get_discriminator(_r, _obj); + const auto get_disc = + [&_r](InputObjectOrArrayType _obj_or_arr) -> Result { + return get_discriminator(_r, _obj_or_arr); }; - const auto to_result = [&_r, _var](const std::string& _disc_value) { - return find_matching_alternative(_r, _disc_value, _var); + const auto to_result = + [&_r, _var](const std::string& _disc_value) -> ResultType { + return find_matching_alternative( + _r, _disc_value, _var, + std::make_integer_sequence()); }; - return _r.to_object(_var).and_then(get_disc).and_then(to_result); + if constexpr (no_field_names_) { + return _r.to_array(_var).and_then(get_disc).and_then(to_result); + } else { + return _r.to_object(_var).and_then(get_disc).and_then(to_result); + } } template @@ -54,68 +71,103 @@ struct Parser, } static schema::Type to_schema( - std::map* _definitions) { + std::map* _definitions) noexcept { using VariantType = std::variant), AlternativeTypes>...>; return Parser::to_schema(_definitions); } private: - template + template static ResultType find_matching_alternative( - const R& _r, const std::string& _disc_value, - const InputVarType& _var) noexcept { - if constexpr (_i == sizeof...(AlternativeTypes)) { + const R& _r, const std::string& _disc_value, const InputVarType& _var, + std::integer_sequence) noexcept { + ResultType res = Error(""); + bool match_found = false; + (set_if_disc_value_matches<_is>(_r, _disc_value, _var, &res, &match_found), + ...); + if (match_found) [[likely]] { + return res; + } else { const auto names = TaggedUnion<_discriminator, AlternativeTypes...>::PossibleTags::names(); return Error("Could not parse tagged union, could not match " + _discriminator.str() + " '" + _disc_value + "'. The following tags are allowed: " + - internal::strings::join(",", names)); - } else { - using AlternativeType = std::remove_cvref_t< - std::variant_alternative_t<_i, std::variant>>; - - if (contains_disc_value(_disc_value)) { - const auto to_tagged_union = [](auto&& _val) { - return TaggedUnion<_discriminator, AlternativeTypes...>( - std::move(_val)); - }; - - const auto embellish_error = [&](Error&& _e) { - return Error( - "Could not parse tagged union with " - "discrimininator " + - _discriminator.str() + " '" + _disc_value + "': " + _e.what()); - }; - - return Parser::read(_r, _var) - .transform(to_tagged_union) - .or_else(embellish_error); + internal::strings::join(", ", names)); + } + } + template + static void set_if_disc_value_matches(const R& _r, + const std::string& _disc_value, + const InputVarType& _var, + ResultType* _res, + bool* _match_found) noexcept { + using AlternativeType = std::remove_cvref_t< + std::variant_alternative_t<_i, std::variant>>; + + if (!*_match_found && contains_disc_value(_disc_value)) { + const auto get_fields = [](auto&& _val) -> AlternativeType { + if constexpr (is_tagged_union_wrapper_v) { + return std::move(_val.fields()); + } else { + return std::move(_val); + } + }; + + const auto to_tagged_union = [](auto&& _val) { + return TaggedUnion<_discriminator, AlternativeTypes...>( + std::move(_val)); + }; + + const auto embellish_error = [&](Error&& _e) { + return Error( + "Could not parse tagged union with " + "discrimininator " + + _discriminator.str() + " '" + _disc_value + "': " + _e.what()); + }; + + if constexpr (no_field_names_) { + using T = tagged_union_wrapper_no_ptr_t), AlternativeType>>; + *_res = Parser::read(_r, _var) + .transform(get_fields) + .transform(to_tagged_union) + .or_else(embellish_error); } else { - return find_matching_alternative<_i + 1>(_r, _disc_value, _var); + *_res = Parser::read(_r, _var) + .transform(to_tagged_union) + .or_else(embellish_error); } + + *_match_found = true; } } - /// Retrieves the discriminator. + /// Retrieves the discriminator from an object static Result get_discriminator( - const R& _r, const InputObjectType& _obj) noexcept { + const R& _r, const InputObjectOrArrayType& _obj_or_arr) noexcept { + const auto to_type = [&_r](auto _var) { + return _r.template to_basic_type(_var); + }; + const auto embellish_error = [](const auto& _err) { return Error("Could not parse tagged union: Could not find field '" + _discriminator.str() + "' or type of field was not a string."); }; - const auto to_type = [&_r](auto _var) { - return _r.template to_basic_type(_var); - }; - - return _r.get_field(_discriminator.str(), _obj) - .and_then(to_type) - .or_else(embellish_error); + if constexpr (no_field_names_) { + return _r.get_field_from_array(0, _obj_or_arr) + .and_then(to_type) + .or_else(embellish_error); + } else { + return _r.get_field_from_object(_discriminator.str(), _obj_or_arr) + .and_then(to_type) + .or_else(embellish_error); + } } /// Determines whether the discriminating literal contains the value @@ -130,7 +182,7 @@ struct Parser, template static void write_wrapped(const W& _w, const T& _val, const P& _parent) noexcept { - const auto wrapped = wrap_if_necessary(_val); + const auto wrapped = wrap_if_necessary(_val); Parser, ProcessorsType>::write( _w, wrapped, _parent); } diff --git a/include/rfl/parsing/TaggedUnionWrapper.hpp b/include/rfl/parsing/TaggedUnionWrapper.hpp index f4262611..cdc45de9 100644 --- a/include/rfl/parsing/TaggedUnionWrapper.hpp +++ b/include/rfl/parsing/TaggedUnionWrapper.hpp @@ -2,6 +2,7 @@ #define RFL_PARSING_TAGGEDUNIONWRAPPER_HPP_ #include +#include #include "../Flatten.hpp" #include "../Rename.hpp" @@ -10,18 +11,24 @@ namespace rfl { namespace parsing { -template +template struct TaggedUnionWrapperNoFields { - using Type = T; + using Type = std::remove_cvref_t; + using FlattenedType = + std::conditional_t<_as_const_pointer, const Type*, Type>; rfl::Rename<_discriminator, TagType> tag; - rfl::Flatten fields; + rfl::Flatten fields; }; -template +template struct TaggedUnionWrapperWithFields { using Type = T; + using FlattenedType = + std::conditional_t<_as_const_pointer, const Type*, Type>; rfl::Field<_discriminator, TagType> tag; - rfl::Flatten fields; + rfl::Flatten fields; }; } // namespace parsing diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp new file mode 100644 index 00000000..89ec151a --- /dev/null +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -0,0 +1,170 @@ +#ifndef RFL_PARSING_VIEWREADERWITHSTRIPPEDFIELDNAMES_HPP_ +#define RFL_PARSING_VIEWREADERWITHSTRIPPEDFIELDNAMES_HPP_ + +#include +#include +#include +#include +#include + +#include "../Result.hpp" +#include "../internal/is_array.hpp" + +namespace rfl::parsing { + +template +class ViewReaderWithStrippedFieldNames { + private: + using InputVarType = typename R::InputVarType; + static constexpr size_t size_ = ViewType::size(); + + public: + ViewReaderWithStrippedFieldNames(const R* _r, ViewType* _view, + std::array* _found, + std::array* _set, + std::vector* _errors) + : i_(0), + r_(_r), + view_(_view), + found_(_found), + set_(_set), + errors_(_errors) {} + + ~ViewReaderWithStrippedFieldNames() = default; + + /// Assigns the parsed version of _var to the field signified by i_, to be + /// used when the field names are stripped. + std::optional read(const InputVarType& _var) const { + if (i_ == size_) { + return Error("Expected a maximum of " + std::to_string(size_) + + " fields, but got at least one more."); + } + assign_to_field_i(*r_, _var, view_, errors_, found_, set_, i_, + std::make_integer_sequence()); + ++i_; + return std::nullopt; + } + + /// Because of the way we have allocated the fields, we need to manually + /// trigger the destructors. + void call_destructors_where_necessary() const { + [&](std::integer_sequence) { + (call_destructor_on_one_if_necessary(), ...); + } + (std::make_integer_sequence()); + } + + private: + template + static void assign_if_field_is_field_i(const R& _r, const auto& _var, + auto* _view, auto* _errors, + auto* _found, auto* _set, int _i) { + using FieldType = std::tuple_element_t; + using OriginalType = typename FieldType::Type; + using T = + std::remove_cvref_t>; + constexpr auto name = FieldType::name(); + if (_i == i) { + std::get(*_found) = true; + auto res = Parser::read(_r, _var); + if (!res) { + _errors->emplace_back(Error("Failed to parse field '" + + std::string(name) + + "': " + std::move(res.error()->what()))); + return; + } + if constexpr (std::is_pointer_v) { + move_to(rfl::get(*_view), &(*res)); + } else { + rfl::get(*_view) = std::move(*res); + } + std::get(*_set) = true; + } + } + + template + static void assign_to_field_i(const R& _r, const auto& _var, auto* _view, + auto* _errors, auto* _found, auto* _set, int _i, + std::integer_sequence) { + (assign_if_field_is_field_i(_r, _var, _view, _errors, _found, _set, _i), + ...); + } + + // TODO: Unnecessary code duplication. + template + static void call_destructor_on_array(const size_t _size, T* _ptr) { + for (size_t i = 0; i < _size; ++i) { + if constexpr (std::is_array_v) { + call_destructor_on_array(sizeof(*_ptr) / sizeof(**_ptr), *(_ptr + i)); + } else if constexpr (std::is_destructible_v) { + (_ptr + i)->~T(); + } + } + } + + // TODO: Unnecessary code duplication. + template + void call_destructor_on_one_if_necessary() const { + using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>; + using OriginalType = std::remove_cvref_t; + using ValueType = + std::remove_cvref_t>; + if constexpr (!std::is_array_v && + std::is_pointer_v && + std::is_destructible_v) { + if (std::get<_i>(*set_)) { + rfl::get<_i>(*view_)->~ValueType(); + } + } else if constexpr (std::is_array_v) { + if (std::get<_i>(*set_)) { + auto ptr = rfl::get<_i>(*view_); + call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr); + } + } + } + + // TODO: Unnecessary code duplication. + template + static void move_to(Target* _t, Source* _s) { + if constexpr (std::is_const_v) { + return move_to(const_cast*>(_t), _s); + } else if constexpr (!rfl::internal::is_array_v && + !std::is_array_v) { + ::new (_t) Target(std::move(*_s)); + } else if constexpr (rfl::internal::is_array_v) { + static_assert(std::is_array_v, + "Expected target to be a c-array."); + for (size_t i = 0; i < _s->arr_.size(); ++i) { + move_to(&((*_t)[i]), &(_s->arr_[i])); + } + } else { + for (size_t i = 0; i < _s->size(); ++i) { + move_to(&((*_t)[i]), &((*_s)[i])); + } + } + } + + private: + /// Indicates the current field. + mutable int i_; + + /// The underlying reader. + const R* r_; + + /// The underlying view. + ViewType* view_; + + /// Indicates that a certain field has been found. + std::array* found_; + + /// Indicates that a certain field has been successfully set - necessary, + /// because we have to trigger the destructors manually. + std::array* set_; + + /// Collects any errors we may have come across. + std::vector* errors_; +}; + +} // namespace rfl::parsing + +#endif diff --git a/include/rfl/parsing/is_tagged_union_wrapper.hpp b/include/rfl/parsing/is_tagged_union_wrapper.hpp index 34021230..31e0bcc5 100644 --- a/include/rfl/parsing/is_tagged_union_wrapper.hpp +++ b/include/rfl/parsing/is_tagged_union_wrapper.hpp @@ -15,18 +15,21 @@ class is_tagged_union_wrapper; template class is_tagged_union_wrapper : public std::false_type {}; -template +template class is_tagged_union_wrapper< - TaggedUnionWrapperNoFields> + TaggedUnionWrapperNoFields> : public std::true_type {}; -template +template class is_tagged_union_wrapper< - TaggedUnionWrapperWithFields> + TaggedUnionWrapperWithFields> : public std::true_type {}; template -constexpr bool is_tagged_union_wrapper_v = is_tagged_union_wrapper::value; +constexpr bool is_tagged_union_wrapper_v = + is_tagged_union_wrapper>::value; } // namespace parsing } // namespace rfl diff --git a/include/rfl/parsing/tagged_union_wrapper_no_ptr.hpp b/include/rfl/parsing/tagged_union_wrapper_no_ptr.hpp new file mode 100644 index 00000000..46389155 --- /dev/null +++ b/include/rfl/parsing/tagged_union_wrapper_no_ptr.hpp @@ -0,0 +1,41 @@ +#ifndef RFL_PARSING_TAGGEDUNIONWRAPPERNOPTR_HPP_ +#define RFL_PARSING_TAGGEDUNIONWRAPPERNOPTR_HPP_ + +#include + +#include "../internal/StringLiteral.hpp" +#include "TaggedUnionWrapper.hpp" + +namespace rfl { +namespace parsing { + +template +struct tagged_union_wrapper_no_ptr; + +template +struct tagged_union_wrapper_no_ptr { + using Type = T; +}; + +template +struct tagged_union_wrapper_no_ptr< + TaggedUnionWrapperNoFields> { + using Type = TaggedUnionWrapperNoFields; +}; + +template +struct tagged_union_wrapper_no_ptr> { + using Type = TaggedUnionWrapperWithFields; +}; + +template +using tagged_union_wrapper_no_ptr_t = + typename tagged_union_wrapper_no_ptr>::Type; + +} // namespace parsing +} // namespace rfl + +#endif diff --git a/include/rfl/toml/Reader.hpp b/include/rfl/toml/Reader.hpp index e205d547..cb1d0875 100644 --- a/include/rfl/toml/Reader.hpp +++ b/include/rfl/toml/Reader.hpp @@ -29,7 +29,15 @@ struct Reader { T::from_toml_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType _arr) const noexcept { + if (_idx >= _arr->size()) { + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + return _arr->get(_idx); + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { auto var = (*_obj)[_name]; if (!var) { diff --git a/include/rfl/toml/read.hpp b/include/rfl/toml/read.hpp index 14706092..3175f564 100644 --- a/include/rfl/toml/read.hpp +++ b/include/rfl/toml/read.hpp @@ -18,7 +18,11 @@ using InputVarType = typename Reader::InputVarType; template auto read(InputVarType _var) { const auto r = Reader(); - return Parser>::read(r, _var); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + return Parser::read(r, _var); } /// Parses an object from TOML using reflection. diff --git a/include/rfl/toml/write.hpp b/include/rfl/toml/write.hpp index b1426127..9d4f306f 100644 --- a/include/rfl/toml/write.hpp +++ b/include/rfl/toml/write.hpp @@ -20,7 +20,11 @@ std::ostream& write(const auto& _obj, std::ostream& _stream) { using ParentType = parsing::Parent; ::toml::table root; auto w = Writer(&root); - Parser>::write(w, _obj, typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(w, _obj, typename ParentType::Root{}); _stream << root; return _stream; } @@ -33,7 +37,11 @@ std::string write(const auto& _obj) { std::stringstream sstream; ::toml::table root; auto w = Writer(&root); - Parser>::write(w, _obj, typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(w, _obj, typename ParentType::Root{}); sstream << root; return sstream.str(); } diff --git a/include/rfl/xml/Parser.hpp b/include/rfl/xml/Parser.hpp index 687f751e..2f2c4ca0 100644 --- a/include/rfl/xml/Parser.hpp +++ b/include/rfl/xml/Parser.hpp @@ -22,7 +22,8 @@ struct Parser, : public NamedTupleParser { + /*_no_field_names_=*/false, ProcessorsType, + FieldTypes...> { }; } // namespace parsing diff --git a/include/rfl/xml/Reader.hpp b/include/rfl/xml/Reader.hpp index 6500b0dc..a7542b98 100644 --- a/include/rfl/xml/Reader.hpp +++ b/include/rfl/xml/Reader.hpp @@ -65,7 +65,19 @@ struct Reader { return std::visit(cast, _node_or_attribute); } - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + const auto name = _arr.node_.name(); + size_t i = 0; + for (auto node = _arr.node_; node; node = node.next_sibling(name)) { + if (i == _idx) { + return InputVarType(node); + } + } + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType _obj) const noexcept { const auto node = _obj.node_.child(_name.c_str()); if (!node) { diff --git a/include/rfl/xml/read.hpp b/include/rfl/xml/read.hpp index e20d4c5b..20fc5a22 100644 --- a/include/rfl/xml/read.hpp +++ b/include/rfl/xml/read.hpp @@ -20,7 +20,11 @@ using InputVarType = typename Reader::InputVarType; template auto read(const InputVarType& _var) { const auto r = Reader(); - return Parser>::read(r, _var); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + return Parser::read(r, _var); } /// Parses an object from XML using reflection. diff --git a/include/rfl/xml/write.hpp b/include/rfl/xml/write.hpp index 023c7577..2ba76c84 100644 --- a/include/rfl/xml/write.hpp +++ b/include/rfl/xml/write.hpp @@ -52,7 +52,11 @@ std::ostream& write(const auto& _obj, std::ostream& _stream, auto w = Writer(doc, root_name.str()); - Parser>::write(w, _obj, typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(w, _obj, typename ParentType::Root{}); doc->save(_stream, _indent.c_str()); diff --git a/include/rfl/yaml/Reader.hpp b/include/rfl/yaml/Reader.hpp index ab950ce1..7b935630 100644 --- a/include/rfl/yaml/Reader.hpp +++ b/include/rfl/yaml/Reader.hpp @@ -49,7 +49,15 @@ struct Reader { T::from_yaml_obj(var); }); - rfl::Result get_field( + rfl::Result get_field_from_array( + const size_t _idx, const InputArrayType& _arr) const noexcept { + if (_idx >= _arr.node_.size()) { + return rfl::Error("Index " + std::to_string(_idx) + " of of bounds."); + } + return InputVarType(_arr.node_[_idx]); + } + + rfl::Result get_field_from_object( const std::string& _name, const InputObjectType& _obj) const noexcept { auto var = InputVarType(_obj.node_[_name]); if (!var.node_) { diff --git a/include/rfl/yaml/read.hpp b/include/rfl/yaml/read.hpp index 0d00eb6d..d25744ee 100644 --- a/include/rfl/yaml/read.hpp +++ b/include/rfl/yaml/read.hpp @@ -19,7 +19,11 @@ using InputVarType = typename Reader::InputVarType; template auto read(const InputVarType& _var) { const auto r = Reader(); - return Parser>::read(r, _var); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + return Parser::read(r, _var); } /// Parses an object from YAML using reflection. diff --git a/include/rfl/yaml/write.hpp b/include/rfl/yaml/write.hpp index f6613ce1..48cadc25 100644 --- a/include/rfl/yaml/write.hpp +++ b/include/rfl/yaml/write.hpp @@ -22,7 +22,11 @@ std::ostream& write(const auto& _obj, std::ostream& _stream) { using ParentType = parsing::Parent; const auto out = Ref::make(); auto w = Writer(out); - Parser>::write(w, _obj, typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(w, _obj, typename ParentType::Root{}); _stream << out->c_str(); return _stream; } @@ -34,7 +38,11 @@ std::string write(const auto& _obj) { using ParentType = parsing::Parent; const auto out = Ref::make(); auto w = Writer(out); - Parser>::write(w, _obj, typename ParentType::Root{}); + using ProcessorsType = Processors; + static_assert(!ProcessorsType::no_field_names_, + "The NoFieldNames processor is not supported for BSON, XML, " + "TOML, or YAML."); + Parser::write(w, _obj, typename ParentType::Root{}); return out->c_str(); } diff --git a/tests/cbor/test_tagged_union2.cpp b/tests/cbor/test_tagged_union2.cpp new file mode 100644 index 00000000..4b527cda --- /dev/null +++ b/tests/cbor/test_tagged_union2.cpp @@ -0,0 +1,31 @@ +#include +#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(cbor, test_tagged_union2) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union2 diff --git a/tests/flexbuffers/test_tagged_union2.cpp b/tests/flexbuffers/test_tagged_union2.cpp new file mode 100644 index 00000000..8f7088a9 --- /dev/null +++ b/tests/flexbuffers/test_tagged_union2.cpp @@ -0,0 +1,31 @@ +#include +#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(flexbuf, test_tagged_union2) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union2 diff --git a/tests/json/test_json_schema3.cpp b/tests/json/test_json_schema3.cpp new file mode 100644 index 00000000..b8c1dd2d --- /dev/null +++ b/tests/json/test_json_schema3.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_json_schema3 { + +enum class Color { red, green, blue }; + +struct Rectangle { + double width; + double height; +}; + +struct Square { + double width; +}; + +struct Circle { + double radius; +}; + +using Age = rfl::Validator, rfl::Maximum<130>>; + +using FieldVariant = + rfl::Variant, + rfl::Field<"square", Square>, rfl::Field<"circle", Circle>>; + +struct Person { + std::string first_name; + std::string last_name = "Simpson"; + rfl::Description<"Must be a proper email in the form xxx@xxx.xxx.", + rfl::Email> + email; + std::string town = "Springfield"; + Color color; + Age age; + float salary; + rfl::Description< + "The person's children. Pass an empty array for no children.", + std::vector> + children; + std::variant, int> variant; + std::tuple, int> tuple; + rfl::Rename<"taggedUnion", + rfl::TaggedUnion<"shape", Rectangle, Square, Circle>> + tagged_union; + rfl::Rename<"fieldVariant", FieldVariant> field_variant; +}; + +TEST(json, test_json_schema3) { + using DescribedType = rfl::Description< + "JSON schema that describes the required " + "attributes for the person class.", + Person>; + + const auto json_schema = + rfl::json::to_schema(); + + const std::string expected = + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/test_json_schema3__Person","description":"JSON schema that describes the required attributes for the person class.","definitions":{"test_json_schema3__Circle":{"type":"array","prefixItems":[{"type":"number"}],"items":false},"test_json_schema3__Circle__tagged":{"type":"array","prefixItems":[{"type":"string","enum":["Circle"]},{"type":"number"}],"items":false},"test_json_schema3__Person":{"type":"array","prefixItems":[{"type":"string"},{"type":"string"},{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},{"type":"string"},{"type":"string","enum":["red","green","blue"]},{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},{"type":"number"},{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/test_json_schema3__Person"}},{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema3__Person"}},{"type":"integer"}]},{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema3__Person"}},{"type":"integer"}],"items":false},{"anyOf":[{"$ref":"#/definitions/test_json_schema3__Rectangle__tagged"},{"$ref":"#/definitions/test_json_schema3__Square__tagged"},{"$ref":"#/definitions/test_json_schema3__Circle__tagged"}]},{"anyOf":[{"type":"array","prefixItems":[{"$ref":"#/definitions/test_json_schema3__Rectangle"}],"items":false},{"type":"array","prefixItems":[{"$ref":"#/definitions/test_json_schema3__Square"}],"items":false},{"type":"array","prefixItems":[{"$ref":"#/definitions/test_json_schema3__Circle"}],"items":false}]}],"items":false},"test_json_schema3__Rectangle":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false},"test_json_schema3__Rectangle__tagged":{"type":"array","prefixItems":[{"type":"string","enum":["Rectangle"]},{"type":"number"},{"type":"number"}],"items":false},"test_json_schema3__Square":{"type":"array","prefixItems":[{"type":"number"}],"items":false},"test_json_schema3__Square__tagged":{"type":"array","prefixItems":[{"type":"string","enum":["Square"]},{"type":"number"}],"items":false}}})"; + + EXPECT_EQ(json_schema, expected); +} +} // namespace test_json_schema3 diff --git a/tests/json/test_strip_field_names.cpp b/tests/json/test_strip_field_names.cpp new file mode 100644 index 00000000..6c2c6e9b --- /dev/null +++ b/tests/json/test_strip_field_names.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_strip_field_names { + +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(json, test_strip_field_names) { + 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, + R"(["Homer","Simpson","Springfield","1987-04-19",45,"homer@simpson.com",[["Bart","Simpson","Springfield","1987-04-19",10,"bart@simpson.com",[]],["Lisa","Simpson","Springfield","1987-04-19",8,"lisa@simpson.com",[]],["Maggie","Simpson","Springfield","1987-04-19",0,"maggie@simpson.com",[]]]])"); +} +} // namespace test_strip_field_names diff --git a/tests/json/test_tagged_union4.cpp b/tests/json/test_tagged_union4.cpp new file mode 100644 index 00000000..e0fa59be --- /dev/null +++ b/tests/json/test_tagged_union4.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_tagged_union4 { + +struct Circle { + double radius; +}; + +struct Rectangle { + double height; + double width; +}; + +struct Square { + double width; +}; + +using Shapes = rfl::TaggedUnion<"shape", Circle, Square, Rectangle>; + +TEST(json, test_tagged_union4) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + + write_and_read(r, R"(["Rectangle",10.0,5.0])"); +} +} // namespace test_tagged_union4 diff --git a/tests/msgpack/test_tagged_union2.cpp b/tests/msgpack/test_tagged_union2.cpp new file mode 100644 index 00000000..b8f6b2a5 --- /dev/null +++ b/tests/msgpack/test_tagged_union2.cpp @@ -0,0 +1,31 @@ +#include +#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(msgpack, test_tagged_union2) { + const Shapes r = Rectangle{.height = 10, .width = 5}; + write_and_read(r); +} +} // namespace test_tagged_union2