Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions benchmarks/all/parser_inlining_cereal.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#include <rfl/FailFast.hpp>
#include <rfl/cereal.hpp>

#include "parser_inlining.hpp"
namespace parser_inlining {
template <class Record>
template <class Record, bool FailFast = false>
void BM_parser_inlining_cereal(benchmark::State& state) {
run<Record>(
state, [&](const auto& input) { return rfl::cereal::write(input); },
[&](const auto& bytes) {
return rfl::cereal::read<Data<Record>>(bytes);
if constexpr (FailFast) {
return rfl::cereal::read<Data<Record>, rfl::FailFast>(bytes);
} else {
return rfl::cereal::read<Data<Record>>(bytes);
}
});
}
BENCHMARK_TEMPLATE(BM_parser_inlining_cereal, Record2)
Expand All @@ -16,4 +21,10 @@ BENCHMARK_TEMPLATE(BM_parser_inlining_cereal, Record2)
BENCHMARK_TEMPLATE(BM_parser_inlining_cereal, Record16)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_cereal, Record2, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_cereal, Record16, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
} // namespace parser_inlining
17 changes: 15 additions & 2 deletions benchmarks/all/parser_inlining_json.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
#include <rfl/FailFast.hpp>
#include <rfl/json.hpp>

#include "parser_inlining.hpp"
namespace parser_inlining {
template <class Record>
template <class Record, bool FailFast = false>
void BM_parser_inlining_json(benchmark::State& state) {
run<Record>(
state, [&](const auto& input) { return rfl::json::write(input); },
[&](const auto& bytes) { return rfl::json::read<Data<Record>>(bytes); });
[&](const auto& bytes) {
if constexpr (FailFast) {
return rfl::json::read<Data<Record>, rfl::FailFast>(bytes);
} else {
return rfl::json::read<Data<Record>>(bytes);
}
});
}
BENCHMARK_TEMPLATE(BM_parser_inlining_json, Record2)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_json, Record16)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_json, Record2, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_json, Record16, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
} // namespace parser_inlining
15 changes: 13 additions & 2 deletions benchmarks/all/parser_inlining_msgpack.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#include <rfl/FailFast.hpp>
#include <rfl/msgpack.hpp>

#include "parser_inlining.hpp"
namespace parser_inlining {
template <class Record>
template <class Record, bool FailFast = false>
void BM_parser_inlining_msgpack(benchmark::State& state) {
run<Record>(
state, [&](const auto& input) { return rfl::msgpack::write(input); },
[&](const auto& bytes) {
return rfl::msgpack::read<Data<Record>>(bytes);
if constexpr (FailFast) {
return rfl::msgpack::read<Data<Record>, rfl::FailFast>(bytes);
} else {
return rfl::msgpack::read<Data<Record>>(bytes);
}
});
}
BENCHMARK_TEMPLATE(BM_parser_inlining_msgpack, Record2)
Expand All @@ -16,4 +21,10 @@ BENCHMARK_TEMPLATE(BM_parser_inlining_msgpack, Record2)
BENCHMARK_TEMPLATE(BM_parser_inlining_msgpack, Record16)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_msgpack, Record2, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_msgpack, Record16, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
} // namespace parser_inlining
30 changes: 30 additions & 0 deletions benchmarks/all/parser_inlining_yas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <rfl/FailFast.hpp>
#include <rfl/yas.hpp>

#include "parser_inlining.hpp"
namespace parser_inlining {
template <class Record, bool FailFast = false>
void BM_parser_inlining_yas(benchmark::State& state) {
run<Record>(
state, [&](const auto& input) { return rfl::yas::write(input); },
[&](const auto& bytes) {
if constexpr (FailFast) {
return rfl::yas::read<Data<Record>, rfl::FailFast>(bytes);
} else {
return rfl::yas::read<Data<Record>>(bytes);
}
});
}
BENCHMARK_TEMPLATE(BM_parser_inlining_yas, Record2)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_yas, Record16)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_yas, Record2, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
BENCHMARK_TEMPLATE(BM_parser_inlining_yas, Record16, true)
->Arg(100000)
->Unit(benchmark::kMillisecond);
} // namespace parser_inlining
19 changes: 19 additions & 0 deletions docs/content/processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,32 @@ reflect-cpp currently supports the following processors:
- `rfl::DefaultIfMissing`
- `rfl::DurationsAsCount`
- `rfl::EnumNamesOnly`
- `rfl::FailFast`
- `rfl::NoExtraFields`
- `rfl::NoFieldNames`
- `rfl::NoOptionals`
- `rfl::UnderlyingEnums`
- `rfl::SnakeCaseToCamelCase`
- `rfl::SnakeCaseToPascalCase`

### `rfl::FailFast`

By default, reflect-cpp collects errors from multiple object fields.
`rfl::FailFast` reports the first field error and skips decoding the remaining
fields of that object. It also applies to nested structs and named tuples,
including structs stored in containers, across serialization formats.

```cpp
const auto result = rfl::yas::read<MyStruct, rfl::FailFast>(bytes);
```

This reduces error bookkeeping and can improve successful read performance.
It does not change the serialized representation. Existing calls without the
processor retain aggregated errors. Readers may still traverse input after a
field failure, and document parsing or structural errors can take precedence.
Variant matching and other parsing outside object fields keep their existing
behavior.

### `rfl::AddStructName`

It is also possible to add the struct name as an additional field, like this:
Expand Down
1 change: 1 addition & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "rfl/DurationsAsCount.hpp"
#include "rfl/EnumNamesOnly.hpp"
#include "rfl/ExtraFields.hpp"
#include "rfl/FailFast.hpp"
#include "rfl/Field.hpp"
#include "rfl/Flatten.hpp"
#include "rfl/Generic.hpp"
Expand Down
20 changes: 20 additions & 0 deletions include/rfl/FailFast.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef RFL_FAILFAST_HPP_
#define RFL_FAILFAST_HPP_

#include <utility>

namespace rfl {

/// Report only the first object field error and skip decoding later fields.
/// For example: rfl::json::read<MyStruct, rfl::FailFast>(input).
/// Readers may still traverse the input after a field error.
struct FailFast {
template <class StructType>
static auto process(auto&& _named_tuple) {
return std::forward<decltype(_named_tuple)>(_named_tuple);
}
};

} // namespace rfl

#endif
26 changes: 26 additions & 0 deletions include/rfl/internal/fail_fast_v.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef RFL_INTERNAL_FAILFAST_HPP_
#define RFL_INTERNAL_FAILFAST_HPP_

#include <type_traits>

#include "../FailFast.hpp"
#include "../Processors.hpp"

namespace rfl::internal {

template <class T>
struct fail_fast : std::false_type {};

template <>
struct fail_fast<FailFast> : std::true_type {};

template <class... Ps>
struct fail_fast<Processors<Ps...>>
: std::bool_constant<(fail_fast<Ps>::value || ...)> {};

template <class T>
inline constexpr bool fail_fast_v = fail_fast<std::remove_cvref_t<T>>::value;

} // namespace rfl::internal

#endif
48 changes: 48 additions & 0 deletions include/rfl/parsing/FieldErrors.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef RFL_PARSING_FIELDERRORS_HPP_
#define RFL_PARSING_FIELDERRORS_HPP_

#include <cstddef>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "../Result.hpp"
#include "../internal/fail_fast_v.hpp"

namespace rfl::parsing {

class FirstFieldError {
public:
bool should_stop() const { return error_.has_value(); }

void reserve(std::size_t) {}

void emplace_back(std::string message) {
if (!error_) error_.emplace(std::move(message));
}

std::optional<Error> error() && { return std::move(error_); }

private:
std::optional<Error> error_;
};

template <class ProcessorsType>
using FieldErrors =
std::conditional_t<internal::fail_fast_v<ProcessorsType>, FirstFieldError,
std::vector<std::string>>;

template <class ProcessorsType>
bool should_stop_on_field_error(const FieldErrors<ProcessorsType>& errors) {
if constexpr (internal::fail_fast_v<ProcessorsType>) {
return errors.should_stop();
} else {
return false;
}
}

} // namespace rfl::parsing

#endif
43 changes: 28 additions & 15 deletions include/rfl/parsing/NamedTupleParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "../num_fields.hpp"
#include "../to_view.hpp"
#include "AreReaderAndWriter.hpp"
#include "FieldErrors.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "ViewReader.hpp"
Expand Down Expand Up @@ -325,7 +326,8 @@ struct NamedTupleParser {
static RFL_ALWAYS_INLINE void handle_one_missing_field(
const std::array<bool, size_>& _found, const NamedTupleType& _view,
std::array<bool, size_>* _set,
std::vector<std::string>* _errors) noexcept {
FieldErrors<ProcessorsType>* _errors) noexcept {
if (should_stop_on_field_error<ProcessorsType>(*_errors)) return;
using FieldType = internal::nth_element_t<_i, FieldTypes...>;
using ValueType = std::remove_reference_t<
std::remove_pointer_t<typename FieldType::Type>>;
Expand Down Expand Up @@ -360,7 +362,7 @@ struct NamedTupleParser {
template <int... _is>
static RFL_ALWAYS_INLINE void handle_missing_fields(
const std::array<bool, size_>& _found, const NamedTupleType& _view,
std::array<bool, size_>* _set, std::vector<std::string>* _errors,
std::array<bool, size_>* _set, FieldErrors<ProcessorsType>* _errors,
std::integer_sequence<int, _is...>) noexcept {
(handle_one_missing_field<_is>(_found, _view, _set, _errors), ...);
}
Expand All @@ -383,7 +385,7 @@ struct NamedTupleParser {
found.fill(false);
auto set = std::array<bool, NamedTupleType::size()>();
set.fill(false);
std::vector<std::string> errors;
FieldErrors<ProcessorsType> errors;
const auto reader = ViewReaderType(&_r, _view, &found, &set, &errors);
if constexpr (_no_field_names) {
const auto err = _r.read_array(reader, _obj_or_arr);
Expand All @@ -396,18 +398,24 @@ struct NamedTupleParser {
return std::make_pair(set, err);
}
}
handle_missing_fields(found, *_view, &set, &errors,
std::make_integer_sequence<int, size_>());
if (errors.size() != 0) {
return std::make_pair(set, to_single_error_message(errors));
if (!should_stop_on_field_error<ProcessorsType>(errors)) {
handle_missing_fields(found, *_view, &set, &errors,
std::make_integer_sequence<int, size_>());
}
if constexpr (internal::fail_fast_v<ProcessorsType>) {
return std::make_pair(set, std::move(errors).error());
} else {
if (errors.size() != 0) {
return std::make_pair(set, to_single_error_message(errors));
}
return std::make_pair(set, std::optional<Error>());
}
return std::make_pair(set, std::optional<Error>());
}

static std::optional<Error> read_object_or_array_with_default(
const R& _r, const InputObjectOrArrayType& _obj_or_arr,
NamedTupleType* _view) noexcept {
std::vector<std::string> errors;
FieldErrors<ProcessorsType> errors;
const auto reader = ViewReaderWithDefaultType(&_r, _view, &errors);
if constexpr (_no_field_names) {
const auto err = _r.read_array(reader, _obj_or_arr);
Expand All @@ -422,14 +430,19 @@ struct NamedTupleParser {
}
if constexpr (internal::has_default_val_v<NamedTupleType> &&
!internal::default_if_missing_v<ProcessorsType>) {
handle_missing_fields(reader.found(), *_view, nullptr, &errors,
std::make_integer_sequence<int, size_>());
if (!should_stop_on_field_error<ProcessorsType>(errors)) {
handle_missing_fields(reader.found(), *_view, nullptr, &errors,
std::make_integer_sequence<int, size_>());
}
}
if (errors.size() != 0) {
return to_single_error_message(errors);
if constexpr (internal::fail_fast_v<ProcessorsType>) {
return std::move(errors).error();
} else {
if (errors.size() != 0) {
return to_single_error_message(errors);
}
return std::nullopt;
}

return std::nullopt;
}
};

Expand Down
Loading
Loading