From 6a7b7223ca0f771a0fad144b6c0deb756f15436b Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 9 Sep 2026 21:27:48 +0200 Subject: [PATCH 01/13] Began redesigning structs --- include/rfl/Field.hpp | 3 + include/rfl/NamedTuple.hpp | 3 + include/rfl/parsing/ArrayReader.hpp | 8 +- include/rfl/parsing/NamedTupleParser.hpp | 28 ++---- include/rfl/parsing/ParserArray.hpp | 13 +-- include/rfl/parsing/ParserBasicType.hpp | 1 - include/rfl/parsing/ParserEnum.hpp | 5 +- include/rfl/parsing/Parser_default.hpp | 35 +++----- include/rfl/parsing/TupleParser.hpp | 17 +--- include/rfl/parsing/TupleReader.hpp | 4 +- include/rfl/parsing/ViewReader.hpp | 3 +- .../ViewReaderWithStrippedFieldNames.hpp | 2 +- ...l_destructors_on_array_where_necessary.hpp | 35 -------- ...l_destructors_on_tuple_where_necessary.hpp | 58 ------------ .../call_destructors_where_necessary.hpp | 78 ---------------- include/rfl/parsing/tabular/ArrowReader.hpp | 23 +---- tests/json/test_const_fields.cpp | 68 +++++++------- tests/json/test_leak.cpp | 89 ++++++++++--------- tests/json/test_leak_arr.cpp | 4 +- tests/json/test_leak_rfl_tup.cpp | 85 +++++++++--------- tests/json/test_leak_tup.cpp | 4 +- 21 files changed, 169 insertions(+), 397 deletions(-) delete mode 100644 include/rfl/parsing/call_destructors_on_array_where_necessary.hpp delete mode 100644 include/rfl/parsing/call_destructors_on_tuple_where_necessary.hpp delete mode 100644 include/rfl/parsing/call_destructors_where_necessary.hpp diff --git a/include/rfl/Field.hpp b/include/rfl/Field.hpp index a725986c5..7de8954a1 100644 --- a/include/rfl/Field.hpp +++ b/include/rfl/Field.hpp @@ -26,6 +26,9 @@ struct Field { /// The name of the field as a Literal type. using Name = rfl::Literal<_name>; + /// Default constructor. + Field() = default; + /// Constructs a Field from a const reference to the value. /// @param _value The value to store in the field Field(const Type& _value) : value_(_value) {} diff --git a/include/rfl/NamedTuple.hpp b/include/rfl/NamedTuple.hpp index 381fe8811..22aea71b7 100644 --- a/include/rfl/NamedTuple.hpp +++ b/include/rfl/NamedTuple.hpp @@ -47,6 +47,9 @@ class NamedTuple { using Values = rfl::Tuple::Type...>; public: + /// @brief Default constructor. + NamedTuple() = default; + /// @brief Construct NamedTuple from values of each field type. /// @param _values Rvalue references to the values for each field. NamedTuple(typename std::remove_cvref::type::Type&&... _values) diff --git a/include/rfl/parsing/ArrayReader.hpp b/include/rfl/parsing/ArrayReader.hpp index e4f094b24..b4f302742 100644 --- a/include/rfl/parsing/ArrayReader.hpp +++ b/include/rfl/parsing/ArrayReader.hpp @@ -30,7 +30,8 @@ class ArrayReader { /** * @brief Checks if the correct number of elements has been read. - * @return An error if the number of elements is incorrect, std::nullopt otherwise. + * @return An error if the number of elements is incorrect, std::nullopt + * otherwise. */ std::optional check_size() const { if (num_set_ != size_) { @@ -49,7 +50,8 @@ class ArrayReader { /** * @brief Reads a variable from the input and adds it to the array. * @param _var The input variable to read. - * @return An error if reading fails or if the array is already full, std::nullopt otherwise. + * @return An error if reading fails or if the array is already full, + * std::nullopt otherwise. */ std::optional read(const InputVarType& _var) const { if (num_set_ == size_) { @@ -83,7 +85,7 @@ class ArrayReader { return move_to(const_cast*>(_t), _s); } else if constexpr (!internal::is_array_v && !std::is_array_v) { - ::new (_t) Target(std::move(*_s)); + *_t = Target(std::move(*_s)); } else if constexpr (internal::is_array_v) { for (size_t i = 0; i < _s->arr_.size(); ++i) { move_to(&((*_t)[i]), &(_s->arr_[i])); diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 8542cdb7b..1ebc7e790 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -16,7 +16,6 @@ #include "../internal/is_extra_fields.hpp" #include "../internal/is_skip.hpp" #include "../internal/nth_element_t.hpp" -#include "../internal/ptr_cast.hpp" #include "../to_view.hpp" #include "AreReaderAndWriter.hpp" #include "Parent.hpp" @@ -25,7 +24,6 @@ #include "ViewReaderWithDefault.hpp" #include "ViewReaderWithDefaultAndStrippedFieldNames.hpp" #include "ViewReaderWithStrippedFieldNames.hpp" -#include "call_destructors_where_necessary.hpp" #include "is_empty.hpp" #include "is_required.hpp" #include "schema/Type.hpp" @@ -88,20 +86,15 @@ struct NamedTupleParser { */ static Result> read( const R& _r, const InputVarType& _var) noexcept { - alignas(NamedTuple) unsigned char - buf[sizeof(NamedTuple)]; - auto ptr = internal::ptr_cast*>(&buf); - auto view = rfl::to_view(*ptr); + NamedTuple nt; + auto view = rfl::to_view(nt); using ViewType = std::remove_cvref_t; const auto [set, err] = Parser::read_view(_r, _var, &view); if (err) [[unlikely]] { - call_destructors_where_necessary(set, &view); return error(*err); } - auto res = Result>(std::move(*ptr)); - call_destructors_where_necessary(set, &view); - return res; + return nt; } /** @@ -238,8 +231,7 @@ struct NamedTupleParser { template static void add_field_to_schema( - std::map* _definitions, - SchemaType* _schema, + std::map* _definitions, SchemaType* _schema, View* _view) noexcept { using F = internal::nth_element_t<_i, FieldTypes...>; using U = std::remove_cvref_t; @@ -270,10 +262,9 @@ struct NamedTupleParser { (add_field_to_object<_is>(_w, _tup, _ptr), ...); } - template + template static void build_schema(std::map* _definitions, - SchemaType* _schema, - View* _view, + SchemaType* _schema, View* _view, std::integer_sequence) noexcept { (add_field_to_schema(_definitions, _schema, _view), ...); @@ -312,12 +303,7 @@ struct NamedTupleParser { _errors->emplace_back(stream.str()); } else if constexpr (!internal::has_default_val_v) { - if constexpr (!std::is_const_v) { - ::new (rfl::get<_i>(_view)) ValueType(); - } else { - using NonConstT = std::remove_const_t; - ::new (const_cast(rfl::get<_i>(_view))) NonConstT(); - } + *rfl::get<_i>(_view) = ValueType(); std::get<_i>(*_set) = true; } } diff --git a/include/rfl/parsing/ParserArray.hpp b/include/rfl/parsing/ParserArray.hpp index 66cc17228..cfc2f34d2 100644 --- a/include/rfl/parsing/ParserArray.hpp +++ b/include/rfl/parsing/ParserArray.hpp @@ -12,7 +12,6 @@ #include "ArrayReader.hpp" #include "Parent.hpp" #include "Parser_base.hpp" -#include "call_destructors_on_array_where_necessary.hpp" #include "schema/Type.hpp" namespace rfl::parsing { @@ -47,24 +46,18 @@ struct ParserArray { const InputVarType& _var) noexcept { const auto parse = [&](const InputArrayType& _arr) -> Result> { - alignas( - std::array) unsigned char buf[sizeof(std::array)]; - auto ptr = internal::ptr_cast*>(&buf); + std::array arr{}; const auto array_reader = - ArrayReader(&_r, ptr); + ArrayReader(&_r, &arr); auto err = _r.read_array(array_reader, _arr); if (err) { - call_destructors_on_array_where_necessary(array_reader.num_set(), ptr); return error(*err); } err = array_reader.check_size(); if (err) { - call_destructors_on_array_where_necessary(array_reader.num_set(), ptr); return error(*err); } - auto result = Result>(std::move(*ptr)); - call_destructors_on_array_where_necessary(array_reader.num_set(), ptr); - return result; + return arr; }; return _r.to_array(_var).and_then(parse); diff --git a/include/rfl/parsing/ParserBasicType.hpp b/include/rfl/parsing/ParserBasicType.hpp index a1413cb83..44fe75e3a 100644 --- a/include/rfl/parsing/ParserBasicType.hpp +++ b/include/rfl/parsing/ParserBasicType.hpp @@ -23,7 +23,6 @@ #include "AreReaderAndWriter.hpp" #include "Parent.hpp" #include "Parser_base.hpp" -#include "call_destructors_where_necessary.hpp" #include "is_tagged_union_wrapper.hpp" #include "make_type_name.hpp" #include "schema/Type.hpp" diff --git a/include/rfl/parsing/ParserEnum.hpp b/include/rfl/parsing/ParserEnum.hpp index 4a216f434..ad5f2644b 100644 --- a/include/rfl/parsing/ParserEnum.hpp +++ b/include/rfl/parsing/ParserEnum.hpp @@ -12,10 +12,8 @@ #include "../internal/enums/is_scoped_enum.hpp" #include "../internal/has_reflector.hpp" #include "../internal/underlying_enums_v.hpp" -#include "AreReaderAndWriter.hpp" #include "Parent.hpp" #include "Parser_base.hpp" -#include "call_destructors_where_necessary.hpp" #include "make_type_name.hpp" #include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" @@ -61,8 +59,7 @@ struct ParserEnum { .transform([](const auto _val) { return static_cast(_val); }); } else { return _r.template to_basic_type(_var).and_then( - rfl::string_to_enum>); + rfl::string_to_enum>); } } diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index ad50b3e2b..ef68f3132 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -61,7 +61,6 @@ #include "ParserWString.hpp" #include "Parser_base.hpp" #include "VectorParser.hpp" -#include "call_destructors_where_necessary.hpp" #include "is_string_map.hpp" #include "make_type_name.hpp" #include "schema/Type.hpp" @@ -682,19 +681,15 @@ struct Parser { /// and placement new. This is how we deal with the fact that some fields /// might not be default-constructible. static Result read_struct(const R& _r, const InputVarType& _var) { - alignas(T) unsigned char buf[sizeof(T)]{}; - auto ptr = internal::ptr_cast(&buf); - auto view = ProcessorsType::template process(to_view(*ptr)); + T t{}; + auto view = ProcessorsType::template process(to_view(t)); using ViewType = std::remove_cvref_t; const auto [set, err] = Parser::read_view(_r, _var, &view); if (err) [[unlikely]] { - call_destructors_where_necessary(set, &view); return error(err->what()); } - auto res = Result(std::move(*ptr)); - call_destructors_where_necessary(set, &view); - return res; + return t; } /// This is actually more straight-forward than the standard case - we @@ -703,22 +698,16 @@ struct Parser { /// so we only use it when the DefaultIfMissing preprocessor is added. static Result read_struct_with_default(const R& _r, const InputVarType& _var) { - try { - auto t = T{}; // This might fail, for instance if the default value does - // not satisfy the validator, but in that case we will just - // return the error. - auto view = ProcessorsType::template process(to_view(t)); - using ViewType = decltype(view); - const auto err = - Parser::read_view_with_default( - _r, _var, &view); - if (err) [[unlikely]] { - return error(*err); - } - return t; - } catch (std::exception& e) { - return error(e.what()); + T t{}; + auto view = ProcessorsType::template process(to_view(t)); + using ViewType = decltype(view); + const auto err = + Parser::read_view_with_default(_r, _var, + &view); + if (err) [[unlikely]] { + return error(*err); } + return t; } }; diff --git a/include/rfl/parsing/TupleParser.hpp b/include/rfl/parsing/TupleParser.hpp index b9dd097c1..1a274cdcc 100644 --- a/include/rfl/parsing/TupleParser.hpp +++ b/include/rfl/parsing/TupleParser.hpp @@ -8,12 +8,8 @@ #include "../Result.hpp" #include "../Tuple.hpp" -#include "../always_false.hpp" -#include "../internal/nth_element_t.hpp" -#include "../internal/ptr_cast.hpp" #include "Parent.hpp" #include "TupleReader.hpp" -#include "call_destructors_on_tuple_where_necessary.hpp" #include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" #include "schemaful/IsSchemafulWriter.hpp" @@ -53,26 +49,19 @@ struct TupleParser { } else { const auto parse = [&](const InputArrayType& _arr) -> Result { - alignas(TupleType) unsigned char buf[sizeof(TupleType)]{}; - auto ptr = internal::ptr_cast(&buf); + TupleType tup{}; const auto tuple_reader = TupleReader(&_r, ptr); + _all_required, ProcessorsType>(&_r, &tup); auto err = _r.read_array(tuple_reader, _arr); if (err) { - call_destructors_on_tuple_where_necessary(tuple_reader.num_set(), - ptr); return error(*err); } err = tuple_reader.handle_missing_fields(); if (err) { - call_destructors_on_tuple_where_necessary(tuple_reader.num_set(), - ptr); return error(*err); } - auto res = Result(std::move(*ptr)); - call_destructors_on_tuple_where_necessary(tuple_reader.num_set(), ptr); - return res; + return tup; }; return _r.to_array(_var).and_then(parse); diff --git a/include/rfl/parsing/TupleReader.hpp b/include/rfl/parsing/TupleReader.hpp index 15ecf5f7e..9ef40ff10 100644 --- a/include/rfl/parsing/TupleReader.hpp +++ b/include/rfl/parsing/TupleReader.hpp @@ -78,7 +78,7 @@ class TupleReader { " was required, but missing."); return; } else { - ::new (&(rfl::get<_i>(*tuple_))) CurrentType(); + rfl::get<_i>(*tuple_) = CurrentType(); ++num_set_; } } @@ -121,7 +121,7 @@ class TupleReader { return move_to(const_cast*>(_t), _s); } else if constexpr (!internal::is_array_v && !std::is_array_v) { - ::new (_t) Target(std::move(*_s)); + *_t = Target(std::move(*_s)); } else if constexpr (internal::is_array_v) { for (size_t i = 0; i < _s->arr_.size(); ++i) { move_to(&((*_t)[i]), &(_s->arr_[i])); diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index c901adab7..ddc2c7e66 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -123,7 +123,6 @@ class ViewReader { using T = std::remove_cvref_t< std::remove_pointer_t>; if (!std::get<_pos>(*_set)) { - ::new (extra_fields) ExtraFieldsType(); std::get<_pos>(*_set) = true; std::get<_pos>(*_found) = true; } @@ -178,7 +177,7 @@ class ViewReader { 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)); + *_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."); diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index cc776bebc..113ded56e 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -116,7 +116,7 @@ class ViewReaderWithStrippedFieldNames { 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)); + *_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."); diff --git a/include/rfl/parsing/call_destructors_on_array_where_necessary.hpp b/include/rfl/parsing/call_destructors_on_array_where_necessary.hpp deleted file mode 100644 index acb4e87e4..000000000 --- a/include/rfl/parsing/call_destructors_on_array_where_necessary.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef RFL_PARSING_CALL_DESTRUCTORS_ON_ARRAY_WHERE_NECESSARY_HPP_ -#define RFL_PARSING_CALL_DESTRUCTORS_ON_ARRAY_WHERE_NECESSARY_HPP_ - -#include -#include - -#include "call_destructors_where_necessary.hpp" - -namespace rfl::parsing { - -/** - * @brief Calls the destructors on the elements of an array, if necessary. - * - * @tparam T The type of the elements in the array. - * @tparam _size The size of the array. - * @param _num_set The number of elements that have been set. - * @param _array The array to call the destructors on. - */ -template -void call_destructors_on_array_where_necessary(const size_t _num_set, - std::array* _array) { - for (size_t i = 0; i < std::min(_num_set, _size); ++i) { - if constexpr (!std::is_array_v && - std::is_destructible_v>) { - (*_array)[i].~T(); - } else if constexpr (std::is_array_v) { - auto ptr = (*_array)[i]; - call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr); - } - } -} - -} // namespace rfl::parsing - -#endif diff --git a/include/rfl/parsing/call_destructors_on_tuple_where_necessary.hpp b/include/rfl/parsing/call_destructors_on_tuple_where_necessary.hpp deleted file mode 100644 index ff8718345..000000000 --- a/include/rfl/parsing/call_destructors_on_tuple_where_necessary.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef RFL_PARSING_CALL_DESTRUCTORS_ON_TUPLE_WHERE_NECESSARY_HPP_ -#define RFL_PARSING_CALL_DESTRUCTORS_ON_TUPLE_WHERE_NECESSARY_HPP_ - -#include - -#include "../Tuple.hpp" -#include "call_destructors_where_necessary.hpp" - -namespace rfl::parsing { - -/** - * @brief Calls the destructor on a single element of a tuple, if necessary. - * - * @tparam TupleType The type of the tuple. - * @tparam _size The size of the tuple. - * @tparam _i The index of the element. - * @param _num_set The number of elements that have been set. - * @param _tup The tuple to call the destructor on. - */ -template -void call_destructor_on_one_tuple_element_if_necessary(const size_t _num_set, - TupleType* _tup) { - using FieldType = tuple_element_t<_i, TupleType>; - using ValueType = std::remove_cv_t>; - if constexpr (!std::is_array_v && - std::is_destructible_v) { - if (_i < _num_set) { - rfl::get<_i>(*_tup).~ValueType(); - } - } else if constexpr (std::is_array_v) { - if (_i < _num_set) { - auto ptr = rfl::get<_i>(*_tup); - call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr); - } - } -} - -/** - * @brief Calls the destructors on the elements of a tuple, if necessary. - * - * @tparam TupleType The type of the tuple. - * @param _num_set The number of elements that have been set. - * @param _tup The tuple to call the destructors on. - */ -template -void call_destructors_on_tuple_where_necessary(const size_t _num_set, - TupleType* _tup) { - [&](std::integer_sequence) { - (call_destructor_on_one_tuple_element_if_necessary< - TupleType, rfl::tuple_size_v, is>(_num_set, _tup), - ...); - } - (std::make_integer_sequence>()); -} - -} // namespace rfl::parsing - -#endif diff --git a/include/rfl/parsing/call_destructors_where_necessary.hpp b/include/rfl/parsing/call_destructors_where_necessary.hpp deleted file mode 100644 index 403609760..000000000 --- a/include/rfl/parsing/call_destructors_where_necessary.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef RFL_PARSING_CALL_DESTRUCTORS_WHERE_NECESSARY_HPP_ -#define RFL_PARSING_CALL_DESTRUCTORS_WHERE_NECESSARY_HPP_ - -#include -#include - -#include "../get.hpp" - -namespace rfl::parsing { - -/** - * @brief Calls the destructors on an array. - * - * @tparam T The type of the elements in the array. - * @param _size The size of the array. - * @param _ptr A pointer to the array. - */ -template -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(); - } - } -} - -/** - * @brief Calls the destructor on a single element of a view, if necessary. - * - * @tparam ViewType The type of the view. - * @tparam _size The size of the set array. - * @tparam _i The index of the element. - * @param _set A boolean array indicating which elements have been set. - * @param _view The view to call the destructor on. - */ -template -void call_destructor_on_one_if_necessary(const std::array& _set, - ViewType* _view) { - using FieldType = tuple_element_t<_i, typename ViewType::Fields>; - using OriginalType = std::remove_cv_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); - } - } -} - -/** - * @brief Calls the destructors on the elements of a view, if necessary. - * - * @tparam ViewType The type of the view. - * @tparam _size The size of the set array. - * @param _set A boolean array indicating which elements have been set. - * @param _view The view to call the destructors on. - */ -template -void call_destructors_where_necessary(const std::array& _set, - ViewType* _view) { - [&](std::integer_sequence) { - (call_destructor_on_one_if_necessary(_set, _view), - ...); - }(std::make_integer_sequence()); -} - -} // namespace rfl::parsing - -#endif diff --git a/include/rfl/parsing/tabular/ArrowReader.hpp b/include/rfl/parsing/tabular/ArrowReader.hpp index aea813965..c652a170f 100644 --- a/include/rfl/parsing/tabular/ArrowReader.hpp +++ b/include/rfl/parsing/tabular/ArrowReader.hpp @@ -35,7 +35,6 @@ #include "../../named_tuple_t.hpp" #include "../../to_view.hpp" #include "../../view_t.hpp" -#include "../call_destructors_where_necessary.hpp" #include "make_chunked_array_iterators.hpp" namespace rfl::parsing::tabular { @@ -134,12 +133,11 @@ class ArrowReader { std::remove_pointer_t>; auto res = *_chunked_array_iterators->template get<_i>(); if (!res) { - destroy_value<_i>(&view); throw std::runtime_error( std::string("Field '") + typename FieldType::Name().str() + std::string("' could not be set: ") + res.error().what()); } - ::new (view.template get<_i>()) T(std::move(*res)); + *view.template get<_i>() = T(std::move(*res)); ++_chunked_array_iterators->template get<_i>(); }; @@ -153,25 +151,6 @@ class ArrowReader { return std::move(*ptr); } - /** - * @brief Destroys the values that have been set in the view so far. - * @tparam _i The number of values that have been set. - * @tparam ViewType The type of the view. - * @param _view The view to destroy. - */ - template - void destroy_value(ViewType* _view) const { - static_assert(_i < ViewType::size(), "_i out of bounds."); - auto set = std::array(); - for (size_t i = 0; i < _i; ++i) { - set[i] = true; - } - for (size_t i = _i; i < ViewType::size(); ++i) { - set[i] = false; - } - call_destructors_where_necessary(set, _view); - } - private: Ref table_; }; diff --git a/tests/json/test_const_fields.cpp b/tests/json/test_const_fields.cpp index d98980694..77dd8dfb1 100644 --- a/tests/json/test_const_fields.cpp +++ b/tests/json/test_const_fields.cpp @@ -8,43 +8,45 @@ namespace test_const_fields { +// TODO +/* using Age = rfl::Validator, rfl::Maximum<130>>; struct Person { - const rfl::Rename<"firstName", std::string> first_name; - const rfl::Rename<"lastName", std::string> last_name = "Simpson"; - const std::string town = "Springfield"; - const rfl::Timestamp<"%Y-%m-%d"> birthday; - const std::optional age; - const rfl::Email email; - const std::vector children; +const rfl::Rename<"firstName", std::string> first_name; +const rfl::Rename<"lastName", std::string> last_name = "Simpson"; +const std::string town = "Springfield"; +const rfl::Timestamp<"%Y-%m-%d"> birthday; +const std::optional age; +const rfl::Email email; +const std::vector children; }; TEST(json, test_const_fields) { - 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"({"firstName":"Homer","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":45,"email":"homer@simpson.com","children":[{"firstName":"Bart","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":10,"email":"bart@simpson.com","children":[]},{"firstName":"Lisa","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":8,"email":"lisa@simpson.com","children":[]},{"firstName":"Maggie","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":0,"email":"maggie@simpson.com","children":[]}]})"); -} +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"({"firstName":"Homer","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":45,"email":"homer@simpson.com","children":[{"firstName":"Bart","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":10,"email":"bart@simpson.com","children":[]},{"firstName":"Lisa","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":8,"email":"lisa@simpson.com","children":[]},{"firstName":"Maggie","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":0,"email":"maggie@simpson.com","children":[]}]})"); +}*/ } // namespace test_const_fields diff --git a/tests/json/test_leak.cpp b/tests/json/test_leak.cpp index 32275d8ed..44f9e2bd9 100644 --- a/tests/json/test_leak.cpp +++ b/tests/json/test_leak.cpp @@ -5,65 +5,66 @@ #include "write_and_read.hpp" namespace test_leak { - +// TODO +/* class TestLeak { - public: - inline static int create_count = 0; - inline static int delete_count = 0; +public: +inline static int create_count = 0; +inline static int delete_count = 0; - using ReflectionType = std::string; +using ReflectionType = std::string; - TestLeak(const std::string& _str) : moved_(false), str_(_str) { - ++create_count; - } +TestLeak(const std::string& _str) : moved_(false), str_(_str) { +++create_count; +} - TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { - ++create_count; - } +TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { +++create_count; +} - TestLeak(TestLeak&& _other) noexcept - : moved_(false), str_(std::move(_other.str_)) { - _other.moved_ = true; - ++create_count; - ++delete_count; - } +TestLeak(TestLeak&& _other) noexcept +: moved_(false), str_(std::move(_other.str_)) { +_other.moved_ = true; +++create_count; +++delete_count; +} - ~TestLeak() { - if (!moved_) { - ++delete_count; - } - } +~TestLeak() { +if (!moved_) { +++delete_count; +} +} - TestLeak& operator=(const TestLeak& _other) { - str_ = _other.str_; - return *this; - } +TestLeak& operator=(const TestLeak& _other) { +str_ = _other.str_; +return *this; +} - TestLeak& operator=(TestLeak&& _other) noexcept { - str_ = std::move(_other.str_); - _other.moved_ = true; - ++delete_count; - return *this; - } +TestLeak& operator=(TestLeak&& _other) noexcept { +str_ = std::move(_other.str_); +_other.moved_ = true; +++delete_count; +return *this; +} - const std::string& reflection() { return str_; } +const std::string& reflection() { return str_; } - private: - bool moved_; - std::string str_; +private: +bool moved_; +std::string str_; }; struct Person { - std::string first_name; - std::string last_name; - const TestLeak test; +std::string first_name; +std::string last_name; +const TestLeak test; }; TEST(json, test_leak) { - { - const auto r = rfl::json::read( - R"({"first_name":"Homer","last_name":"Simpson","test":"test"})"); - } - EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); +{ +const auto r = rfl::json::read( +R"({"first_name":"Homer","last_name":"Simpson","test":"test"})"); } +EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); +}*/ } // namespace test_leak diff --git a/tests/json/test_leak_arr.cpp b/tests/json/test_leak_arr.cpp index 943df9dff..46ad4c05c 100644 --- a/tests/json/test_leak_arr.cpp +++ b/tests/json/test_leak_arr.cpp @@ -5,7 +5,7 @@ #include "write_and_read.hpp" namespace test_leak_arr { - +/* class TestLeak { public: inline static int create_count = 0; @@ -59,5 +59,5 @@ TEST(json, test_leak_arr) { R"(["Homer","Simpson","test"])"); } EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -} +}*/ } // namespace test_leak_arr diff --git a/tests/json/test_leak_rfl_tup.cpp b/tests/json/test_leak_rfl_tup.cpp index 59a4ffe92..af895187b 100644 --- a/tests/json/test_leak_rfl_tup.cpp +++ b/tests/json/test_leak_rfl_tup.cpp @@ -5,60 +5,61 @@ #include "write_and_read.hpp" namespace test_leak_rfl_tup { - +// TODO +/* class TestLeak { - public: - inline static int create_count = 0; - inline static int delete_count = 0; +public: +inline static int create_count = 0; +inline static int delete_count = 0; - using ReflectionType = std::string; +using ReflectionType = std::string; - TestLeak(const std::string& _str) : moved_(false), str_(_str) { - ++create_count; - } +TestLeak(const std::string& _str) : moved_(false), str_(_str) { +++create_count; +} - TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { - ++create_count; - } +TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { +++create_count; +} - TestLeak(TestLeak&& _other) noexcept - : moved_(false), str_(std::move(_other.str_)) { - _other.moved_ = true; - ++create_count; - ++delete_count; - } +TestLeak(TestLeak&& _other) noexcept +: moved_(false), str_(std::move(_other.str_)) { +_other.moved_ = true; +++create_count; +++delete_count; +} - ~TestLeak() { - if (!moved_) { - ++delete_count; - } - } +~TestLeak() { +if (!moved_) { +++delete_count; +} +} - TestLeak& operator=(const TestLeak& _other) { - str_ = _other.str_; - return *this; - } +TestLeak& operator=(const TestLeak& _other) { +str_ = _other.str_; +return *this; +} - TestLeak& operator=(TestLeak&& _other) noexcept { - str_ = std::move(_other.str_); - _other.moved_ = true; - ++delete_count; - return *this; - } +TestLeak& operator=(TestLeak&& _other) noexcept { +str_ = std::move(_other.str_); +_other.moved_ = true; +++delete_count; +return *this; +} - const std::string& reflection() { return str_; } +const std::string& reflection() { return str_; } - private: - bool moved_; - std::string str_; +private: +bool moved_; +std::string str_; }; TEST(json, test_leak_rfl_tup) { - { - const auto r = - rfl::json::read>( - R"(["Homer","Simpson","test"])"); - } - EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); +{ +const auto r = +rfl::json::read>( + R"(["Homer","Simpson","test"])"); } +EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); +}*/ } // namespace test_leak_rfl_tup diff --git a/tests/json/test_leak_tup.cpp b/tests/json/test_leak_tup.cpp index 82ce358af..2da5731ee 100644 --- a/tests/json/test_leak_tup.cpp +++ b/tests/json/test_leak_tup.cpp @@ -6,7 +6,7 @@ #include "write_and_read.hpp" namespace test_leak_tup { - +/* class TestLeak { public: inline static int create_count = 0; @@ -61,5 +61,5 @@ TEST(json, test_leak_tup) { R"(["Homer","Simpson","test"])"); } EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -} +}*/ } // namespace test_leak_tup From 9588b90dd526bf206c02562d1a33717b32e434c0 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Wed, 9 Sep 2026 22:44:05 +0200 Subject: [PATCH 02/13] Redesigned the validator --- include/rfl/Validator.hpp | 65 +++++++------ include/rfl/parsing/ParserValidator.hpp | 119 ++++++++++++++++++++++++ include/rfl/parsing/Parser_default.hpp | 11 ++- 3 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 include/rfl/parsing/ParserValidator.hpp diff --git a/include/rfl/Validator.hpp b/include/rfl/Validator.hpp index b10c543dd..e2dc82215 100644 --- a/include/rfl/Validator.hpp +++ b/include/rfl/Validator.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -23,9 +24,9 @@ template requires internal::HasValidation, T> struct Validator { public: - using ReflectionType = T; using ValidationType = std::conditional_t>; + using Type = T; /// Exception-free validation from a value. /// @param _value The value to validate @@ -39,9 +40,14 @@ struct Validator { } } - /// Default constructor - validates a default-constructed T. - /// @throws std::exception if validation of the default value fails - Validator() : value_(ValidationType::validate(T()).value()) {} + /// Default constructor - if the default value is legal, assign default value, + /// else assign std::nullopt. + Validator() + : value_(from_value(T()) + .transform([](const auto& _v) { + return std::optional(_v.value()); + }) + .value_or(std::nullopt)) {} /// Move constructor. /// @param _other The validator to move from @@ -135,51 +141,45 @@ struct Validator { return *this; } - /// Equality operator for comparing with other Validators. - /// @param _other The other validator to compare with - /// @return true if the underlying values are equal - bool operator==(const Validator& _other) const { - return value() == _other.value(); - } + /// When used in a boolean context, returns true if the validator contains a + /// valid value, false otherwise. + operator bool() const { return value_.has_value(); } /// Returns the underlying object. - const T& get() const noexcept { return value_; } + const T& get() const noexcept { return *value_; } /// Returns the underlying object. - T& get() noexcept { return value_; } + T& get() noexcept { return *value_; } /// Returns the underlying object. - T& operator*() noexcept { return value_; } + T& operator*() noexcept { return *value_; } /// Returns the underlying object. - const T& operator*() const noexcept { return value_; } + const T& operator*() const noexcept { return *value_; } /// Returns the underlying object. - T& operator()() noexcept { return value_; } + T& operator()() noexcept { return value_.value(); } /// Returns the underlying object. - const T& operator()() const noexcept { return value_; } + const T& operator()() const noexcept { return value_.value(); } /// Pointer to the underlying value. /// @return Pointer to the stored value - T* operator->() noexcept { return &value_; } + T* operator->() noexcept { return &(*value_); } /// Pointer to the underlying value (const). /// @return Const pointer to the stored value - const T* operator->() const noexcept { return &value_; } + const T* operator->() const noexcept { return &(*value_); } /// Exposes the underlying value. - T& value() noexcept { return value_; } + T& value() noexcept { return value_.value(); } /// Exposes the underlying value. - const T& value() const noexcept { return value_; } - - /// Necessary for the serialization to work. - const T& reflection() const { return value_; } + const T& value() const noexcept { return value_.value(); } private: /// The underlying value. - T value_; + std::optional value_; }; /// Three-way comparison operator for validators with the same validation rules. @@ -190,9 +190,12 @@ struct Validator { /// @param _v2 The second validator to compare /// @return The ordering relationship between the underlying values template -inline auto operator<=>(const Validator& _v1, - const Validator& _v2) { - return _v1.value() <=> _v2.value(); +inline std::partial_ordering operator<=>(const Validator& _v1, + const Validator& _v2) { + if (!_v1 || !_v2) { + return std::partial_ordering::unordered; + } + return *_v1 <=> *_v2; } /// Three-way comparison operator for comparing a validator with a raw value. @@ -204,8 +207,12 @@ inline auto operator<=>(const Validator& _v1, /// @return The ordering relationship between the validator's value and the raw /// value template -inline auto operator<=>(const Validator& _v, const T& _t) { - return _v.value() <=> _t; +inline std::partial_ordering operator<=>(const Validator& _v, + const T& _t) { + if (!_v) { + return std::partial_ordering::unordered; + } + return *_v <=> _t; } } // namespace rfl diff --git a/include/rfl/parsing/ParserValidator.hpp b/include/rfl/parsing/ParserValidator.hpp new file mode 100644 index 000000000..1d0a8e535 --- /dev/null +++ b/include/rfl/parsing/ParserValidator.hpp @@ -0,0 +1,119 @@ +#ifndef RFL_PARSING_PARSER_VALIDATOR_HPP_ +#define RFL_PARSING_PARSER_VALIDATOR_HPP_ + +#include +#include +#include + +#include "../Ref.hpp" +#include "../Result.hpp" +#include "../Validator.hpp" +#include "Parent.hpp" +#include "Parser_base.hpp" +#include "schema/Type.hpp" +#include "schemaful/IsSchemafulReader.hpp" +#include "schemaful/IsSchemafulWriter.hpp" +#include "schemaful/OptionalReader.hpp" + +namespace rfl::parsing { + +template +struct is_validator : std::false_type {}; + +template +struct is_validator> : std::true_type {}; + +template +constexpr bool is_validator_v = is_validator>::value; + +template +struct ParserValidator { + using InputVarType = typename R::InputVarType; + using ParentType = Parent; + using T = std::remove_cvref_t; + + /** + * @brief Reads an optional value from the input. + * + * @param _r The reader to use. + * @param _var The input variable to read from. + * @return A Result containing the parsed optional value or an error. + */ + static Result read(const R& _r, + const InputVarType& _var) noexcept { + const auto to_validator = [](auto&& _optional) -> ValidatorType { + if (!_optional) { + return ValidatorType(); + } else { + return ValidatorType(std::move(*_optional)); + } + }; + + if constexpr (schemaful::IsSchemafulReader) { + using O = schemaful::OptionalReader, + ProcessorsType>; + const auto to_optional = [&](const auto& _u) -> Result> { + return _r.template read_union, O>(_u); + }; + return _r.to_union(_var).and_then(to_optional).transform(to_validator); + } else { + if (_r.is_empty(_var)) { + return ValidatorType(); + } + const auto to_optional = [](auto&& _t) { + return std::make_optional(std::move(_t)); + }; + return Parser, ProcessorsType>::read(_r, + _var) + .transform(to_optional) + .transform(to_validator); + } + } + + /** + * @brief Writes an optional value to the output. + * + * @tparam P The type of the parent. + * @param _w The writer to use. + * @param _o The optional value to write. + * @param _parent The parent object. + */ + template + static void write(const W& _w, const ValidatorType& _v, const P& _parent) { + if constexpr (schemaful::IsSchemafulWriter) { + auto u = ParentType::add_union(_w, _parent); + using UnionType = typename ParentType::template Union; + auto p = + UnionType{.index_ = static_cast(_v ? 0 : 1), .union_ = &u}; + if (_v) { + Parser, ProcessorsType>::write(_w, *_v, p); + } else { + ParentType::add_null(_w, p); + } + } else { + if (_v) { + Parser, ProcessorsType>::write(_w, *_v, + _parent); + } else { + ParentType::add_null(_w, _parent); + } + } + } + + /** + * @brief Generates the schema for the optional value. + * + * @param _definitions The map of definitions to add to. + * @return The schema type. + */ + static schema::Type to_schema( + std::map* _definitions) { + using U = std::remove_cvref_t; + return schema::Type{schema::Type::Optional{Ref::make( + Parser::to_schema(_definitions))}}; + } +}; + +} // namespace rfl::parsing + +#endif diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index ef68f3132..7ae820727 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -17,7 +17,6 @@ #include "../internal/is_literal.hpp" #include "../internal/is_validator.hpp" #include "../internal/processed_t.hpp" -#include "../internal/ptr_cast.hpp" #include "../internal/to_ptr_named_tuple.hpp" #include "../to_view.hpp" #include "AreReaderAndWriter.hpp" @@ -56,6 +55,7 @@ #include "ParserTimePoint.hpp" #include "ParserTuple.hpp" #include "ParserUniquePtr.hpp" +#include "ParserValidator.hpp" #include "ParserVariant.hpp" #include "ParserVectorstring.hpp" #include "ParserWString.hpp" @@ -197,6 +197,9 @@ struct Parser { } else if constexpr (std::is_enum_v) { return ParserEnum::read(_r, _var); + } else if constexpr (is_validator_v) { + return ParserValidator::read(_r, _var); + } else if constexpr (is_wstring_v) { return ParserWString::read(_r, _var); @@ -380,6 +383,9 @@ struct Parser { } else if constexpr (std::is_enum_v) { ParserEnum::write(_w, _var, _parent); + } else if constexpr (is_validator_v) { + ParserValidator::write(_w, _var, _parent); + } else if constexpr (is_wstring_v) { ParserWString::write(_w, _var, _parent); @@ -552,6 +558,9 @@ struct Parser { } else if constexpr (std::is_enum_v) { return ParserEnum::to_schema(_definitions); + } else if constexpr (is_validator_v) { + return ParserValidator::to_schema(_definitions); + } else if constexpr (is_wstring_v) { return ParserWString::to_schema(_definitions); From 234866b13bad01e8277a75b0a4f0ee41c7592423 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Thu, 10 Sep 2026 23:44:43 +0200 Subject: [PATCH 03/13] Redesign the Validator as wrapping a Result type --- include/rfl/Validator.hpp | 21 +++-- include/rfl/parsing/ParserValidator.hpp | 119 ------------------------ include/rfl/parsing/Parser_default.hpp | 10 -- 3 files changed, 11 insertions(+), 139 deletions(-) delete mode 100644 include/rfl/parsing/ParserValidator.hpp diff --git a/include/rfl/Validator.hpp b/include/rfl/Validator.hpp index e2dc82215..00b8633af 100644 --- a/include/rfl/Validator.hpp +++ b/include/rfl/Validator.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -26,7 +25,7 @@ struct Validator { public: using ValidationType = std::conditional_t>; - using Type = T; + using ReflectionType = Result; /// Exception-free validation from a value. /// @param _value The value to validate @@ -41,13 +40,11 @@ struct Validator { } /// Default constructor - if the default value is legal, assign default value, - /// else assign std::nullopt. - Validator() - : value_(from_value(T()) - .transform([](const auto& _v) { - return std::optional(_v.value()); - }) - .value_or(std::nullopt)) {} + /// else assign Error. + Validator() : value_(ValidationType::validate(T())) {} + + /// Constructs from a Result. + Validator(Result _value) : value_(std::move(_value)) {} /// Move constructor. /// @param _other The validator to move from @@ -171,6 +168,10 @@ struct Validator { /// @return Const pointer to the stored value const T* operator->() const noexcept { return &(*value_); } + /// Returns the underlying result type. + /// @return The underlying result type. + const Result& reflection() const noexcept { return value_; } + /// Exposes the underlying value. T& value() noexcept { return value_.value(); } @@ -179,7 +180,7 @@ struct Validator { private: /// The underlying value. - std::optional value_; + Result value_; }; /// Three-way comparison operator for validators with the same validation rules. diff --git a/include/rfl/parsing/ParserValidator.hpp b/include/rfl/parsing/ParserValidator.hpp deleted file mode 100644 index 1d0a8e535..000000000 --- a/include/rfl/parsing/ParserValidator.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef RFL_PARSING_PARSER_VALIDATOR_HPP_ -#define RFL_PARSING_PARSER_VALIDATOR_HPP_ - -#include -#include -#include - -#include "../Ref.hpp" -#include "../Result.hpp" -#include "../Validator.hpp" -#include "Parent.hpp" -#include "Parser_base.hpp" -#include "schema/Type.hpp" -#include "schemaful/IsSchemafulReader.hpp" -#include "schemaful/IsSchemafulWriter.hpp" -#include "schemaful/OptionalReader.hpp" - -namespace rfl::parsing { - -template -struct is_validator : std::false_type {}; - -template -struct is_validator> : std::true_type {}; - -template -constexpr bool is_validator_v = is_validator>::value; - -template -struct ParserValidator { - using InputVarType = typename R::InputVarType; - using ParentType = Parent; - using T = std::remove_cvref_t; - - /** - * @brief Reads an optional value from the input. - * - * @param _r The reader to use. - * @param _var The input variable to read from. - * @return A Result containing the parsed optional value or an error. - */ - static Result read(const R& _r, - const InputVarType& _var) noexcept { - const auto to_validator = [](auto&& _optional) -> ValidatorType { - if (!_optional) { - return ValidatorType(); - } else { - return ValidatorType(std::move(*_optional)); - } - }; - - if constexpr (schemaful::IsSchemafulReader) { - using O = schemaful::OptionalReader, - ProcessorsType>; - const auto to_optional = [&](const auto& _u) -> Result> { - return _r.template read_union, O>(_u); - }; - return _r.to_union(_var).and_then(to_optional).transform(to_validator); - } else { - if (_r.is_empty(_var)) { - return ValidatorType(); - } - const auto to_optional = [](auto&& _t) { - return std::make_optional(std::move(_t)); - }; - return Parser, ProcessorsType>::read(_r, - _var) - .transform(to_optional) - .transform(to_validator); - } - } - - /** - * @brief Writes an optional value to the output. - * - * @tparam P The type of the parent. - * @param _w The writer to use. - * @param _o The optional value to write. - * @param _parent The parent object. - */ - template - static void write(const W& _w, const ValidatorType& _v, const P& _parent) { - if constexpr (schemaful::IsSchemafulWriter) { - auto u = ParentType::add_union(_w, _parent); - using UnionType = typename ParentType::template Union; - auto p = - UnionType{.index_ = static_cast(_v ? 0 : 1), .union_ = &u}; - if (_v) { - Parser, ProcessorsType>::write(_w, *_v, p); - } else { - ParentType::add_null(_w, p); - } - } else { - if (_v) { - Parser, ProcessorsType>::write(_w, *_v, - _parent); - } else { - ParentType::add_null(_w, _parent); - } - } - } - - /** - * @brief Generates the schema for the optional value. - * - * @param _definitions The map of definitions to add to. - * @return The schema type. - */ - static schema::Type to_schema( - std::map* _definitions) { - using U = std::remove_cvref_t; - return schema::Type{schema::Type::Optional{Ref::make( - Parser::to_schema(_definitions))}}; - } -}; - -} // namespace rfl::parsing - -#endif diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 7ae820727..b1e2c9741 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -55,7 +55,6 @@ #include "ParserTimePoint.hpp" #include "ParserTuple.hpp" #include "ParserUniquePtr.hpp" -#include "ParserValidator.hpp" #include "ParserVariant.hpp" #include "ParserVectorstring.hpp" #include "ParserWString.hpp" @@ -197,9 +196,6 @@ struct Parser { } else if constexpr (std::is_enum_v) { return ParserEnum::read(_r, _var); - } else if constexpr (is_validator_v) { - return ParserValidator::read(_r, _var); - } else if constexpr (is_wstring_v) { return ParserWString::read(_r, _var); @@ -383,9 +379,6 @@ struct Parser { } else if constexpr (std::is_enum_v) { ParserEnum::write(_w, _var, _parent); - } else if constexpr (is_validator_v) { - ParserValidator::write(_w, _var, _parent); - } else if constexpr (is_wstring_v) { ParserWString::write(_w, _var, _parent); @@ -558,9 +551,6 @@ struct Parser { } else if constexpr (std::is_enum_v) { return ParserEnum::to_schema(_definitions); - } else if constexpr (is_validator_v) { - return ParserValidator::to_schema(_definitions); - } else if constexpr (is_wstring_v) { return ParserWString::to_schema(_definitions); From 863a894a6bed61f12aa95a4a1af7e75424e6d726 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 11 Sep 2026 22:15:12 +0200 Subject: [PATCH 04/13] Handle const fields in the NamedTuple --- include/rfl/internal/has_const_v.hpp | 24 +++++ include/rfl/internal/has_fields.hpp | 16 +--- .../rfl/internal/remove_cvref_fields_t.hpp | 24 +++++ include/rfl/parsing/MapParser.hpp | 6 +- include/rfl/parsing/MapReader.hpp | 3 + include/rfl/parsing/Parser_default.hpp | 27 ++++-- tests/json/test_leak.cpp | 93 +++++++++---------- tests/json/test_leak_arr.cpp | 4 +- 8 files changed, 125 insertions(+), 72 deletions(-) create mode 100644 include/rfl/internal/has_const_v.hpp create mode 100644 include/rfl/internal/remove_cvref_fields_t.hpp diff --git a/include/rfl/internal/has_const_v.hpp b/include/rfl/internal/has_const_v.hpp new file mode 100644 index 000000000..a68c5c937 --- /dev/null +++ b/include/rfl/internal/has_const_v.hpp @@ -0,0 +1,24 @@ +#ifndef RFL_INTERNAL_HASCONST_V_HPP_ +#define RFL_INTERNAL_HASCONST_V_HPP_ + +#include + +#include "../NamedTuple.hpp" + +namespace rfl::internal { + +template +struct has_const; + +template +struct has_const> { + constexpr static bool value = + (std::is_const_v> || ...); +}; + +template +inline constexpr bool has_const_v = has_const::value; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/internal/has_fields.hpp b/include/rfl/internal/has_fields.hpp index 412b87b09..63aaf78b2 100644 --- a/include/rfl/internal/has_fields.hpp +++ b/include/rfl/internal/has_fields.hpp @@ -5,15 +5,12 @@ #include #include "../Tuple.hpp" -//#include "all_fields.hpp" #include "is_field.hpp" #include "is_flatten_field.hpp" -#include "ptr_tuple_t.hpp" #include "is_named_tuple.hpp" +#include "ptr_tuple_t.hpp" - -namespace rfl { -namespace internal { +namespace rfl::internal { template constexpr bool all_fields_or_flatten() { @@ -30,8 +27,7 @@ constexpr bool all_fields_or_flatten() { return [&](std::integer_sequence) { return (true && ... && is_true_for_one(std::integral_constant{})); - } - (std::make_integer_sequence>()); + }(std::make_integer_sequence>()); } template @@ -50,8 +46,7 @@ constexpr bool some_fields_or_flatten() { return [&](std::integer_sequence) { return (false || ... || is_true_for_one(std::integral_constant{})); - } - (std::make_integer_sequence>()); + }(std::make_integer_sequence>()); } template @@ -74,7 +69,6 @@ constexpr bool has_fields() { } } -} // namespace internal -} // namespace rfl +} // namespace rfl::internal #endif diff --git a/include/rfl/internal/remove_cvref_fields_t.hpp b/include/rfl/internal/remove_cvref_fields_t.hpp new file mode 100644 index 000000000..19d64c8ec --- /dev/null +++ b/include/rfl/internal/remove_cvref_fields_t.hpp @@ -0,0 +1,24 @@ +#ifndef RFL_INTERNAL_REMOVE_CVREF_FIELDS_T_HPP_ +#define RFL_INTERNAL_REMOVE_CVREF_FIELDS_T_HPP_ + +#include + +#include "../NamedTuple.hpp" + +namespace rfl::internal { + +template +struct remove_cvref_fields; + +template +struct remove_cvref_fields> { + using Type = NamedTuple< + Field>...>; +}; + +template +using remove_cvref_fields_t = typename remove_cvref_fields::Type; + +} // namespace rfl::internal + +#endif diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index 748d6d5c3..bf574402c 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -16,8 +16,7 @@ #include "schemaful/IsSchemafulWriter.hpp" #include "to_single_error_message.hpp" -namespace rfl { -namespace parsing { +namespace rfl::parsing { template requires AreReaderAndWriter @@ -177,7 +176,6 @@ struct MapParser { } }; -} // namespace parsing -} // namespace rfl +} // namespace rfl::parsing #endif diff --git a/include/rfl/parsing/MapReader.hpp b/include/rfl/parsing/MapReader.hpp index fe0cd9a1e..187d74339 100644 --- a/include/rfl/parsing/MapReader.hpp +++ b/include/rfl/parsing/MapReader.hpp @@ -58,10 +58,13 @@ class MapReader { try { if constexpr (std::is_integral_v && std::is_signed_v) { return static_cast(std::stoll(_pair.first)); + } else if constexpr (std::is_integral_v && std::is_unsigned_v) { return static_cast(std::stoull(_pair.first)); + } else if constexpr (std::is_floating_point_v) { return static_cast(std::stod(_pair.first)); + } else { static_assert(always_false_v, "Unsupported type"); } diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index b1e2c9741..57de8c460 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -7,6 +7,7 @@ #include "../Result.hpp" #include "../always_false.hpp" #include "../internal/default_if_missing_v.hpp" +#include "../internal/has_const_v.hpp" #include "../internal/has_default_val_v.hpp" #include "../internal/has_reflection_method_v.hpp" #include "../internal/has_reflection_type_v.hpp" @@ -17,6 +18,7 @@ #include "../internal/is_literal.hpp" #include "../internal/is_validator.hpp" #include "../internal/processed_t.hpp" +#include "../internal/remove_cvref_fields_t.hpp" #include "../internal/to_ptr_named_tuple.hpp" #include "../to_view.hpp" #include "AreReaderAndWriter.hpp" @@ -680,15 +682,24 @@ struct Parser { /// and placement new. This is how we deal with the fact that some fields /// might not be default-constructible. static Result read_struct(const R& _r, const InputVarType& _var) { - T t{}; - auto view = ProcessorsType::template process(to_view(t)); - using ViewType = std::remove_cvref_t; - const auto [set, err] = - Parser::read_view(_r, _var, &view); - if (err) [[unlikely]] { - return error(err->what()); + using ViewType = std::remove_cvref_t>; + + if constexpr (internal::has_const_v) { + using NamedTupleType = internal::remove_cvref_fields_t>; + return Parser::read(_r, _var) + .transform( + [](auto&& _nt) { return from_named_tuple(std::move(_nt)); }); + + } else { + T t{}; + auto view = ProcessorsType::template process(to_view(t)); + const auto [set, err] = + Parser::read_view(_r, _var, &view); + if (err) [[unlikely]] { + return error(err->what()); + } + return t; } - return t; } /// This is actually more straight-forward than the standard case - we diff --git a/tests/json/test_leak.cpp b/tests/json/test_leak.cpp index 44f9e2bd9..b7434d045 100644 --- a/tests/json/test_leak.cpp +++ b/tests/json/test_leak.cpp @@ -2,69 +2,68 @@ #include #include -#include "write_and_read.hpp" - namespace test_leak { -// TODO -/* + class TestLeak { -public: -inline static int create_count = 0; -inline static int delete_count = 0; + public: + inline static int create_count = 0; + inline static int delete_count = 0; -using ReflectionType = std::string; + using ReflectionType = std::string; -TestLeak(const std::string& _str) : moved_(false), str_(_str) { -++create_count; -} + TestLeak() : TestLeak("") {} -TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { -++create_count; -} + TestLeak(const std::string& _str) : moved_(false), str_(_str) { + ++create_count; + } -TestLeak(TestLeak&& _other) noexcept -: moved_(false), str_(std::move(_other.str_)) { -_other.moved_ = true; -++create_count; -++delete_count; -} + TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { + ++create_count; + } -~TestLeak() { -if (!moved_) { -++delete_count; -} -} + TestLeak(TestLeak&& _other) noexcept + : moved_(false), str_(std::move(_other.str_)) { + _other.moved_ = true; + ++create_count; + ++delete_count; + } -TestLeak& operator=(const TestLeak& _other) { -str_ = _other.str_; -return *this; -} + ~TestLeak() { + if (!moved_) { + ++delete_count; + } + } -TestLeak& operator=(TestLeak&& _other) noexcept { -str_ = std::move(_other.str_); -_other.moved_ = true; -++delete_count; -return *this; -} + TestLeak& operator=(const TestLeak& _other) { + str_ = _other.str_; + return *this; + } + + TestLeak& operator=(TestLeak&& _other) noexcept { + str_ = std::move(_other.str_); + _other.moved_ = true; + ++delete_count; + return *this; + } -const std::string& reflection() { return str_; } + const std::string& reflection() { return str_; } -private: -bool moved_; -std::string str_; + private: + bool moved_; + std::string str_; }; struct Person { -std::string first_name; -std::string last_name; -const TestLeak test; + std::string first_name; + std::string last_name; + const TestLeak test; }; TEST(json, test_leak) { -{ -const auto r = rfl::json::read( -R"({"first_name":"Homer","last_name":"Simpson","test":"test"})"); + { + const auto r = rfl::json::read( + R"({"first_name":"Homer","last_name":"Simpson","test":"test"})"); + } + EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); } -EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -}*/ } // namespace test_leak diff --git a/tests/json/test_leak_arr.cpp b/tests/json/test_leak_arr.cpp index 46ad4c05c..a2555beed 100644 --- a/tests/json/test_leak_arr.cpp +++ b/tests/json/test_leak_arr.cpp @@ -2,8 +2,6 @@ #include #include -#include "write_and_read.hpp" - namespace test_leak_arr { /* class TestLeak { @@ -13,6 +11,8 @@ class TestLeak { using ReflectionType = std::string; + TestLeak() : moved_(false) {} + TestLeak(const std::string& _str) : moved_(false), str_(_str) { ++create_count; } From f996509da3e0a92a09b40ac97704fd427aab8aa3 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Fri, 11 Sep 2026 22:55:03 +0200 Subject: [PATCH 05/13] Got rid of the "set" array --- include/rfl/parsing/NamedTupleParser.hpp | 50 +++++++------------ include/rfl/parsing/Parser_default.hpp | 2 +- include/rfl/parsing/ViewReader.hpp | 30 ++++------- .../ViewReaderWithStrippedFieldNames.hpp | 24 ++------- 4 files changed, 35 insertions(+), 71 deletions(-) diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index 1ebc7e790..89212ee6e 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -89,7 +89,7 @@ struct NamedTupleParser { NamedTuple nt; auto view = rfl::to_view(nt); using ViewType = std::remove_cvref_t; - const auto [set, err] = + const auto err = Parser::read_view(_r, _var, &view); if (err) [[unlikely]] { return error(*err); @@ -106,22 +106,19 @@ struct NamedTupleParser { * @return A pair containing a boolean array indicating which fields were * found and an optional error. */ - static std::pair, - std::optional> - read_view(const R& _r, const InputVarType& _var, - NamedTuple* _view) noexcept { + static std::optional read_view( + const R& _r, const InputVarType& _var, + NamedTuple* _view) noexcept { if constexpr (_no_field_names) { auto arr = _r.to_array(_var); if (!arr) [[unlikely]] { - auto set = std::array{}; - return std::make_pair(set, arr.error()); + return arr.error(); } return read_object_or_array(_r, *arr, _view); } else { auto obj = _r.to_object(_var); if (!obj) [[unlikely]] { - auto set = std::array{}; - return std::make_pair(set, obj.error()); + return obj.error(); } return read_object_or_array(_r, *obj, _view); } @@ -282,7 +279,6 @@ struct NamedTupleParser { template static void handle_one_missing_field( const std::array& _found, const NamedTupleType& _view, - std::array* _set, std::vector* _errors) noexcept { using FieldType = internal::nth_element_t<_i, FieldTypes...>; using ValueType = std::remove_reference_t< @@ -301,10 +297,6 @@ struct NamedTupleParser { stream << "Field named '" << std::string(current_name) << "' not found."; _errors->emplace_back(stream.str()); - - } else if constexpr (!internal::has_default_val_v) { - *rfl::get<_i>(_view) = ValueType(); - std::get<_i>(*_set) = true; } } } @@ -313,9 +305,9 @@ struct NamedTupleParser { template static void handle_missing_fields( const std::array& _found, const NamedTupleType& _view, - std::array* _set, std::vector* _errors, + std::vector* _errors, std::integer_sequence) noexcept { - (handle_one_missing_field<_is>(_found, _view, _set, _errors), ...); + (handle_one_missing_field<_is>(_found, _view, _errors), ...); } static auto make_parent(const std::string_view& _name, @@ -327,34 +319,30 @@ struct NamedTupleParser { } } - static std::pair, - 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); + static std::optional read_object_or_array( + const R& _r, const InputObjectOrArrayType& _obj_or_arr, + NamedTupleType* _view) noexcept { + auto found = std::array{}; std::vector errors; errors.reserve(size_); - const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors); + const auto reader = ViewReaderType(&_r, _view, &found, &errors); if constexpr (_no_field_names) { const auto err = _r.read_array(reader, _obj_or_arr); if (err) { - return std::make_pair(set, err); + return err; } } else { const auto err = _r.read_object(reader, _obj_or_arr); if (err) { - return std::make_pair(set, err); + return err; } } - handle_missing_fields(found, *_view, &set, &errors, + handle_missing_fields(found, *_view, &errors, std::make_integer_sequence()); if (errors.size() != 0) { - return std::make_pair(set, to_single_error_message(errors)); + return to_single_error_message(errors); } - return std::make_pair(set, std::optional()); + return std::nullopt; } static std::optional read_object_or_array_with_default( @@ -376,7 +364,7 @@ struct NamedTupleParser { } if constexpr (internal::has_default_val_v && !internal::default_if_missing_v) { - handle_missing_fields(reader.found(), *_view, nullptr, &errors, + handle_missing_fields(reader.found(), *_view, &errors, std::make_integer_sequence()); } if (errors.size() != 0) { diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 57de8c460..5e66072e2 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -693,7 +693,7 @@ struct Parser { } else { T t{}; auto view = ProcessorsType::template process(to_view(t)); - const auto [set, err] = + const auto err = Parser::read_view(_r, _var, &view); if (err) [[unlikely]] { return error(err->what()); diff --git a/include/rfl/parsing/ViewReader.hpp b/include/rfl/parsing/ViewReader.hpp index ddc2c7e66..96d892e00 100644 --- a/include/rfl/parsing/ViewReader.hpp +++ b/include/rfl/parsing/ViewReader.hpp @@ -30,13 +30,11 @@ class ViewReader { * @param _r The reader to use. * @param _view The view to read into. * @param _found A boolean array indicating which fields have been found. - * @param _set A boolean array indicating which fields have been successfully - * set. * @param _errors The vector to collect errors in. */ ViewReader(const R* _r, ViewType* _view, std::array* _found, - std::array* _set, std::vector* _errors) - : r_(_r), view_(_view), found_(_found), set_(_set), errors_(_errors) {} + std::vector* _errors) + : r_(_r), view_(_view), found_(_found), errors_(_errors) {} ~ViewReader() = default; @@ -47,7 +45,7 @@ class ViewReader { * @param _var The input variable to read from. */ void read(const std::string_view& _name, const InputVarType& _var) const { - assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, set_, + assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, std::make_integer_sequence()); } @@ -58,7 +56,7 @@ class ViewReader { * @param _var The input variable to read from. */ void read(const int _index, const InputVarType& _var) const { - assign_to_matching_field(*r_, _index, _var, view_, errors_, found_, set_, + assign_to_matching_field(*r_, _index, _var, view_, errors_, found_, std::make_integer_sequence()); } @@ -84,7 +82,7 @@ class ViewReader { static void assign_if_field_matches(const R& _r, const auto _current_name_or_index, const auto& _var, auto* _view, - auto* _errors, auto* _found, auto* _set, + auto* _errors, auto* _found, bool* _already_assigned) { using FieldType = tuple_element_t; using OriginalType = typename FieldType::Type; @@ -108,7 +106,6 @@ class ViewReader { } else { rfl::get(*_view) = std::move(*res); } - std::get(*_set) = true; } } @@ -116,16 +113,13 @@ class ViewReader { static void assign_to_extra_fields(const R& _r, const std::string_view& _current_name, const auto& _var, auto* _view, - auto* _errors, auto* _found, auto* _set) { + auto* _errors, auto* _found) { auto* extra_fields = _view->template get<_pos>(); using ExtraFieldsType = std::remove_cvref_t>; using T = std::remove_cvref_t< std::remove_pointer_t>; - if (!std::get<_pos>(*_set)) { - std::get<_pos>(*_set) = true; - std::get<_pos>(*_found) = true; - } + std::get<_pos>(*_found) = true; auto res = Parser::read(_r, _var); if (!res) { std::stringstream stream; @@ -141,12 +135,12 @@ class ViewReader { static void assign_to_matching_field(const R& _r, const auto _current_name_or_index, const auto& _var, auto* _view, - auto* _errors, auto* _found, auto* _set, + auto* _errors, auto* _found, std::integer_sequence) { bool already_assigned = false; (assign_if_field_matches(_r, _current_name_or_index, _var, _view, - _errors, _found, _set, &already_assigned), + _errors, _found, &already_assigned), ...); if constexpr (ViewType::pos_extra_fields() != -1) { @@ -156,7 +150,7 @@ class ViewReader { constexpr int pos = ViewType::pos_extra_fields(); if (!already_assigned) { assign_to_extra_fields(_r, _current_name_or_index, _var, _view, - _errors, _found, _set); + _errors, _found); } } else if constexpr (internal::no_extra_fields_v) { static_assert( @@ -201,10 +195,6 @@ class ViewReader { /// 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_; }; diff --git a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp index 113ded56e..e0f84a49a 100644 --- a/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp +++ b/include/rfl/parsing/ViewReaderWithStrippedFieldNames.hpp @@ -29,20 +29,12 @@ class ViewReaderWithStrippedFieldNames { * @param _r The reader to use. * @param _view The view to read into. * @param _found A boolean array indicating which fields have been found. - * @param _set A boolean array indicating which fields have been successfully - * set. * @param _errors The vector to collect errors in. */ 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) {} + : i_(0), r_(_r), view_(_view), found_(_found), errors_(_errors) {} ~ViewReaderWithStrippedFieldNames() = default; @@ -59,7 +51,7 @@ class ViewReaderWithStrippedFieldNames { << " fields, but got at least one more."; return Error(stream.str()); } - assign_to_field_i(*r_, _var, view_, errors_, found_, set_, i_, + assign_to_field_i(*r_, _var, view_, errors_, found_, i_, std::make_integer_sequence()); ++i_; return std::nullopt; @@ -76,7 +68,7 @@ class ViewReaderWithStrippedFieldNames { 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) { + auto* _found, int _i) { using FieldType = tuple_element_t; using OriginalType = typename FieldType::Type; using T = @@ -97,16 +89,14 @@ class ViewReaderWithStrippedFieldNames { } 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, + auto* _errors, auto* _found, int _i, std::integer_sequence) { - (assign_if_field_is_field_i(_r, _var, _view, _errors, _found, _set, _i), - ...); + (assign_if_field_is_field_i(_r, _var, _view, _errors, _found, _i), ...); } // TODO: Unnecessary code duplication. @@ -143,10 +133,6 @@ class ViewReaderWithStrippedFieldNames { /// 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_; }; From 981bd034e4225086b193db40ccd2f91c85becba2 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 13 Sep 2026 13:41:06 +0200 Subject: [PATCH 06/13] Adapted ParserArray --- include/rfl/parsing/ArrayReader.hpp | 5 +---- include/rfl/parsing/ParserArray.hpp | 14 +++++++++++--- include/rfl/parsing/Parser_default.hpp | 3 +-- tests/json/test_leak_arr.cpp | 9 ++++++--- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/rfl/parsing/ArrayReader.hpp b/include/rfl/parsing/ArrayReader.hpp index b4f302742..7f424c132 100644 --- a/include/rfl/parsing/ArrayReader.hpp +++ b/include/rfl/parsing/ArrayReader.hpp @@ -81,10 +81,7 @@ class ArrayReader { */ template void move_to(Target* _t, Source* _s) const { - if constexpr (std::is_const_v) { - return move_to(const_cast*>(_t), _s); - } else if constexpr (!internal::is_array_v && - !std::is_array_v) { + if constexpr (!internal::is_array_v && !std::is_array_v) { *_t = Target(std::move(*_s)); } else if constexpr (internal::is_array_v) { for (size_t i = 0; i < _s->arr_.size(); ++i) { diff --git a/include/rfl/parsing/ParserArray.hpp b/include/rfl/parsing/ParserArray.hpp index cfc2f34d2..63a924bb5 100644 --- a/include/rfl/parsing/ParserArray.hpp +++ b/include/rfl/parsing/ParserArray.hpp @@ -2,6 +2,7 @@ #define RFL_PARSING_PARSER_ARRAY_HPP_ #include +#include #include #include @@ -46,9 +47,10 @@ struct ParserArray { const InputVarType& _var) noexcept { const auto parse = [&](const InputArrayType& _arr) -> Result> { - std::array arr{}; + std::array, _size> arr{}; const auto array_reader = - ArrayReader(&_r, &arr); + ArrayReader, _size>( + &_r, &arr); auto err = _r.read_array(array_reader, _arr); if (err) { return error(*err); @@ -57,7 +59,13 @@ struct ParserArray { if (err) { return error(*err); } - return arr; + if constexpr (std::is_same_v>) { + return arr; + } else { + return [&](std::index_sequence<_is...>) { + return std::array{std::move(std::get<_is>(arr))...}; + }(std::make_index_sequence<_size>{}); + } }; return _r.to_array(_var).and_then(parse); diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 5e66072e2..7ecc89605 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -241,8 +241,7 @@ struct Parser { using ReflectionType = std::remove_cvref_t; const auto wrap_in_t = [](auto&& _named_tuple) -> Result { try { - using NT = decltype(_named_tuple); - return T{std::forward(_named_tuple)}; + return T{std::move(_named_tuple)}; } catch (std::exception& e) { return error(e.what()); } diff --git a/tests/json/test_leak_arr.cpp b/tests/json/test_leak_arr.cpp index a2555beed..0d0279440 100644 --- a/tests/json/test_leak_arr.cpp +++ b/tests/json/test_leak_arr.cpp @@ -3,7 +3,7 @@ #include namespace test_leak_arr { -/* + class TestLeak { public: inline static int create_count = 0; @@ -11,7 +11,7 @@ class TestLeak { using ReflectionType = std::string; - TestLeak() : moved_(false) {} + TestLeak() : moved_(false), str_("") { ++create_count; } TestLeak(const std::string& _str) : moved_(false), str_(_str) { ++create_count; @@ -40,6 +40,9 @@ class TestLeak { } TestLeak& operator=(TestLeak&& _other) noexcept { + if (this == &_other) { + return *this; + } str_ = std::move(_other.str_); _other.moved_ = true; ++delete_count; @@ -59,5 +62,5 @@ TEST(json, test_leak_arr) { R"(["Homer","Simpson","test"])"); } EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -}*/ +} } // namespace test_leak_arr From 4a0cb7013cd7233f96780581253666b9e0ddccbc Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Sun, 13 Sep 2026 14:18:26 +0200 Subject: [PATCH 07/13] Adapted tuple --- include/rfl/internal/has_const_v.hpp | 14 +++ .../rfl/internal/remove_cvref_fields_t.hpp | 13 +++ include/rfl/parsing/TupleParser.hpp | 24 +++-- include/rfl/parsing/TupleReader.hpp | 6 +- tests/json/test_leak_rfl_tup.cpp | 89 +++++++++---------- tests/json/test_leak_tup.cpp | 6 +- 6 files changed, 91 insertions(+), 61 deletions(-) diff --git a/include/rfl/internal/has_const_v.hpp b/include/rfl/internal/has_const_v.hpp index a68c5c937..8664f322d 100644 --- a/include/rfl/internal/has_const_v.hpp +++ b/include/rfl/internal/has_const_v.hpp @@ -1,9 +1,11 @@ #ifndef RFL_INTERNAL_HASCONST_V_HPP_ #define RFL_INTERNAL_HASCONST_V_HPP_ +#include #include #include "../NamedTuple.hpp" +#include "../Tuple.hpp" namespace rfl::internal { @@ -16,6 +18,18 @@ struct has_const> { (std::is_const_v> || ...); }; +template +struct has_const> { + constexpr static bool value = + (std::is_const_v> || ...); +}; + +template +struct has_const> { + constexpr static bool value = + (std::is_const_v> || ...); +}; + template inline constexpr bool has_const_v = has_const::value; diff --git a/include/rfl/internal/remove_cvref_fields_t.hpp b/include/rfl/internal/remove_cvref_fields_t.hpp index 19d64c8ec..5f0105fe1 100644 --- a/include/rfl/internal/remove_cvref_fields_t.hpp +++ b/include/rfl/internal/remove_cvref_fields_t.hpp @@ -1,9 +1,12 @@ #ifndef RFL_INTERNAL_REMOVE_CVREF_FIELDS_T_HPP_ #define RFL_INTERNAL_REMOVE_CVREF_FIELDS_T_HPP_ +#include #include +#include "../Field.hpp" #include "../NamedTuple.hpp" +#include "../Tuple.hpp" namespace rfl::internal { @@ -16,6 +19,16 @@ struct remove_cvref_fields> { Field>...>; }; +template +struct remove_cvref_fields> { + using Type = Tuple...>; +}; + +template +struct remove_cvref_fields> { + using Type = std::tuple...>; +}; + template using remove_cvref_fields_t = typename remove_cvref_fields::Type; diff --git a/include/rfl/parsing/TupleParser.hpp b/include/rfl/parsing/TupleParser.hpp index 1a274cdcc..258bb432a 100644 --- a/include/rfl/parsing/TupleParser.hpp +++ b/include/rfl/parsing/TupleParser.hpp @@ -8,12 +8,11 @@ #include "../Result.hpp" #include "../Tuple.hpp" +#include "../internal/has_const_v.hpp" +#include "../internal/remove_cvref_fields_t.hpp" #include "Parent.hpp" #include "TupleReader.hpp" -#include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" -#include "schemaful/IsSchemafulWriter.hpp" -#include "schemaful/tuple_to_named_tuple.hpp" #include "schemaful/tuple_to_named_tuple_t.hpp" namespace rfl::parsing { @@ -40,19 +39,20 @@ struct TupleParser { if constexpr (schemaful::IsSchemafulReader) { using NamedTupleType = schemaful::tuple_to_named_tuple_t; const auto to_tuple = [](auto&& _named_tuple) { - return [&](std::integer_sequence) { + return [&](std::index_sequence<_is...>) { return TupleType(std::move(rfl::get<_is>(_named_tuple))...); - }(std::make_integer_sequence()); + }(std::make_index_sequence()); }; return Parser::read(_r, _var) .transform(to_tuple); } else { const auto parse = [&](const InputArrayType& _arr) -> Result { - TupleType tup{}; + internal::remove_cvref_fields_t tup{}; const auto tuple_reader = - TupleReader(&_r, &tup); + TupleReader, + _ignore_empty_containers, _all_required, + ProcessorsType>(&_r, &tup); auto err = _r.read_array(tuple_reader, _arr); if (err) { return error(*err); @@ -61,7 +61,13 @@ struct TupleParser { if (err) { return error(*err); } - return tup; + if constexpr (!internal::has_const_v) { + return tup; + } else { + return [&](std::index_sequence<_is...>) { + return TupleType(std::move(rfl::get<_is>(tup))...); + }(std::make_index_sequence>()); + } }; return _r.to_array(_var).and_then(parse); diff --git a/include/rfl/parsing/TupleReader.hpp b/include/rfl/parsing/TupleReader.hpp index 9ef40ff10..f0a30325c 100644 --- a/include/rfl/parsing/TupleReader.hpp +++ b/include/rfl/parsing/TupleReader.hpp @@ -7,7 +7,6 @@ #include "../Result.hpp" #include "../Tuple.hpp" #include "../internal/is_array.hpp" -#include "AreReaderAndWriter.hpp" #include "Parser_base.hpp" #include "is_required.hpp" @@ -117,10 +116,7 @@ class TupleReader { template void move_to(Target* _t, Source* _s) const { - if constexpr (std::is_const_v) { - return move_to(const_cast*>(_t), _s); - } else if constexpr (!internal::is_array_v && - !std::is_array_v) { + if constexpr (!internal::is_array_v && !std::is_array_v) { *_t = Target(std::move(*_s)); } else if constexpr (internal::is_array_v) { for (size_t i = 0; i < _s->arr_.size(); ++i) { diff --git a/tests/json/test_leak_rfl_tup.cpp b/tests/json/test_leak_rfl_tup.cpp index af895187b..2fe632751 100644 --- a/tests/json/test_leak_rfl_tup.cpp +++ b/tests/json/test_leak_rfl_tup.cpp @@ -2,64 +2,63 @@ #include #include -#include "write_and_read.hpp" - namespace test_leak_rfl_tup { -// TODO -/* + class TestLeak { -public: -inline static int create_count = 0; -inline static int delete_count = 0; + public: + inline static int create_count = 0; + inline static int delete_count = 0; -using ReflectionType = std::string; + using ReflectionType = std::string; -TestLeak(const std::string& _str) : moved_(false), str_(_str) { -++create_count; -} + TestLeak() : moved_(false), str_("") { ++create_count; } -TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { -++create_count; -} + TestLeak(const std::string& _str) : moved_(false), str_(_str) { + ++create_count; + } -TestLeak(TestLeak&& _other) noexcept -: moved_(false), str_(std::move(_other.str_)) { -_other.moved_ = true; -++create_count; -++delete_count; -} + TestLeak(const TestLeak& _other) : moved_(false), str_(_other.str_) { + ++create_count; + } -~TestLeak() { -if (!moved_) { -++delete_count; -} -} + TestLeak(TestLeak&& _other) noexcept + : moved_(false), str_(std::move(_other.str_)) { + _other.moved_ = true; + ++create_count; + ++delete_count; + } -TestLeak& operator=(const TestLeak& _other) { -str_ = _other.str_; -return *this; -} + ~TestLeak() { + if (!moved_) { + ++delete_count; + } + } -TestLeak& operator=(TestLeak&& _other) noexcept { -str_ = std::move(_other.str_); -_other.moved_ = true; -++delete_count; -return *this; -} + TestLeak& operator=(const TestLeak& _other) { + str_ = _other.str_; + return *this; + } + + TestLeak& operator=(TestLeak&& _other) noexcept { + str_ = std::move(_other.str_); + _other.moved_ = true; + ++delete_count; + return *this; + } -const std::string& reflection() { return str_; } + const std::string& reflection() { return str_; } -private: -bool moved_; -std::string str_; + private: + bool moved_; + std::string str_; }; TEST(json, test_leak_rfl_tup) { -{ -const auto r = -rfl::json::read>( - R"(["Homer","Simpson","test"])"); + { + const auto r = + rfl::json::read>( + R"(["Homer","Simpson","test"])"); + } + EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); } -EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -}*/ } // namespace test_leak_rfl_tup diff --git a/tests/json/test_leak_tup.cpp b/tests/json/test_leak_tup.cpp index 2da5731ee..40212cbf7 100644 --- a/tests/json/test_leak_tup.cpp +++ b/tests/json/test_leak_tup.cpp @@ -6,7 +6,7 @@ #include "write_and_read.hpp" namespace test_leak_tup { -/* + class TestLeak { public: inline static int create_count = 0; @@ -14,6 +14,8 @@ class TestLeak { using ReflectionType = std::string; + TestLeak() : moved_(false), str_("") { ++create_count; } + TestLeak(const std::string& _str) : moved_(false), str_(_str) { ++create_count; } @@ -61,5 +63,5 @@ TEST(json, test_leak_tup) { R"(["Homer","Simpson","test"])"); } EXPECT_EQ(TestLeak::delete_count, TestLeak::create_count); -}*/ +} } // namespace test_leak_tup From 91a2cba1f26138212ffab94ffa833ee85d9e3915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Sat, 10 Oct 2026 16:45:47 +0200 Subject: [PATCH 08/13] Validators as map types --- include/rfl/Validator.hpp | 3 +- include/rfl/parsing/MapParser.hpp | 107 +++++++++++++------------- include/rfl/parsing/MapReader.hpp | 45 +++++++---- include/rfl/parsing/is_string_map.hpp | 22 ++++++ 4 files changed, 104 insertions(+), 73 deletions(-) diff --git a/include/rfl/Validator.hpp b/include/rfl/Validator.hpp index 00b8633af..748e45559 100644 --- a/include/rfl/Validator.hpp +++ b/include/rfl/Validator.hpp @@ -44,7 +44,8 @@ struct Validator { Validator() : value_(ValidationType::validate(T())) {} /// Constructs from a Result. - Validator(Result _value) : value_(std::move(_value)) {} + Validator(Result _value) + : value_(ValidationType::validate(std::move(_value).value()).value()) {} /// Move constructor. /// @param _other The validator to move from diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index bf574402c..60024f27c 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -7,9 +7,9 @@ #include "../Ref.hpp" #include "../Result.hpp" -#include "../always_false.hpp" #include "MapReader.hpp" #include "Parent.hpp" +#include "ParserResult.hpp" #include "Parser_base.hpp" #include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" @@ -109,71 +109,68 @@ struct MapParser { typename ParentType::template Map; for (const auto& [k, v] : _m) { - if constexpr (internal::has_reflection_type_v) { - using ReflT = typename KeyType::ReflectionType; - - if constexpr (std::is_integral_v || - std::is_floating_point_v) { - const auto name = std::to_string(k.reflection()); - const auto new_parent = ParentMapType{name, &m}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } else { - const auto name = k.reflection(); - const auto new_parent = ParentMapType{name, &m}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } - - } else if constexpr (std::is_integral_v || - std::is_floating_point_v) { - const auto name = std::to_string(k); - const auto new_parent = ParentMapType{name, &m}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } else { - const auto new_parent = ParentMapType{k, &m}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } + write_map_elem(_w, k, v, &m); } _w.end_map(&m); } + template + static void write_map_elem(const W& _w, const auto& _key, const auto& _value, + auto* _m) { + using KeyType = std::remove_cvref_t; + + if constexpr (std::is_integral_v || + std::is_floating_point_v) { + const auto name = std::to_string(_key); + const auto new_parent = ParentMapType{name, _m}; + Parser, ProcessorsType>::write( + _w, _value, new_parent); + + } else if constexpr (internal::has_reflection_type_v) { + write_map_elem(_w, _key.reflection(), _value, _m); + + } else if constexpr (is_result_v) { + write_object_elem(_w, _key.value(), _value, _m); + + } else { + const auto new_parent = ParentMapType{_key, _m}; + Parser, ProcessorsType>::write( + _w, _value, new_parent); + } + } + template static void write_object(const W& _w, const MapType& _m, const P& _parent) { auto obj = ParentType::add_object(_w, _m.size(), _parent); for (const auto& [k, v] : _m) { - if constexpr (internal::has_reflection_type_v) { - using ReflT = typename KeyType::ReflectionType; - - if constexpr (std::is_integral_v || - std::is_floating_point_v) { - const auto name = std::to_string(k.reflection()); - const auto new_parent = typename ParentType::Object{name, &obj}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } else { - const auto name = k.reflection(); - const auto new_parent = typename ParentType::Object{name, &obj}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } - - } else if constexpr (std::is_integral_v || - std::is_floating_point_v) { - const auto name = std::to_string(k); - const auto new_parent = typename ParentType::Object{name, &obj}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } else { - const auto new_parent = typename ParentType::Object{k, &obj}; - Parser, ProcessorsType>::write( - _w, v, new_parent); - } + write_object_elem(_w, k, v, &obj); } _w.end_object(&obj); } + + static void write_object_elem(const W& _w, const auto& _key, + const auto& _value, auto* _obj) { + using KeyType = std::remove_cvref_t; + + if constexpr (std::is_integral_v || + std::is_floating_point_v) { + const auto name = std::to_string(_key); + const auto new_parent = typename ParentType::Object{name, _obj}; + Parser, ProcessorsType>::write( + _w, _value, new_parent); + + } else if constexpr (internal::has_reflection_type_v) { + write_object_elem(_w, _key.reflection(), _value, _obj); + + } else if constexpr (is_result_v) { + write_object_elem(_w, _key.value(), _value, _obj); + + } else { + const auto new_parent = typename ParentType::Object{_key, _obj}; + Parser, ProcessorsType>::write( + _w, _value, new_parent); + } + } }; } // namespace rfl::parsing diff --git a/include/rfl/parsing/MapReader.hpp b/include/rfl/parsing/MapReader.hpp index 187d74339..269860b34 100644 --- a/include/rfl/parsing/MapReader.hpp +++ b/include/rfl/parsing/MapReader.hpp @@ -10,6 +10,7 @@ #include "../Result.hpp" #include "../always_false.hpp" #include "../internal/has_reflection_type_v.hpp" +#include "ParserResult.hpp" #include "Parser_base.hpp" namespace rfl::parsing { @@ -73,33 +74,43 @@ class MapReader { } } - Result> make_key(auto&& _pair) const noexcept { + template + Result> make_key(auto&& _pair) const noexcept { static_assert(std::is_rvalue_reference_v, "Expected an rvalue"); const auto to_pair = - [&](auto&& _key) -> Result> { + [&](auto&& _key) -> Result> { try { using K = decltype(_key); - return std::make_pair(KeyType(std::forward(_key)), + return std::make_pair(KeyT(std::forward(_key)), std::move(_pair.second)); } catch (std::exception& e) { return error(e.what()); } }; - if constexpr (std::is_integral_v || - std::is_floating_point_v) { - return key_to_numeric(_pair).and_then(to_pair); - - } else if constexpr (internal::has_reflection_type_v) { - using ReflT = typename KeyType::ReflectionType; - - if constexpr (std::is_integral_v || - std::is_floating_point_v) { - return key_to_numeric(_pair).and_then(to_pair); - } else { - return to_pair(std::move(_pair.first)); - } + if constexpr (std::is_integral_v || std::is_floating_point_v) { + return key_to_numeric(_pair).and_then(to_pair); + + } else if constexpr (internal::has_reflection_type_v) { + using ReflT = typename KeyT::ReflectionType; + return make_key(std::move(_pair)) + .and_then([](auto&& _p) -> Result> { + try { + return std::make_pair(KeyT(std::move(_p.first)), + std::move(_p.second)); + } catch (const std::exception& e) { + return error(e.what()); + } + }); + + } else if constexpr (is_result_v) { + using ValT = typename KeyT::value_type; + return make_key(std::move(_pair)) + .transform([](auto&& _p) -> std::pair { + return std::make_pair(KeyT(std::move(_p.first)), + std::move(_p.second)); + }); } else { return std::move(_pair); @@ -110,7 +121,7 @@ class MapReader { const std::string_view& _name, const InputVarType& _var) const noexcept { const auto to_pair = [&](ValueType&& _val) { auto pair = std::make_pair(std::string(_name), std::move(_val)); - return make_key(std::move(pair)); + return make_key(std::move(pair)); }; return Parser, ProcessorsType>::read( *r_, _var) diff --git a/include/rfl/parsing/is_string_map.hpp b/include/rfl/parsing/is_string_map.hpp index 1c4069d03..9c2c75bcc 100644 --- a/include/rfl/parsing/is_string_map.hpp +++ b/include/rfl/parsing/is_string_map.hpp @@ -8,6 +8,7 @@ #include "../ExtraFields.hpp" #include "../Object.hpp" +#include "../Validator.hpp" namespace rfl ::parsing { @@ -23,6 +24,27 @@ struct is_string_map< std::unordered_map> : std::true_type {}; +template +struct is_string_map< + std::map, T, Compare, Allocator>> + : std::true_type {}; + +template +struct is_string_map< + std::unordered_map, T, Compare, Allocator>> + : std::true_type {}; + +template + requires(std::is_integral_v || std::is_floating_point_v) +struct is_string_map, U, Compare, Allocator>> + : std::true_type {}; + +template + requires(std::is_integral_v || std::is_floating_point_v) +struct is_string_map< + std::unordered_map, U, Compare, Allocator>> + : std::true_type {}; + template struct is_string_map> : std::true_type {}; From 4f94c1a1f106ca0b4fd779d2093c1ae152e18053 Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Mon, 12 Oct 2026 23:34:13 +0200 Subject: [PATCH 09/13] Minor fixes --- include/rfl/Validator.hpp | 1 + include/rfl/parsing/MapParser.hpp | 2 +- include/rfl/parsing/ParserValidator.hpp | 69 +++++++++++++++++++++++++ include/rfl/parsing/Parser_default.hpp | 27 ++++------ tests/json/test_leak_tup.cpp | 2 - 5 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 include/rfl/parsing/ParserValidator.hpp diff --git a/include/rfl/Validator.hpp b/include/rfl/Validator.hpp index 748e45559..a37a8352c 100644 --- a/include/rfl/Validator.hpp +++ b/include/rfl/Validator.hpp @@ -26,6 +26,7 @@ struct Validator { using ValidationType = std::conditional_t>; using ReflectionType = Result; + using ValueType = T; /// Exception-free validation from a value. /// @param _value The value to validate diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index 60024f27c..32885d4e0 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -130,7 +130,7 @@ struct MapParser { write_map_elem(_w, _key.reflection(), _value, _m); } else if constexpr (is_result_v) { - write_object_elem(_w, _key.value(), _value, _m); + write_map_elem(_w, _key.value(), _value, _m); } else { const auto new_parent = ParentMapType{_key, _m}; diff --git a/include/rfl/parsing/ParserValidator.hpp b/include/rfl/parsing/ParserValidator.hpp new file mode 100644 index 000000000..ed9c11996 --- /dev/null +++ b/include/rfl/parsing/ParserValidator.hpp @@ -0,0 +1,69 @@ +#ifndef RFL_PARSING_PARSER_VALIDATOR_HPP_ +#define RFL_PARSING_PARSER_VALIDATOR_HPP_ + +#include +#include +#include +#include + +#include "../NamedTuple.hpp" +#include "../Result.hpp" +#include "../Validator.hpp" +#include "../internal/add_tags_to_variants_v.hpp" +#include "../internal/all_fields.hpp" +#include "../internal/to_ptr_field.hpp" +#include "../visit.hpp" +#include "FieldVariantParser.hpp" +#include "Parent.hpp" +#include "Parser_base.hpp" +#include "VariantAlternativeWrapper.hpp" +#include "schema/Type.hpp" +#include "schemaful/IsSchemafulReader.hpp" +#include "schemaful/IsSchemafulWriter.hpp" +#include "schemaful/VariantReader.hpp" +#include "to_single_error_message.hpp" + +namespace rfl::parsing { + +template +struct is_validator : std::false_type {}; + +template +struct is_validator> : std::true_type {}; + +template +constexpr bool is_validator_v = is_validator>::value; + +template +struct ParserValidator { + using InputVarType = typename R::InputVarType; + + static Result read(const R& _r, const InputVarType& _var) noexcept { + using ValueType = std::remove_cvref_t; + return Parser::read(_r, _var).and_then( + [](auto&& _value) -> Result { + return T::from_value(std::move(_value)); + }); + } + + template + static void write(const W& _w, const T& _val, const P& _parent) { + using ValueType = std::remove_cvref_t; + Parser::write(_w, _val.value(), _parent); + } + + static schema::Type to_schema( + std::map* _definitions) { + using Type = schema::Type; + using ValueType = std::remove_cvref_t; + using ValidationType = std::remove_cvref_t; + return Type{Type::Validated{ + .type_ = Ref::make( + Parser::to_schema(_definitions)), + .validation_ = ValidationType::template to_schema()}}; + } +}; + +} // namespace rfl::parsing + +#endif diff --git a/include/rfl/parsing/Parser_default.hpp b/include/rfl/parsing/Parser_default.hpp index 4c3f2fe1e..e8f56bccf 100644 --- a/include/rfl/parsing/Parser_default.hpp +++ b/include/rfl/parsing/Parser_default.hpp @@ -17,7 +17,6 @@ #include "../internal/is_deprecated.hpp" #include "../internal/is_description.hpp" #include "../internal/is_literal.hpp" -#include "../internal/is_validator.hpp" #include "../internal/processed_t.hpp" #include "../internal/remove_cvref_fields_t.hpp" #include "../internal/to_ptr_named_tuple.hpp" @@ -58,6 +57,7 @@ #include "ParserTimePoint.hpp" #include "ParserTuple.hpp" #include "ParserUniquePtr.hpp" +#include "ParserValidator.hpp" #include "ParserVariant.hpp" #include "ParserVectorstring.hpp" #include "ParserWString.hpp" @@ -137,6 +137,9 @@ struct Parser { } else if constexpr (is_rename_v) { return ParserRename::read(_r, _var); + } else if constexpr (is_validator_v) { + return ParserValidator::read(_r, _var); + } else if constexpr (is_result_v) { return ParserResult::read(_r, _var); @@ -330,6 +333,9 @@ struct Parser { } else if constexpr (is_rename_v) { ParserRename::write(_w, _var, _parent); + } else if constexpr (is_validator_v) { + ParserValidator::write(_w, _var, _parent); + } else if constexpr (is_duration_v) { using U = std::remove_cvref_t; ParserDuration) { return ParserRename::to_schema(_definitions); + } else if constexpr (is_validator_v) { + return ParserValidator::to_schema(_definitions); + } else if constexpr (is_result_v) { return ParserResult::to_schema(_definitions); @@ -594,9 +603,6 @@ struct Parser { } else if constexpr (internal::is_literal_v) { return Type{Type::Literal{.values_ = U::strings()}}; - } else if constexpr (internal::is_validator_v) { - return make_validated(_definitions); - } else if constexpr (internal::has_reflection_type_v || internal::has_read_reflector || internal::has_write_reflector) { @@ -669,19 +675,6 @@ struct Parser { return Type{Type::Reference{name}}; } - template - static schema::Type make_validated( - std::map* _definitions) { - using Type = schema::Type; - using ReflectionType = std::remove_cvref_t; - using ValidationType = std::remove_cvref_t; - return Type{Type::Validated{ - .type_ = Ref::make( - Parser::to_schema( - _definitions)), - .validation_ = ValidationType::template to_schema()}}; - } - /// The way this works is that we allocate space on the stack in this size /// of the struct in which we then write the individual fields using views /// and placement new. This is how we deal with the fact that some fields diff --git a/tests/json/test_leak_tup.cpp b/tests/json/test_leak_tup.cpp index 40212cbf7..6b8af952e 100644 --- a/tests/json/test_leak_tup.cpp +++ b/tests/json/test_leak_tup.cpp @@ -3,8 +3,6 @@ #include #include -#include "write_and_read.hpp" - namespace test_leak_tup { class TestLeak { From e259d97a7523bc661daaff3f53bcdb08bfe3494a Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 13 Oct 2026 21:56:30 +0200 Subject: [PATCH 10/13] Minor bugfix --- include/rfl/parsing/is_required.hpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/include/rfl/parsing/is_required.hpp b/include/rfl/parsing/is_required.hpp index 30fe10f6a..d3c080c92 100644 --- a/include/rfl/parsing/is_required.hpp +++ b/include/rfl/parsing/is_required.hpp @@ -6,19 +6,15 @@ #include #include "../Generic.hpp" -#include "../Positional.hpp" -#include "../Rename.hpp" -#include "../Short.hpp" -#include "../internal/StringLiteral.hpp" #include "../internal/has_reflection_type_v.hpp" #include "../internal/is_positional.hpp" #include "../internal/is_rename.hpp" #include "../internal/is_short.hpp" +#include "ParserValidator.hpp" #include "is_map_like.hpp" #include "is_vector_like.hpp" -namespace rfl { -namespace parsing { +namespace rfl::parsing { /** * @brief Trait to check if a type is never required. @@ -53,16 +49,24 @@ constexpr bool is_never_required_v = is_never_required::value; template consteval bool is_required() { using Type = std::remove_cvref_t>; + if constexpr (internal::has_reflection_type_v && !std::is_same_v) { return is_required(); + + } else if constexpr (is_validator_v) { + return is_required(); + } else if constexpr (internal::is_rename_v) { return is_required(); + } else if constexpr (internal::is_positional_v) { return is_required(); + } else if constexpr (internal::is_short_v) { return is_required(); + } else { return !(is_never_required_v || (_ignore_empty_containers && @@ -70,7 +74,6 @@ consteval bool is_required() { } } -} // namespace parsing -} // namespace rfl +} // namespace rfl::parsing #endif From dc85a7cc1e55cd193d6dbbdb79e3d2545c266bbf Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 13 Oct 2026 22:08:39 +0200 Subject: [PATCH 11/13] Got rid of the validator as a reflection type --- include/rfl/Validator.hpp | 12 ++---------- include/rfl/parsing/MapParser.hpp | 7 +++++++ include/rfl/parsing/MapReader.hpp | 9 +++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/rfl/Validator.hpp b/include/rfl/Validator.hpp index a37a8352c..42822f1ee 100644 --- a/include/rfl/Validator.hpp +++ b/include/rfl/Validator.hpp @@ -25,7 +25,6 @@ struct Validator { public: using ValidationType = std::conditional_t>; - using ReflectionType = Result; using ValueType = T; /// Exception-free validation from a value. @@ -44,10 +43,6 @@ struct Validator { /// else assign Error. Validator() : value_(ValidationType::validate(T())) {} - /// Constructs from a Result. - Validator(Result _value) - : value_(ValidationType::validate(std::move(_value).value()).value()) {} - /// Move constructor. /// @param _other The validator to move from Validator(Validator&& _other) noexcept = default; @@ -59,7 +54,8 @@ struct Validator { /// Constructs from an rvalue of T (validates the value). /// @param _value The value to validate and wrap /// @throws std::exception if validation fails - Validator(T&& _value) : value_(ValidationType::validate(_value).value()) {} + Validator(T&& _value) + : value_(ValidationType::validate(std::forward(_value)).value()) {} /// Constructs from a const reference to T (validates the value). /// @param _value The value to validate and wrap @@ -170,10 +166,6 @@ struct Validator { /// @return Const pointer to the stored value const T* operator->() const noexcept { return &(*value_); } - /// Returns the underlying result type. - /// @return The underlying result type. - const Result& reflection() const noexcept { return value_; } - /// Exposes the underlying value. T& value() noexcept { return value_.value(); } diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index 32885d4e0..36ba67c24 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -10,6 +10,7 @@ #include "MapReader.hpp" #include "Parent.hpp" #include "ParserResult.hpp" +#include "ParserValidator.hpp" #include "Parser_base.hpp" #include "schema/Type.hpp" #include "schemaful/IsSchemafulReader.hpp" @@ -132,6 +133,9 @@ struct MapParser { } else if constexpr (is_result_v) { write_map_elem(_w, _key.value(), _value, _m); + } else if constexpr (is_validator_v) { + write_map_elem(_w, _key.value(), _value, _m); + } else { const auto new_parent = ParentMapType{_key, _m}; Parser, ProcessorsType>::write( @@ -165,6 +169,9 @@ struct MapParser { } else if constexpr (is_result_v) { write_object_elem(_w, _key.value(), _value, _obj); + } else if constexpr (is_validator_v) { + write_object_elem(_w, _key.value(), _value, _obj); + } else { const auto new_parent = typename ParentType::Object{_key, _obj}; Parser, ProcessorsType>::write( diff --git a/include/rfl/parsing/MapReader.hpp b/include/rfl/parsing/MapReader.hpp index 269860b34..341e9ffed 100644 --- a/include/rfl/parsing/MapReader.hpp +++ b/include/rfl/parsing/MapReader.hpp @@ -11,6 +11,7 @@ #include "../always_false.hpp" #include "../internal/has_reflection_type_v.hpp" #include "ParserResult.hpp" +#include "ParserValidator.hpp" #include "Parser_base.hpp" namespace rfl::parsing { @@ -112,6 +113,14 @@ class MapReader { std::move(_p.second)); }); + } else if constexpr (is_validator_v) { + using ValT = typename KeyT::ValueType; + return make_key(std::move(_pair)) + .transform([](auto&& _p) -> std::pair { + return std::make_pair(KeyT(std::move(_p.first)), + std::move(_p.second)); + }); + } else { return std::move(_pair); } From 1bf5649a0b510666272f71a08eec8a648139137c Mon Sep 17 00:00:00 2001 From: "Dr. Patrick Urbanke" Date: Tue, 13 Oct 2026 22:11:34 +0200 Subject: [PATCH 12/13] Added docstrings --- include/rfl/parsing/MapParser.hpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/include/rfl/parsing/MapParser.hpp b/include/rfl/parsing/MapParser.hpp index 36ba67c24..7b24aa47a 100644 --- a/include/rfl/parsing/MapParser.hpp +++ b/include/rfl/parsing/MapParser.hpp @@ -102,6 +102,14 @@ struct MapParser { return map; } + /** + * @brief Writes a map to the output. + * + * @tparam P The type of the parent. + * @param _w The writer to use. + * @param _m The map to write. + * @param _parent The parent object. + */ template static void write_map(const W& _w, const MapType& _m, const P& _parent) { auto m = ParentType::add_map(_w, _m.size(), _parent); @@ -115,6 +123,13 @@ struct MapParser { _w.end_map(&m); } + /** @brief Writes a map element to the output. + * @tparam ParentMapType The type of the parent map. + * @param _w The writer to use. + * @param _key The key of the map element. + * @param _value The value of the map element. + * @param _m The parent map. + * */ template static void write_map_elem(const W& _w, const auto& _key, const auto& _value, auto* _m) { @@ -143,6 +158,13 @@ struct MapParser { } } + /** @brief Writes an object to the output. + * + * @tparam P The type of the parent. + * @param _w The writer to use. + * @param _m The map to write. + * @param _parent The parent object. + */ template static void write_object(const W& _w, const MapType& _m, const P& _parent) { auto obj = ParentType::add_object(_w, _m.size(), _parent); @@ -152,6 +174,14 @@ struct MapParser { _w.end_object(&obj); } + /** @brief Writes an object element to the output. + * + * @tparam P The type of the parent. + * @param _w The writer to use. + * @param _key The key of the object element. + * @param _value The value of the object element. + * @param _obj The parent object. + */ static void write_object_elem(const W& _w, const auto& _key, const auto& _value, auto* _obj) { using KeyType = std::remove_cvref_t; From 62cd4c788f19e72c33e2e1d8e4a3f4a55400d449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Wed, 14 Oct 2026 21:20:14 +0200 Subject: [PATCH 13/13] Resolve regression 1: Maps with key validation --- include/rfl/parsing/MapReader.hpp | 11 ++++++----- ..._map_with_key_validation_with_wrong_key.cpp | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/json/test_map_with_key_validation_with_wrong_key.cpp diff --git a/include/rfl/parsing/MapReader.hpp b/include/rfl/parsing/MapReader.hpp index 341e9ffed..f1f4c6d96 100644 --- a/include/rfl/parsing/MapReader.hpp +++ b/include/rfl/parsing/MapReader.hpp @@ -115,11 +115,12 @@ class MapReader { } else if constexpr (is_validator_v) { using ValT = typename KeyT::ValueType; - return make_key(std::move(_pair)) - .transform([](auto&& _p) -> std::pair { - return std::make_pair(KeyT(std::move(_p.first)), - std::move(_p.second)); - }); + return make_key(std::move(_pair)).and_then([](auto&& _p) { + return KeyT::from_value(std::move(_p.first)) + .transform([&](auto&& _k) -> std::pair { + return std::make_pair(std::move(_k), std::move(_p.second)); + }); + }); } else { return std::move(_pair); diff --git a/tests/json/test_map_with_key_validation_with_wrong_key.cpp b/tests/json/test_map_with_key_validation_with_wrong_key.cpp new file mode 100644 index 000000000..beac54eac --- /dev/null +++ b/tests/json/test_map_with_key_validation_with_wrong_key.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_map_with_key_validation_with_wrong_key { + +using Between1and3 = rfl::Validator, rfl::Maximum<3>>; +using MapT = std::map; + +TEST(json, test_map_with_key_validation_with_wrong_key) { + const auto res = rfl::json::read(R"({"0":"zero"})"); + EXPECT_FALSE(res); +} + +} // namespace test_map_with_key_validation_with_wrong_key