Commit 4bbccd70 authored by Dominik Charousset's avatar Dominik Charousset

Re-implement inspector_access

parent 4eae4034
...@@ -198,6 +198,7 @@ endif() ...@@ -198,6 +198,7 @@ endif()
add_executable(caf-core-test add_executable(caf-core-test
test/core-test.cpp test/core-test.cpp
test/nasty.cpp
$<TARGET_OBJECTS:libcaf_core_obj>) $<TARGET_OBJECTS:libcaf_core_obj>)
caf_core_set_default_properties(caf-core-test) caf_core_set_default_properties(caf-core-test)
......
...@@ -30,10 +30,10 @@ namespace caf { ...@@ -30,10 +30,10 @@ namespace caf {
// -- 1 param templates -------------------------------------------------------- // -- 1 param templates --------------------------------------------------------
template <class> class [[nodiscard]] error_code;
template <class> class behavior_type_of; template <class> class behavior_type_of;
template <class> class dictionary; template <class> class dictionary;
template <class> class downstream; template <class> class downstream;
template <class> class [[nodiscard]] error_code;
template <class> class expected; template <class> class expected;
template <class> class intrusive_cow_ptr; template <class> class intrusive_cow_ptr;
template <class> class intrusive_ptr; template <class> class intrusive_ptr;
...@@ -45,6 +45,7 @@ template <class> class stream_sink; ...@@ -45,6 +45,7 @@ template <class> class stream_sink;
template <class> class stream_source; template <class> class stream_source;
template <class> class weak_intrusive_ptr; template <class> class weak_intrusive_ptr;
template <class> struct inspector_access;
template <class> struct timeout_definition; template <class> struct timeout_definition;
template <class> struct type_id; template <class> struct type_id;
......
...@@ -18,28 +18,202 @@ ...@@ -18,28 +18,202 @@
#pragma once #pragma once
#include <tuple>
#include "caf/detail/inspect.hpp" #include "caf/detail/inspect.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/optional.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/sum_type_access.hpp"
#include "caf/variant.hpp"
namespace caf { namespace caf {
/// Customization point for adding support for a custom type. The default // -- utility function objects and functions for load and save inspectors ------
/// implementation requires an `inspect` overload for `T` that is available via
/// ADL. namespace detail {
/// Utility class for predicates that always return `true`.
struct always_true_t {
template <class... Ts>
[[nodiscard]] constexpr bool operator()(Ts&&...) const noexcept {
return true;
}
};
/// Predicate that always returns `true`.
constexpr auto always_true = always_true_t{};
/// Returns a mutable reference to `x`.
template <class T>
T& as_mutable_ref(T& x) {
return x;
}
/// Returns a mutable reference to `x`.
template <class T> template <class T>
struct inspector_access { T& as_mutable_ref(const T& x) {
return const_cast<T&>(x);
}
/// Checks whether the inspector has a `value` overload for `T`.
template <class Inspector, class T>
class is_trivially_inspectable {
private:
template <class U>
static auto sfinae(Inspector& f, U& x)
-> decltype(f.value(x), std::true_type{});
static std::false_type sfinae(Inspector&, ...);
using sfinae_result
= decltype(sfinae(std::declval<Inspector&>(), std::declval<T&>()));
public:
static constexpr bool value = sfinae_result::value;
};
} // namespace detail
/// Calls `f.value(x)` if this is a valid expression,
/// `inspector_access<T>::apply_value(f, x)` otherwise.
template <class Inspector, class T>
bool inspect_value(Inspector& f, T& x) {
if constexpr (detail::is_trivially_inspectable<Inspector, T>::value) {
return f.value(x);
} else {
return inspector_access<T>::apply_value(f, x);
}
}
/// @copydoc inspect_value
template <class Inspector, class T>
bool inspect_value(Inspector& f, const T& x) {
if constexpr (detail::is_trivially_inspectable<Inspector, T>::value) {
return f.value(const_cast<T&>(x));
} else {
return inspector_access<T>::apply_value(f, const_cast<T&>(x));
}
}
template <class Inspector, class Get, class Set>
bool inspect_value(Inspector& f, Get&& get, Set&& set) {
using value_type = std::decay_t<decltype(get())>;
if constexpr (Inspector::is_loading) {
auto tmp = value_type{};
if (inspect_value(f, tmp))
return set(std::move(tmp));
return false;
} else {
auto&& x = get();
return inspect_value(f, detail::as_mutable_ref(x));
}
}
/// Provides default implementations for `save_field`, `load_field`, and
/// `apply_value`.
template <class T>
struct inspector_access_base {
/// Saves a mandatory field to `f`.
template <class Inspector> template <class Inspector>
static bool apply(Inspector& f, T& x) { static bool save_field(Inspector& f, string_view field_name, T& x) {
return f.begin_field(field_name) && inspect_value(f, x) && f.end_field();
}
/// Saves an optional field to `f`.
template <class Inspector, class IsPresent, class Get>
static bool save_field(Inspector& f, string_view field_name,
IsPresent& is_present, Get& get) {
if (is_present()) {
auto&& x = get();
return f.begin_field(field_name, true) //
&& inspect_value(f, x) //
&& f.end_field();
}
return f.begin_field(field_name, false) && f.end_field();
}
/// Loads a mandatory field from `f`.
template <class Inspector, class IsValid, class SyncValue>
static bool load_field(Inspector& f, string_view field_name, T& x,
IsValid& is_valid, SyncValue& sync_value) {
if (f.begin_field(field_name) && inspect_value(f, x)) {
if (!is_valid(x))
return f.load_field_failed(field_name,
sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
return f.end_field();
}
return false;
}
/// Loads an optional field from `f`, calling `set_fallback` if the source
/// contains no value for `x`.
template <class Inspector, class IsValid, class SyncValue, class SetFallback>
static bool load_field(Inspector& f, string_view field_name, T& x,
IsValid& is_valid, SyncValue& sync_value,
SetFallback& set_fallback) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return false;
if (is_present) {
if (!inspect_value(f, x))
return false;
if (!is_valid(x))
return f.load_field_failed(field_name,
sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
return f.end_field();
}
set_fallback();
return f.end_field();
}
};
/// Default implementation for @ref inspector_access.
template <class T>
struct default_inspector_access : inspector_access_base<T> {
/// Applies `x` as an object to `f`.
template <class Inspector>
[[nodiscard]] static bool apply_object(Inspector& f, T& x) {
using detail::inspect; using detail::inspect;
using result_type = decltype(inspect(f, x)); // User-provided `inspect` overloads come first.
if constexpr (std::is_same<result_type, bool>::value) if constexpr (detail::is_inspectable<Inspector, T>::value) {
return inspect(f, x); using result_type = decltype(inspect(f, x));
else if constexpr (std::is_same<result_type, bool>::value)
return apply_deprecated(f, x); return inspect(f, x);
else
return apply_deprecated(f, x);
} else if constexpr (Inspector::is_loading) {
return load_object(f, x);
} else {
return save_object(f, x);
}
}
/// Applies `x` as a single value to `f`.
template <class Inspector>
[[nodiscard]] static bool apply_value(Inspector& f, T& x) {
return apply_object(f, x);
}
template <class Inspector>
[[nodiscard]] static bool load_object(Inspector& f, T& x) {
if constexpr (std::is_arithmetic<T>::value) {
return f.object(x).fields(f.field("value", x));
}
} }
template <class Inspector> template <class Inspector>
[[deprecated("inspect() overloads should return bool")]] static bool [[deprecated("inspect() overloads should return bool")]] //
[[nodiscard]] static bool
apply_deprecated(Inspector& f, T& x) { apply_deprecated(Inspector& f, T& x) {
if (auto err = inspect(f, x)) { if (auto err = inspect(f, x)) {
f.set_error(std::move(err)); f.set_error(std::move(err));
...@@ -49,23 +223,299 @@ struct inspector_access { ...@@ -49,23 +223,299 @@ struct inspector_access {
} }
}; };
/// Customization point to influence how inspectors treat fields of type `T`. /// Customization point for adding support for a custom type. The default
/// implementation requires an `inspect` overload for `T` that is available via
/// ADL.
template <class T> template <class T>
struct inspector_access_traits { struct inspector_access : default_inspector_access<T> {};
static constexpr bool is_optional = false;
static constexpr bool is_sum_type = false; template <class... Ts>
struct inspector_access<std::tuple<Ts...>> {
// -- boilerplate ------------------------------------------------------------
using value_type = std::tuple<Ts...>;
using tuple_indexes = std::make_index_sequence<sizeof...(Ts)>;
template <class Inspector>
static bool apply_object(Inspector& f, value_type& x) {
return f.object(x).fields(f.field("value", x));
}
template <class Inspector>
static bool apply_value(Inspector& f, value_type& x) {
return apply_object(f, x);
}
// -- saving -----------------------------------------------------------------
template <class Inspector, class IsPresent, class Get, size_t... Is>
static bool save_field(Inspector& f, string_view field_name,
IsPresent&& is_present, Get&& get,
std::index_sequence<Is...>) {
if (is_present()) {
auto&& x = get();
return f.begin_field(field_name, true) //
&& f.begin_tuple(sizeof...(Ts)) //
&& (inspect_value(f, std::get<Is>(x)) && ...) //
&& f.end_tuple() //
&& f.end_field();
} else {
return f.begin_field(field_name, false) && f.end_field();
}
}
template <class Inspector, class IsPresent, class Get>
static bool save_field(Inspector& f, string_view field_name,
IsPresent&& is_present, Get&& get) {
return save_field(f, field_name, std::forward<IsPresent>(is_present),
std::forward<Get>(get), tuple_indexes{});
}
template <class Inspector, size_t... Is>
static bool save_field(Inspector& f, string_view field_name, value_type& x,
std::index_sequence<Is...>) {
return f.begin_field(field_name) //
&& f.begin_tuple(sizeof...(Ts)) //
&& (inspect_value(f, std::get<Is>(x)) && ...) //
&& f.end_tuple() //
&& f.end_field();
}
template <class Inspector>
static bool save_field(Inspector& f, string_view field_name, value_type& x) {
return save_field(f, field_name, x, tuple_indexes{});
}
// -- loading ----------------------------------------------------------------
template <class Inspector, class IsValid, class SyncValue, size_t... Is>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value,
std::index_sequence<Is...>) {
if (f.begin_field(field_name) //
&& f.begin_tuple(sizeof...(Ts)) //
&& (inspect_value(f, std::get<Is>(x)) && ...)) {
if (!is_valid(x))
return f.load_field_failed(field_name,
sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
return f.end_tuple() && f.end_field();
}
return false;
}
template <class Inspector, class IsValid, class SyncValue>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value) {
return load_field(f, field_name, x, is_valid, sync_value, tuple_indexes{});
}
template <class Inspector, class IsValid, class SyncValue, class SetFallback,
size_t... Is>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value,
SetFallback& set_fallback,
std::index_sequence<Is...>) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return false;
if (is_present) {
if (!f.begin_tuple(sizeof...(Ts)) //
|| !(inspect_value(f, std::get<Is>(x)) && ...))
return false;
if (!is_valid(x))
return f.load_field_failed(field_name,
sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
return f.end_tuple() && f.end_field();
}
set_fallback();
return f.end_field();
}
template <class Inspector, class IsValid, class SyncValue, class SetFallback>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value,
SetFallback& set_fallback) {
return load_field(f, field_name, x, is_valid, sync_value, set_fallback,
tuple_indexes{});
}
}; };
template <class T> template <class T>
struct inspector_access_traits<optional<T>> { struct inspector_access<optional<T>> {
static constexpr bool is_optional = true; using value_type = optional<T>;
static constexpr bool is_sum_type = false;
template <class Inspector>
[[nodiscard]] static bool apply_object(Inspector& f, value_type& x) {
return f.object(x).fields(f.field("value", x));
}
template <class Inspector>
[[nodiscard]] static bool apply_value(Inspector& f, value_type&) {
f.set_error(
make_error(sec::runtime_error, "apply_value called on an optional"));
return false;
}
template <class Inspector>
static bool save_field(Inspector& f, string_view field_name, value_type& x) {
auto is_present = [&x] { return static_cast<bool>(x); };
auto get = [&x] { return *x; };
return inspector_access<T>::save_field(f, field_name, is_present, get);
}
template <class Inspector, class IsPresent, class Get>
static bool save_field(Inspector& f, string_view field_name,
IsPresent& is_present, Get& get) {
if (is_present()) {
auto&& x = get();
return save_field(f, field_name, detail::as_mutable_ref(x));
}
return f.begin_field(field_name, false) && f.end_field();
}
template <class Inspector, class IsValid, class SyncValue>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value) {
auto tmp = T{};
auto set_x = [&] {
x = std::move(tmp);
return sync_value();
};
auto reset = [&x] { x = value_type{}; };
return inspector_access<T>::load_field(f, field_name, tmp, is_valid, set_x,
reset);
}
template <class Inspector, class IsValid, class SyncValue, class SetFallback>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value,
SetFallback& set_fallback) {
auto tmp = T{};
auto set_x = [&] {
x = std::move(tmp);
return sync_value();
};
return inspector_access<T>::load_field(f, field_name, tmp, is_valid, set_x,
set_fallback);
}
}; };
template <class... Ts> template <class... Ts>
struct inspector_access_traits<variant<Ts...>> { struct inspector_access<variant<Ts...>> {
static constexpr bool is_optional = false; using value_type = variant<Ts...>;
static constexpr bool is_sum_type = true;
static constexpr type_id_t allowed_types[] = {type_id_v<Ts>...};
template <class Inspector>
[[nodiscard]] static bool apply_object(Inspector& f, value_type& x) {
return f.object(x).fields(f.field("value", x));
}
template <class Inspector>
[[nodiscard]] static bool apply_value(Inspector& f, value_type&) {
f.set_error(
make_error(sec::runtime_error, "apply_value called on a variant"));
return false;
}
template <class Inspector>
static bool save_field(Inspector& f, string_view field_name, value_type& x) {
auto g = [&f](auto& y) { return inspect_value(f, y); };
return f.begin_field(field_name, make_span(allowed_types), x.index()) //
&& visit(g, x) //
&& f.end_field();
}
template <class Inspector, class IsPresent, class Get>
static bool save_field(Inspector& f, string_view field_name,
IsPresent& is_present, Get& get) {
if (is_present()) {
auto&& x = get();
auto g = [&f](auto& y) { return inspect_value(f, y); };
return f.begin_field(field_name, true, make_span(allowed_types),
x.index()) //
&& visit(g, x) //
&& f.end_field();
}
return f.begin_field(field_name, false, make_span(allowed_types), 0) //
&& f.end_field();
}
template <class Inspector>
static bool load_variant_value(Inspector& f, string_view field_name,
value_type&, type_id_t, detail::type_list<>) {
return f.load_field_failed(field_name, sec::invalid_field_type);
}
template <class Inspector, class U, class... Us>
static bool load_variant_value(Inspector& f, string_view field_name,
value_type& x, type_id_t runtime_type,
detail::type_list<U, Us...>) {
if (type_id_v<U> == runtime_type) {
auto tmp = U{};
if (!inspect_value(f, tmp))
return false;
x = std::move(tmp);
return true;
}
detail::type_list<Us...> token;
return load_variant_value(f, field_name, x, runtime_type, token);
}
template <class Inspector, class IsValid, class SyncValue>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value) {
size_t type_index = std::numeric_limits<size_t>::max();
if (!f.begin_field(field_name, make_span(allowed_types), type_index))
return false;
if (type_index >= std::size(allowed_types))
return f.load_field_failed(field_name, sec::invalid_field_type);
auto runtime_type = allowed_types[type_index];
detail::type_list<Ts...> types;
if (!load_variant_value(f, field_name, x, runtime_type, types))
return false;
if (!is_valid(x))
return f.load_field_failed(field_name, sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
return f.end_field();
}
template <class Inspector, class IsValid, class SyncValue, class SetFallback>
static bool load_field(Inspector& f, string_view field_name, value_type& x,
IsValid& is_valid, SyncValue& sync_value,
SetFallback& set_fallback) {
bool is_present = false;
size_t type_index = std::numeric_limits<size_t>::max();
if (!f.begin_field(field_name, is_present, make_span(allowed_types),
type_index))
return false;
if (is_present) {
if (type_index >= std::size(allowed_types))
return f.load_field_failed(field_name, sec::invalid_field_type);
auto runtime_type = allowed_types[type_index];
detail::type_list<Ts...> types;
if (!load_variant_value(f, field_name, x, runtime_type, types))
return false;
if (!is_valid(x))
return f.load_field_failed(field_name,
sec::field_invariant_check_failed);
if (!sync_value())
return f.load_field_failed(field_name,
sec::field_value_synchronization_failed);
} else {
set_fallback();
}
return f.end_field();
}
}; };
} // namespace caf } // namespace caf
...@@ -21,9 +21,7 @@ ...@@ -21,9 +21,7 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "caf/error.hpp"
#include "caf/inspector_access.hpp" #include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
namespace caf { namespace caf {
...@@ -34,7 +32,7 @@ namespace caf { ...@@ -34,7 +32,7 @@ namespace caf {
/// for the DSL. /// for the DSL.
class load_inspector { class load_inspector {
public: public:
// -- contants --------------------------------------------------------------- // -- constants --------------------------------------------------------------
/// Convenience constant to indicate success of a processing step. /// Convenience constant to indicate success of a processing step.
static constexpr bool ok = true; static constexpr bool ok = true;
...@@ -52,77 +50,43 @@ public: ...@@ -52,77 +50,43 @@ public:
/// A load inspector overrides the state of an object. /// A load inspector overrides the state of an object.
static constexpr bool writes_state = true; static constexpr bool writes_state = true;
// -- error management ------------------------------------------------------- /// Inspecting objects, fields and values always returns a `bool`.
using result_type = bool;
template <class Inspector>
static void set_invariant_check_error(Inspector& f, string_view field_name,
string_view object_name) {
std::string msg = "invalid argument to field ";
msg.insert(msg.end(), field_name.begin(), field_name.end());
msg += " for object of type ";
msg.insert(msg.end(), object_name.begin(), object_name.end());
msg += ": invariant check failed";
f.set_error(make_error(caf::sec::invalid_argument, std::move(msg)));
}
template <class Inspector>
static void set_field_store_error(Inspector& f, string_view field_name,
string_view object_name) {
std::string msg = "invalid argument to field ";
msg.insert(msg.end(), field_name.begin(), field_name.end());
msg += " for object of type ";
msg.insert(msg.end(), object_name.begin(), object_name.end());
msg += ": setter returned false";
f.set_error(make_error(caf::sec::invalid_argument, std::move(msg)));
}
// -- DSL types for regular fields ------------------------------------------- // -- DSL types for regular fields -------------------------------------------
template <class T, class Predicate> template <class T, class U, class Predicate>
struct field_with_invariant_and_fallback_t { struct field_with_invariant_and_fallback_t {
string_view field_name; string_view field_name;
T* val; T* val;
T fallback; U fallback;
Predicate predicate; Predicate predicate;
template <class Inspector> template <class Inspector>
bool operator()(string_view object_name, Inspector& f) { bool operator()(Inspector& f) {
bool is_present = false; auto reset = [this] { *val = std::move(fallback); };
if (!f.begin_field(field_name, is_present)) return inspector_access<T>::load_field(f, field_name, *val, predicate,
return stop; detail::always_true, reset);
if (is_present) {
if (inspector_access<T>::apply(f, *val) && f.end_field()) {
if (predicate(*val))
return ok;
set_invariant_check_error(f, field_name, object_name);
}
return stop;
}
*val = std::move(fallback);
return f.end_field();
} }
}; };
template <class T> template <class T, class U>
struct field_with_fallback_t { struct field_with_fallback_t {
string_view field_name; string_view field_name;
T* val; T* val;
T fallback; U fallback;
template <class Inspector> template <class Inspector>
bool operator()(string_view, Inspector& f) { bool operator()(Inspector& f) {
bool is_present = false; auto reset = [this] { *val = std::move(fallback); };
if (!f.begin_field(field_name, is_present)) return inspector_access<T>::load_field(f, field_name, *val,
return stop; detail::always_true,
if (is_present) detail::always_true, reset);
return inspector_access<T>::apply(f, *val) && f.end_field();
*val = std::move(fallback);
return f.end_field();
} }
template <class Predicate> template <class Predicate>
auto invariant(Predicate predicate) && { auto invariant(Predicate predicate) && {
return field_with_invariant_and_fallback_t<T, Predicate>{ return field_with_invariant_and_fallback_t<T, U, Predicate>{
field_name, field_name,
val, val,
std::move(fallback), std::move(fallback),
...@@ -138,19 +102,14 @@ public: ...@@ -138,19 +102,14 @@ public:
Predicate predicate; Predicate predicate;
template <class Inspector> template <class Inspector>
bool operator()(string_view object_name, Inspector& f) { bool operator()(Inspector& f) {
if (f.begin_field(field_name) // return inspector_access<T>::load_field(f, field_name, *val, predicate,
&& inspector_access<T>::apply(f, *val) // detail::always_true);
&& f.end_field()) {
if (predicate(*val))
return ok;
set_invariant_check_error(f, field_name, object_name);
}
return stop;
} }
auto fallback(T value) && { template <class U>
return field_with_invariant_and_fallback_t<T, Predicate>{ auto fallback(U value) && {
return field_with_invariant_and_fallback_t<T, U, Predicate>{
field_name, field_name,
val, val,
std::move(value), std::move(value),
...@@ -165,31 +124,15 @@ public: ...@@ -165,31 +124,15 @@ public:
T* val; T* val;
template <class Inspector> template <class Inspector>
bool operator()(string_view, Inspector& f) { bool operator()(Inspector& f) {
if constexpr (inspector_access_traits<T>::is_optional) { return inspector_access<T>::load_field(f, field_name, *val,
bool is_present = false; detail::always_true,
if (!f.begin_field(field_name, is_present)) detail::always_true);
return stop;
using value_type = std::decay_t<decltype(**val)>;
if (is_present) {
auto tmp = value_type{};
if (!inspector_access<value_type>::apply(f, tmp))
return stop;
*val = std::move(tmp);
return f.end_field();
} else {
*val = T{};
return f.end_field();
}
} else {
return f.begin_field(field_name) //
&& inspector_access<T>::apply(f, *val) //
&& f.end_field();
}
} }
auto fallback(T value) && { template <class U>
return field_with_fallback_t<T>{field_name, val, std::move(value)}; auto fallback(U value) && {
return field_with_fallback_t<T, U>{field_name, val, std::move(value)};
} }
template <class Predicate> template <class Predicate>
...@@ -204,66 +147,46 @@ public: ...@@ -204,66 +147,46 @@ public:
// -- DSL types for virtual fields (getter and setter access) ---------------- // -- DSL types for virtual fields (getter and setter access) ----------------
template <class T, class Set, class Predicate> template <class T, class Set, class U, class Predicate>
struct virt_field_with_invariant_and_fallback_t { struct virt_field_with_invariant_and_fallback_t {
string_view field_name; string_view field_name;
Set set; Set set;
T fallback; U fallback;
Predicate predicate; Predicate predicate;
template <class Inspector> template <class Inspector>
bool operator()(string_view object_name, Inspector& f) { bool operator()(Inspector& f) {
bool is_present = false; auto tmp = T{};
if (!f.begin_field(field_name, is_present)) auto sync = [this, &tmp] { return set(std::move(tmp)); };
return stop; auto reset = [this] { set(std::move(fallback)); };
if (is_present) { return inspector_access<T>::load_field(f, field_name, tmp, predicate,
auto tmp = T{}; sync, reset);
if (!inspector_access<T>::apply(f, tmp))
return stop;
if (!predicate(tmp)) {
set_invariant_check_error(f, field_name, object_name);
return stop;
}
if (!set(std::move(tmp))) {
set_field_store_error(f, field_name, object_name);
return stop;
}
return f.end_field();
}
if (!set(std::move(fallback))) {
set_field_store_error(f, field_name, object_name);
return stop;
}
return f.end_field();
} }
}; };
template <class T, class Set> template <class T, class Set, class U>
struct virt_field_with_fallback_t { struct virt_field_with_fallback_t {
string_view field_name; string_view field_name;
Set set; Set set;
T fallback; U fallback;
template <class Inspector> template <class Inspector>
bool operator()(string_view object_name, Inspector& f) { bool operator()(Inspector& f) {
bool is_present = false; auto tmp = T{};
if (!f.begin_field(field_name, is_present)) auto sync = [this, &tmp] { return set(std::move(tmp)); };
return stop; auto reset = [this] { set(std::move(fallback)); };
if (is_present) { return inspector_access<T>::load_field(f, field_name, tmp,
auto tmp = T{}; detail::always_true, sync, reset);
if (!inspector_access<T>::apply(f, tmp)) }
return stop;
if (!set(std::move(tmp))) { template <class Predicate>
set_field_store_error(f, field_name, object_name); auto invariant(Predicate predicate) && {
return stop; return virt_field_with_invariant_and_fallback_t<T, Set, U, Predicate>{
} field_name,
return f.end_field(); std::move(set),
} std::move(fallback),
if (!set(std::move(fallback))) { std::move(predicate),
set_field_store_error(f, field_name, object_name); };
return stop;
}
return f.end_field();
} }
}; };
...@@ -274,25 +197,16 @@ public: ...@@ -274,25 +197,16 @@ public:
Predicate predicate; Predicate predicate;
template <class Inspector> template <class Inspector>
bool operator()(string_view object_name, Inspector& f) { bool operator()(Inspector& f) {
if (!f.begin_field(field_name))
return stop;
auto tmp = T{}; auto tmp = T{};
if (!inspector_access<T>::apply(f, tmp)) auto sync = [this, &tmp] { return set(std::move(tmp)); };
return stop; return inspector_access<T>::load_field(f, field_name, tmp, predicate,
if (!predicate(tmp)) { sync);
set_invariant_check_error(f, field_name, object_name);
return stop;
}
if (!set(std::move(tmp))) {
set_field_store_error(f, field_name, object_name);
return stop;
}
return f.end_field();
} }
auto fallback(T value) && { template <class U>
return virt_field_with_invariant_and_fallback_t<T, Set, Predicate>{ auto fallback(U value) && {
return virt_field_with_invariant_and_fallback_t<T, Set, U, Predicate>{
field_name, field_name,
std::move(set), std::move(set),
std::move(value), std::move(value),
...@@ -307,24 +221,30 @@ public: ...@@ -307,24 +221,30 @@ public:
Set set; Set set;
template <class Inspector> template <class Inspector>
bool operator()(string_view, Inspector& f) { bool operator()(Inspector& f) {
if (!f.begin_field(field_name))
return stop;
auto tmp = T{}; auto tmp = T{};
if (!inspector_access<T>::apply(f, tmp)) auto sync = [this, &tmp] { return set(std::move(tmp)); };
return stop; return inspector_access<T>::load_field(f, field_name, tmp,
if (!set(std::move(tmp))) { detail::always_true, sync);
}
return f.end_field();
} }
auto fallback(T value) && { template <class U>
return virt_field_with_fallback_t<T, Set>{ auto fallback(U value) && {
return virt_field_with_fallback_t<T, Set, U>{
field_name, field_name,
std::move(set), std::move(set),
std::move(value), std::move(value),
}; };
} }
template <class Predicate>
auto invariant(Predicate predicate) && {
return virt_field_with_invariant_t<T, Set, Predicate>{
field_name,
std::move(set),
std::move(predicate),
};
}
}; };
template <class Inspector> template <class Inspector>
...@@ -334,8 +254,7 @@ public: ...@@ -334,8 +254,7 @@ public:
template <class... Fields> template <class... Fields>
bool fields(Fields&&... fs) { bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(object_name, *f) && ...) return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
&& f->end_object();
} }
auto pretty_name(string_view name) && { auto pretty_name(string_view name) && {
...@@ -353,9 +272,6 @@ public: ...@@ -353,9 +272,6 @@ public:
template <class Get, class Set> template <class Get, class Set>
static auto field(string_view name, Get get, Set set) { static auto field(string_view name, Get get, Set set) {
using field_type = std::decay_t<decltype(get())>; using field_type = std::decay_t<decltype(get())>;
using set_result = decltype(set(std::declval<field_type>()));
static_assert(std::is_same<set_result, bool>::value,
"setters of fields must return bool");
return virt_field_t<field_type, Set>{name, set}; return virt_field_t<field_type, Set>{name, set};
} }
}; };
......
...@@ -18,9 +18,7 @@ ...@@ -18,9 +18,7 @@
#pragma once #pragma once
#include "caf/error.hpp"
#include "caf/inspector_access.hpp" #include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
namespace caf { namespace caf {
...@@ -31,7 +29,7 @@ namespace caf { ...@@ -31,7 +29,7 @@ namespace caf {
/// for the DSL. /// for the DSL.
class save_inspector { class save_inspector {
public: public:
// -- contants --------------------------------------------------------------- // -- constants --------------------------------------------------------------
/// Convenience constant to indicate success of a processing step. /// Convenience constant to indicate success of a processing step.
static constexpr bool ok = true; static constexpr bool ok = true;
...@@ -49,78 +47,95 @@ public: ...@@ -49,78 +47,95 @@ public:
/// A load inspector never modifies the state of an object. /// A load inspector never modifies the state of an object.
static constexpr bool writes_state = false; static constexpr bool writes_state = false;
/// Inspecting objects, fields and values always returns a `bool`.
using result_type = bool;
// -- DSL types for regular fields ------------------------------------------- // -- DSL types for regular fields -------------------------------------------
template <class T, class U>
struct field_with_fallback_t {
string_view field_name;
T* val;
U fallback;
template <class Inspector>
bool operator()(Inspector& f) {
auto is_present = [this] { return *val != fallback; };
auto get = [this] { return *val; };
return inspector_access<T>::save_field(f, field_name, is_present, get);
}
template <class Predicate>
field_with_fallback_t&& invariant(Predicate&&) && {
return std::move(*this);
}
};
template <class T> template <class T>
struct field_t { struct field_t {
string_view field_name; string_view field_name;
T* val; T* val;
template <class Inspector> template <class Inspector>
bool operator()(string_view, Inspector& f) { bool operator()(Inspector& f) {
if constexpr (inspector_access_traits<T>::is_optional) { return inspector_access<T>::save_field(f, field_name, *val);
auto& ref = *val;
using value_type = std::decay_t<decltype(*ref)>;
if (ref) {
return f.begin_field(field_name, true) //
&& inspector_access<value_type>::apply(f, *ref) //
&& f.end_field();
} else {
return f.begin_field(field_name, false) && f.end_field();
}
} else {
return f.begin_field(field_name) //
&& inspector_access<T>::apply(f, *val) //
&& f.end_field();
}
} }
template <class Unused> template <class U>
field_t& fallback(Unused&&) { auto fallback(U value) && {
return *this; return field_with_fallback_t<T, U>{field_name, val, std::move(value)};
} }
template <class Predicate> template <class Predicate>
field_t invariant(Predicate&&) { field_t&& invariant(Predicate&&) && {
return *this; return std::move(*this);
} }
}; };
// -- DSL types for virtual fields (getter and setter access) ---------------- // -- DSL types for virtual fields (getter and setter access) ----------------
template <class T, class Get, class U>
struct virt_field_with_fallback_t {
string_view field_name;
Get get;
U fallback;
template <class Inspector>
bool operator()(Inspector& f) {
auto is_present = [this] { return get() != fallback; };
return inspector_access<T>::save_field(f, field_name, is_present, get);
}
template <class Predicate>
virt_field_with_fallback_t&& invariant(Predicate&&) && {
return std::move(*this);
}
};
template <class T, class Get> template <class T, class Get>
struct virt_field_t { struct virt_field_t {
string_view field_name; string_view field_name;
Get get; Get get;
template <class Inspector> template <class Inspector>
bool operator()(string_view, Inspector& f) { bool operator()(Inspector& f) {
if (!f.begin_field(field_name)) auto&& x = get();
return stop; return inspector_access<T>::save_field(f, field_name,
auto&& value = get(); detail::as_mutable_ref(x));
using value_type = std::remove_reference_t<decltype(value)>;
if constexpr (std::is_const<value_type>::value) {
// Force a mutable reference, because the inspect API requires it. This
// const_cast is always safe, because we never actually modify the
// object.
using mutable_ref = std::remove_const_t<value_type>&;
if (!inspector_access<T>::apply(f, const_cast<mutable_ref>(value)))
return stop;
} else {
if (!inspector_access<T>::apply(f, value))
return stop;
}
return f.end_field();
} }
template <class Unused> template <class U>
virt_field_t& fallback(Unused&&) { auto fallback(U value) && {
return *this; return virt_field_with_fallback_t<T, Get, U>{
field_name,
std::move(get),
std::move(value),
};
} }
template <class Predicate> template <class Predicate>
virt_field_t invariant(Predicate&&) { virt_field_t&& invariant(Predicate&&) && {
return *this; return std::move(*this);
} }
}; };
...@@ -131,8 +146,7 @@ public: ...@@ -131,8 +146,7 @@ public:
template <class... Fields> template <class... Fields>
bool fields(Fields&&... fs) { bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(object_name, *f) && ...) return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
&& f->end_object();
} }
auto pretty_name(string_view name) && { auto pretty_name(string_view name) && {
......
...@@ -136,7 +136,7 @@ enum class sec : uint8_t { ...@@ -136,7 +136,7 @@ enum class sec : uint8_t {
malformed_basp_message, malformed_basp_message,
/// The middleman closed a connection because it failed to serialize or /// The middleman closed a connection because it failed to serialize or
/// deserialize a payload. /// deserialize a payload.
serializing_basp_payload_failed, serializing_basp_payload_failed = 50,
/// The middleman closed a connection to itself or an already connected node. /// The middleman closed a connection to itself or an already connected node.
redundant_connection, redundant_connection,
/// Resolving a path on a remote node failed. /// Resolving a path on a remote node failed.
...@@ -145,6 +145,13 @@ enum class sec : uint8_t { ...@@ -145,6 +145,13 @@ enum class sec : uint8_t {
no_tracing_context, no_tracing_context,
/// No request produced a valid result. /// No request produced a valid result.
all_requests_failed, all_requests_failed,
/// Deserialization failed, because an invariant got violated after reading
/// the content of a field.
field_invariant_check_failed = 55,
/// Deserialization failed, because a setter rejected the input.
field_value_synchronization_failed,
/// Deserialization failed, because the source announced an invalid type.
invalid_field_type,
}; };
/// @relates sec /// @relates sec
......
...@@ -37,6 +37,10 @@ namespace caf { ...@@ -37,6 +37,10 @@ namespace caf {
/// Internal representation of a type ID. /// Internal representation of a type ID.
using type_id_t = uint16_t; using type_id_t = uint16_t;
/// Special value equal to the greatest possible value for `type_id_t`.
/// Generally indicates that no type ID for a given type exists.
constexpr type_id_t invalid_type_id = 65535;
/// Maps the type `T` to a globally unique ID. /// Maps the type `T` to a globally unique ID.
template <class T> template <class T>
struct type_id; struct type_id;
......
...@@ -240,8 +240,8 @@ enum dummy_enum { de_foo, de_bar }; ...@@ -240,8 +240,8 @@ enum dummy_enum { de_foo, de_bar };
CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((caf::stream<int32_t>) ) ADD_TYPE_ID((caf::stream<int32_t>) )
ADD_TYPE_ID((caf::stream<std::string>) )
ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) ) ADD_TYPE_ID((caf::stream<std::pair<level, std::string>>) )
ADD_TYPE_ID((caf::stream<std::string>) )
ADD_TYPE_ID((dummy_enum)) ADD_TYPE_ID((dummy_enum))
ADD_TYPE_ID((dummy_enum_class)) ADD_TYPE_ID((dummy_enum_class))
ADD_TYPE_ID((dummy_struct)) ADD_TYPE_ID((dummy_struct))
......
...@@ -26,33 +26,11 @@ ...@@ -26,33 +26,11 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace caf { #include "caf/span.hpp"
#include "caf/type_id.hpp"
#include "caf/variant.hpp"
template <> #include "nasty.hpp"
struct inspector_access<std::string> {
template <class Inspector>
static bool apply(Inspector& f, std::string& x) {
return f.value(x);
}
};
template <>
struct inspector_access<int32_t> {
template <class Inspector>
static bool apply(Inspector& f, int32_t& x) {
return f.value(x);
}
};
template <>
struct inspector_access<double> {
template <class Inspector>
static bool apply(Inspector& f, double& x) {
return f.value(x);
}
};
} // namespace caf
using namespace caf; using namespace caf;
...@@ -153,6 +131,28 @@ bool inspect(Inspector& f, foobar& x) { ...@@ -153,6 +131,28 @@ bool inspect(Inspector& f, foobar& x) {
f.field("bar", get_bar, set_bar)); f.field("bar", get_bar, set_bar));
} }
struct dummy_message {
static inline string_view tname = "dummy_message";
variant<std::string, double> content;
};
template <class Inspector>
bool inspect(Inspector& f, dummy_message& x) {
return f.object(x).fields(f.field("content", x.content));
}
struct fallback_dummy_message {
static inline string_view tname = "fallback_dummy_message";
variant<std::string, double> content;
};
template <class Inspector>
bool inspect(Inspector& f, fallback_dummy_message& x) {
return f.object(x).fields(f.field("content", x.content).fallback(42.0));
}
struct testee : load_inspector { struct testee : load_inspector {
std::string log; std::string log;
...@@ -162,6 +162,11 @@ struct testee : load_inspector { ...@@ -162,6 +162,11 @@ struct testee : load_inspector {
err = std::move(x); err = std::move(x);
} }
bool load_field_failed(string_view, sec code) {
set_error(make_error(code));
return stop;
}
size_t indent = 0; size_t indent = 0;
void new_line() { void new_line() {
...@@ -206,6 +211,26 @@ struct testee : load_inspector { ...@@ -206,6 +211,26 @@ struct testee : load_inspector {
return ok; return ok;
} }
bool begin_field(string_view name, span<const type_id_t>,
size_t& type_index) {
new_line();
indent += 2;
log += "begin variant field ";
log.insert(log.end(), name.begin(), name.end());
type_index = 0;
return ok;
}
bool begin_field(string_view name, bool& is_present, span<const type_id_t>,
size_t&) {
new_line();
indent += 2;
log += "begin optional variant field ";
log.insert(log.end(), name.begin(), name.end());
is_present = false;
return ok;
}
bool end_field() { bool end_field() {
indent -= 2; indent -= 2;
new_line(); new_line();
...@@ -213,14 +238,36 @@ struct testee : load_inspector { ...@@ -213,14 +238,36 @@ struct testee : load_inspector {
return ok; return ok;
} }
bool begin_tuple(size_t size) {
new_line();
indent += 2;
log += "begin tuple of size ";
log += std::to_string(size);
return ok;
}
bool end_tuple() {
indent -= 2;
new_line();
log += "end tuple";
return ok;
}
template <class T> template <class T>
bool value(T& x) { std::enable_if_t<std::is_arithmetic<T>::value, bool> value(T& x) {
new_line(); new_line();
log += type_name_v<T>; log += type_name_v<T>;
log += " value"; log += " value";
x = T{}; x = T{};
return ok; return ok;
} }
bool value(std::string& x) {
new_line();
log += "std::string value";
x.clear();
return ok;
}
}; };
struct fixture { struct fixture {
...@@ -340,4 +387,134 @@ begin object foobar ...@@ -340,4 +387,134 @@ begin object foobar
end object)_"); end object)_");
} }
CAF_TEST(load inspectors support variant fields) {
dummy_message d;
d.content = 42.0;
CAF_CHECK(inspect(f, d));
// Our dummy inspector resets variants to their first type.
CAF_CHECK(holds_alternative<std::string>(d.content));
CAF_CHECK_EQUAL(f.log, R"_(
begin object dummy_message
begin variant field content
std::string value
end field
end object)_");
}
CAF_TEST(load inspectors support variant fields with fallbacks) {
fallback_dummy_message d;
d.content = std::string{"hello world"};
CAF_CHECK(inspect(f, d));
CAF_CHECK_EQUAL(d.content, 42.0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object fallback_dummy_message
begin optional variant field content
end field
end object)_");
}
CAF_TEST(load inspectors support nasty data structures) {
nasty x;
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object nasty
begin field field_01
int32_t value
end field
begin optional field field_02
end field
begin field field_03
int32_t value
end field
begin optional field field_04
end field
begin optional field field_05
end field
begin optional field field_06
end field
begin optional field field_07
end field
begin optional field field_08
end field
begin variant field field_09
std::string value
end field
begin optional variant field field_10
end field
begin variant field field_11
std::string value
end field
begin optional variant field field_12
end field
begin field field_13
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_14
end field
begin field field_15
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_16
end field
begin field field_17
int32_t value
end field
begin optional field field_18
end field
begin field field_19
int32_t value
end field
begin optional field field_20
end field
begin optional field field_21
end field
begin optional field field_22
end field
begin optional field field_23
end field
begin optional field field_24
end field
begin variant field field_25
std::string value
end field
begin optional variant field field_26
end field
begin variant field field_27
std::string value
end field
begin optional variant field field_28
end field
begin field field_29
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_30
end field
begin field field_31
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_32
end field
begin optional variant field field_33
end field
begin optional field field_34
end field
begin optional variant field field_35
end field
begin optional field field_36
end field
end object)_");
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
#include "nasty.hpp"
std::string to_string(weekday x) {
switch (x) {
default:
return "???";
case weekday::monday:
return "monday";
case weekday::tuesday:
return "tuesday";
case weekday::wednesday:
return "wednesday";
case weekday::thursday:
return "thursday";
case weekday::friday:
return "friday";
case weekday::saturday:
return "saturday";
case weekday::sunday:
return "sunday";
}
}
bool parse(std::string_view input, weekday& dest) {
if (input == "monday") {
dest = weekday::monday;
return true;
}
if (input == "tuesday") {
dest = weekday::tuesday;
return true;
}
if (input == "wednesday") {
dest = weekday::wednesday;
return true;
}
if (input == "thursday") {
dest = weekday::thursday;
return true;
}
if (input == "friday") {
dest = weekday::friday;
return true;
}
if (input == "saturday") {
dest = weekday::saturday;
return true;
}
if (input == "sunday") {
dest = weekday::sunday;
return true;
}
return false;
}
#pragma once
#include <cstdint>
#include <string>
#include <tuple>
#include "caf/inspector_access.hpp"
#include "caf/optional.hpp"
#include "caf/string_view.hpp"
#include "caf/variant.hpp"
enum class weekday {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday,
};
std::string to_string(weekday x);
bool parse(std::string_view input, weekday& dest);
namespace caf {
template <>
struct inspector_access<weekday> : inspector_access_base<weekday> {
using default_impl = default_inspector_access<weekday>;
template <class Inspector>
static bool apply_object(Inspector& f, weekday& x) {
if constexpr (Inspector::has_human_readable_format) {
auto get = [&x] { return to_string(x); };
auto set = [&x](std::string str) { return parse(str, x); };
f.object(x).fields(f.field("value", get, set));
} else {
return default_impl::apply_object(f, x);
}
}
template <class Inspector>
static bool apply_value(Inspector& f, weekday& x) {
if constexpr (Inspector::has_human_readable_format) {
auto get = [&x] { return to_string(x); };
auto set = [&x](std::string str) { return parse(str, x); };
return inspect_value(f, get, set);
} else {
return default_impl::apply_value(f, x);
}
}
};
} // namespace caf
#define ADD_GET_SET_FIELD(type, name) \
private: \
type name##_ = type{}; \
\
public: \
const auto& name() const noexcept { \
return name##_; \
} \
void name(type value) { \
name##_ = std::move(value); \
}
// A mean data type designed for maximum coverage of the inspect API.
class nasty {
public:
static inline caf::string_view tname = "nasty";
using optional_type = caf::optional<int32_t>;
using variant_type = caf::variant<std::string, int32_t>;
using tuple_type = std::tuple<std::string, int32_t>;
using optional_variant_type = caf::optional<variant_type>;
using optional_tuple_type = caf::optional<tuple_type>;
// Plain, direct access.
int32_t field_01 = 0;
// Plain, direct access, fallback (0).
int32_t field_02 = 0;
// Plain, direct access, invariant (>= 0).
int32_t field_03 = 0;
// Plain, direct access, fallback (0), invariant (>= 0).
int32_t field_04 = 0;
// Optional, direct access.
optional_type field_05;
// Optional, direct access, fallback (0).
optional_type field_06;
// Optional, direct access, invariant (>= 0).
optional_type field_07;
// Optional, direct access, fallback (0), invariant (>= 0).
optional_type field_08;
// Variant, direct access.
variant_type field_09;
// Variant, direct access, fallback (0).
variant_type field_10;
// Variant, direct access, invariant (>= 0).
variant_type field_11;
// Variant, direct access, fallback (0), invariant (>= 0).
variant_type field_12;
// Tuple, direct access.
tuple_type field_13;
// Tuple, direct access, fallback ("", 0).
tuple_type field_14;
// Tuple, direct access, invariant (>= 0).
tuple_type field_15;
// Tuple, direct access, fallback ("", 0), invariant (>= 0).
tuple_type field_16;
// Plain, get/set access.
ADD_GET_SET_FIELD(int32_t, field_17)
// Plain, get/set access, fallback (0).
ADD_GET_SET_FIELD(int32_t, field_18)
// Plain, get/set access, invariant (>= 0).
ADD_GET_SET_FIELD(int32_t, field_19)
// Plain, get/set access, fallback (0), invariant (>= 0).
ADD_GET_SET_FIELD(int32_t, field_20)
// Optional, get/set access.
ADD_GET_SET_FIELD(optional_type, field_21)
// Optional, get/set access, fallback (0).
ADD_GET_SET_FIELD(optional_type, field_22)
// Optional, get/set access, invariant (>= 0).
ADD_GET_SET_FIELD(optional_type, field_23)
// Optional, get/set access, fallback (0), invariant (>= 0).
ADD_GET_SET_FIELD(optional_type, field_24)
// Variant, get/set access.
ADD_GET_SET_FIELD(variant_type, field_25)
// Variant, get/set access, fallback (0).
ADD_GET_SET_FIELD(variant_type, field_26)
// Variant, get/set access, invariant (>= 0).
ADD_GET_SET_FIELD(variant_type, field_27)
// Variant, get/set access, fallback (0), invariant (>= 0).
ADD_GET_SET_FIELD(variant_type, field_28)
// Tuple, get/set access.
ADD_GET_SET_FIELD(tuple_type, field_29)
// Tuple, get/set access, fallback ("", 0).
ADD_GET_SET_FIELD(tuple_type, field_30)
// Tuple, get/set access, invariant (>= 0).
ADD_GET_SET_FIELD(tuple_type, field_31)
// Tuple, get/set access, fallback ("", 0), invariant (>= 0).
ADD_GET_SET_FIELD(tuple_type, field_32)
// Optional variant, direct access.
optional_variant_type field_33;
// Optional tuple, direct access.
optional_tuple_type field_34;
// Optional variant, get/set access.
ADD_GET_SET_FIELD(optional_variant_type, field_35)
// Optional variant, get/set access.
ADD_GET_SET_FIELD(optional_tuple_type, field_36)
// Plain, direct access with custom inspector_access.
weekday field_37;
// Plain, get/set access with custom inspector_access.
ADD_GET_SET_FIELD(weekday, field_38)
};
#undef ADD_GET_SET_FIELD
#define DIRECT_FIELD(num) field("field_" #num, x.field_##num)
#define GET_SET_FIELD(num) \
field( \
"field_" #num, [&x]() -> decltype(auto) { return x.field_##num(); }, \
[&x](auto&& value) { \
x.field_##num(std::forward<decltype(value)>(value)); \
return true; \
})
template <class Inspector>
bool inspect(Inspector& f, nasty& x) {
using caf::get;
using caf::get_if;
struct {
bool operator()(int32_t x) {
return x >= 0;
}
bool operator()(const nasty::optional_type& x) {
return x ? *x >= 0 : true;
}
bool operator()(const nasty::variant_type& x) {
if (auto ptr = get_if<int32_t>(&x))
return *ptr >= 0;
return true;
}
bool operator()(const nasty::tuple_type& x) {
return get<1>(x) >= 0;
}
} is_positive;
nasty::variant_type default_variant{int32_t{0}};
nasty::tuple_type default_tuple{"", 0};
return f.object(x).fields( //
f.DIRECT_FIELD(01), //
f.DIRECT_FIELD(02).fallback(0), //
f.DIRECT_FIELD(03).invariant(is_positive), //
f.DIRECT_FIELD(04).fallback(0).invariant(is_positive), //
f.DIRECT_FIELD(05), //
f.DIRECT_FIELD(06).fallback(0), //
f.DIRECT_FIELD(07).invariant(is_positive), //
f.DIRECT_FIELD(08).fallback(0).invariant(is_positive), //
f.DIRECT_FIELD(09), //
f.DIRECT_FIELD(10).fallback(default_variant), //
f.DIRECT_FIELD(11).invariant(is_positive), //
f.DIRECT_FIELD(12).fallback(default_variant).invariant(is_positive), //
f.DIRECT_FIELD(13), //
f.DIRECT_FIELD(14).fallback(default_tuple), //
f.DIRECT_FIELD(15).invariant(is_positive), //
f.DIRECT_FIELD(16).fallback(default_tuple).invariant(is_positive), //
f.GET_SET_FIELD(17), //
f.GET_SET_FIELD(18).fallback(0), //
f.GET_SET_FIELD(19).invariant(is_positive), //
f.GET_SET_FIELD(20).fallback(0).invariant(is_positive), //
f.GET_SET_FIELD(21), //
f.GET_SET_FIELD(22).fallback(0), //
f.GET_SET_FIELD(23).invariant(is_positive), //
f.GET_SET_FIELD(24).fallback(0).invariant(is_positive), //
f.GET_SET_FIELD(25), //
f.GET_SET_FIELD(26).fallback(default_variant), //
f.GET_SET_FIELD(27).invariant(is_positive), //
f.GET_SET_FIELD(28).fallback(default_variant).invariant(is_positive), //
f.GET_SET_FIELD(29), //
f.GET_SET_FIELD(30).fallback(default_tuple), //
f.GET_SET_FIELD(31).invariant(is_positive), //
f.GET_SET_FIELD(32).fallback(default_tuple).invariant(is_positive), //
f.DIRECT_FIELD(33), //
f.DIRECT_FIELD(34), //
f.GET_SET_FIELD(35), //
f.GET_SET_FIELD(36));
}
#undef DIRECT_FIELD
#undef GET_SET_FIELD
...@@ -26,33 +26,7 @@ ...@@ -26,33 +26,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace caf { #include "nasty.hpp"
template <>
struct inspector_access<std::string> {
template <class Inspector>
static bool apply(Inspector& f, std::string& x) {
return f.value(x);
}
};
template <>
struct inspector_access<int32_t> {
template <class Inspector>
static bool apply(Inspector& f, int32_t& x) {
return f.value(x);
}
};
template <>
struct inspector_access<double> {
template <class Inspector>
static bool apply(Inspector& f, double& x) {
return f.value(x);
}
};
} // namespace caf
using namespace caf; using namespace caf;
...@@ -174,6 +148,16 @@ struct testee : save_inspector { ...@@ -174,6 +148,16 @@ struct testee : save_inspector {
return object_t<testee>{T::tname, this}; return object_t<testee>{T::tname, this};
} }
template <class T>
auto object(optional<T>&) {
return object_t<testee>{"optional", this};
}
template <class... Ts>
auto object(variant<Ts...>&) {
return object_t<testee>{"variant", this};
}
bool begin_object(string_view object_name) { bool begin_object(string_view object_name) {
new_line(); new_line();
indent += 2; indent += 2;
...@@ -205,6 +189,22 @@ struct testee : save_inspector { ...@@ -205,6 +189,22 @@ struct testee : save_inspector {
return ok; return ok;
} }
bool begin_field(string_view name, span<const type_id_t>, size_t) {
new_line();
indent += 2;
log += "begin variant field ";
log.insert(log.end(), name.begin(), name.end());
return ok;
}
bool begin_field(string_view name, bool, span<const type_id_t>, size_t) {
new_line();
indent += 2;
log += "begin optional variant field ";
log.insert(log.end(), name.begin(), name.end());
return ok;
}
bool end_field() { bool end_field() {
indent -= 2; indent -= 2;
new_line(); new_line();
...@@ -212,13 +212,34 @@ struct testee : save_inspector { ...@@ -212,13 +212,34 @@ struct testee : save_inspector {
return ok; return ok;
} }
bool begin_tuple(size_t size) {
new_line();
indent += 2;
log += "begin tuple of size ";
log += std::to_string(size);
return ok;
}
bool end_tuple() {
indent -= 2;
new_line();
log += "end tuple";
return ok;
}
template <class T> template <class T>
bool value(const T&) { std::enable_if_t<std::is_arithmetic<T>::value, bool> value(const T&) {
new_line(); new_line();
log += type_name_v<T>; log += type_name_v<T>;
log += " value"; log += " value";
return ok; return ok;
} }
bool value(const std::string&) {
new_line();
log += "std::string value";
return ok;
}
}; };
struct fixture { struct fixture {
...@@ -291,19 +312,38 @@ end object)_"); ...@@ -291,19 +312,38 @@ end object)_");
} }
CAF_TEST(save inspectors support fields with fallbacks and invariants) { CAF_TEST(save inspectors support fields with fallbacks and invariants) {
duration d{"minutes", 42.0}; CAF_MESSAGE("save inspectors suppress fields with their default value");
CAF_CHECK_EQUAL(inspect(f, d), true); {
CAF_CHECK_EQUAL(d.unit, "minutes"); duration d{"seconds", 12.0};
CAF_CHECK_EQUAL(d.count, 42.0); CAF_CHECK_EQUAL(inspect(f, d), true);
CAF_CHECK_EQUAL(f.log, R"_( CAF_CHECK_EQUAL(d.unit, "seconds");
CAF_CHECK_EQUAL(d.count, 12.0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object duration begin object duration
begin field unit begin optional field unit
end field
begin field count
double value
end field
end object)_");
}
f.log.clear();
CAF_MESSAGE("save inspectors include fields with non-default value");
{
duration d{"minutes", 42.0};
CAF_CHECK_EQUAL(inspect(f, d), true);
CAF_CHECK_EQUAL(d.unit, "minutes");
CAF_CHECK_EQUAL(d.count, 42.0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object duration
begin optional field unit
std::string value std::string value
end field end field
begin field count begin field count
double value double value
end field end field
end object)_"); end object)_");
}
} }
CAF_TEST(save inspectors support fields with optional values) { CAF_TEST(save inspectors support fields with optional values) {
...@@ -335,7 +375,7 @@ CAF_TEST(save inspectors support fields with getters and setters) { ...@@ -335,7 +375,7 @@ CAF_TEST(save inspectors support fields with getters and setters) {
foobar fb; foobar fb;
fb.foo("hello"); fb.foo("hello");
fb.bar("world"); fb.bar("world");
CAF_CHECK_EQUAL(inspect(f, fb), true); CAF_CHECK(inspect(f, fb));
CAF_CHECK_EQUAL(fb.foo(), "hello"); CAF_CHECK_EQUAL(fb.foo(), "hello");
CAF_CHECK_EQUAL(fb.bar(), "world"); CAF_CHECK_EQUAL(fb.bar(), "world");
CAF_CHECK_EQUAL(f.log, R"_( CAF_CHECK_EQUAL(f.log, R"_(
...@@ -349,4 +389,112 @@ begin object foobar ...@@ -349,4 +389,112 @@ begin object foobar
end object)_"); end object)_");
} }
CAF_TEST(save inspectors support nasty data structures) {
nasty x;
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object nasty
begin field field_01
int32_t value
end field
begin optional field field_02
end field
begin field field_03
int32_t value
end field
begin optional field field_04
end field
begin optional field field_05
end field
begin optional field field_06
end field
begin optional field field_07
end field
begin optional field field_08
end field
begin variant field field_09
std::string value
end field
begin optional variant field field_10
std::string value
end field
begin variant field field_11
std::string value
end field
begin optional variant field field_12
std::string value
end field
begin field field_13
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_14
end field
begin field field_15
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_16
end field
begin field field_17
int32_t value
end field
begin optional field field_18
end field
begin field field_19
int32_t value
end field
begin optional field field_20
end field
begin optional field field_21
end field
begin optional field field_22
end field
begin optional field field_23
end field
begin optional field field_24
end field
begin variant field field_25
std::string value
end field
begin optional variant field field_26
std::string value
end field
begin variant field field_27
std::string value
end field
begin optional variant field field_28
std::string value
end field
begin field field_29
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_30
end field
begin field field_31
begin tuple of size 2
std::string value
int32_t value
end tuple
end field
begin optional field field_32
end field
begin optional variant field field_33
end field
begin optional field field_34
end field
begin optional variant field field_35
end field
begin optional field field_36
end field
end object)_");
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment