Commit f4ecc9b4 authored by Dominik Charousset's avatar Dominik Charousset

Add scaffold for the new inspector API

parent 5930a041
...@@ -280,6 +280,7 @@ caf_add_test_suites(caf-core-test ...@@ -280,6 +280,7 @@ caf_add_test_suites(caf-core-test
ipv6_address ipv6_address
ipv6_endpoint ipv6_endpoint
ipv6_subnet ipv6_subnet
load_inspector
local_group local_group
logger logger
mailbox_element mailbox_element
...@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test ...@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test
policy.select_any policy.select_any
request_timeout request_timeout
result result
save_inspector
selective_streaming selective_streaming
serial_reply serial_reply
serialization serialization
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/detail/inspect.hpp"
#include "caf/fwd.hpp"
namespace caf {
/// 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>
struct inspector_access {
template <class Inspector>
static bool apply(Inspector& f, T& x) {
using detail::inspect;
using result_type = decltype(inspect(f, x));
if constexpr (std::is_same<result_type, bool>::value)
return inspect(f, x);
else
return apply_deprecated(f, x);
}
template <class Inspector>
[[deprecated("inspect() overloads should return bool")]] static bool
apply_deprecated(Inspector& f, T& x) {
if (auto err = inspect(f, x)) {
f.set_error(std::move(err));
return false;
}
return true;
}
};
/// Customization point to influence how inspectors treat fields of type `T`.
template <class T>
struct inspector_access_traits {
static constexpr bool is_optional = false;
static constexpr bool is_sum_type = false;
};
template <class T>
struct inspector_access_traits<optional<T>> {
static constexpr bool is_optional = true;
static constexpr bool is_sum_type = false;
};
template <class... Ts>
struct inspector_access_traits<variant<Ts...>> {
static constexpr bool is_optional = false;
static constexpr bool is_sum_type = true;
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
#include <utility>
#include "caf/error.hpp"
#include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Base type for inspectors that load objects from some input source. Deriving
/// from this class enables the inspector DSL.
/// @note The derived type still needs to provide an `object()` member function
/// for the DSL.
class load_inspector {
public:
// -- contants ---------------------------------------------------------------
/// Convenience constant to indicate success of a processing step.
static constexpr bool ok = true;
/// Convenience constant to indicate that a processing step failed and no
/// further processing steps should take place.
static constexpr bool stop = false;
/// Enables dispatching on the inspector type.
static constexpr bool is_loading = true;
/// A load inspector never reads the state of an object.
static constexpr bool reads_state = false;
/// A load inspector overrides the state of an object.
static constexpr bool writes_state = true;
// -- error management -------------------------------------------------------
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 -------------------------------------------
template <class T, class Predicate>
struct field_with_invariant_and_fallback_t {
string_view field_name;
T* val;
T fallback;
Predicate predicate;
template <class Inspector>
bool operator()(string_view object_name, Inspector& f) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return stop;
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>
struct field_with_fallback_t {
string_view field_name;
T* val;
T fallback;
template <class Inspector>
bool operator()(string_view, Inspector& f) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return stop;
if (is_present)
return inspector_access<T>::apply(f, *val) && f.end_field();
*val = std::move(fallback);
return f.end_field();
}
template <class Predicate>
auto invariant(Predicate predicate) && {
return field_with_invariant_and_fallback_t<T, Predicate>{
field_name,
val,
std::move(fallback),
std::move(predicate),
};
}
};
template <class T, class Predicate>
struct field_with_invariant_t {
string_view field_name;
T* val;
Predicate predicate;
template <class Inspector>
bool operator()(string_view object_name, Inspector& f) {
if (f.begin_field(field_name) //
&& 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;
}
auto fallback(T value) && {
return field_with_invariant_and_fallback_t<T, Predicate>{
field_name,
val,
std::move(value),
std::move(predicate),
};
}
};
template <class T>
struct field_t {
string_view field_name;
T* val;
template <class Inspector>
bool operator()(string_view, Inspector& f) {
if constexpr (inspector_access_traits<T>::is_optional) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
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) && {
return field_with_fallback_t<T>{field_name, val, std::move(value)};
}
template <class Predicate>
auto invariant(Predicate predicate) && {
return field_with_invariant_t<T, Predicate>{
field_name,
val,
std::move(predicate),
};
}
};
// -- DSL types for virtual fields (getter and setter access) ----------------
template <class T, class Set, class Predicate>
struct virt_field_with_invariant_and_fallback_t {
string_view field_name;
Set set;
T fallback;
Predicate predicate;
template <class Inspector>
bool operator()(string_view object_name, Inspector& f) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return stop;
if (is_present) {
auto tmp = T{};
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>
struct virt_field_with_fallback_t {
string_view field_name;
Set set;
T fallback;
template <class Inspector>
bool operator()(string_view object_name, Inspector& f) {
bool is_present = false;
if (!f.begin_field(field_name, is_present))
return stop;
if (is_present) {
auto tmp = T{};
if (!inspector_access<T>::apply(f, tmp))
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, class Predicate>
struct virt_field_with_invariant_t {
string_view field_name;
Set set;
Predicate predicate;
template <class Inspector>
bool operator()(string_view object_name, Inspector& f) {
if (!f.begin_field(field_name))
return stop;
auto tmp = T{};
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();
}
auto fallback(T value) && {
return virt_field_with_invariant_and_fallback_t<T, Set, Predicate>{
field_name,
std::move(set),
std::move(value),
std::move(predicate),
};
}
};
template <class T, class Set>
struct virt_field_t {
string_view field_name;
Set set;
template <class Inspector>
bool operator()(string_view, Inspector& f) {
if (!f.begin_field(field_name))
return stop;
auto tmp = T{};
if (!inspector_access<T>::apply(f, tmp))
return stop;
if (!set(std::move(tmp))) {
}
return f.end_field();
}
auto fallback(T value) && {
return virt_field_with_fallback_t<T, Set>{
field_name,
std::move(set),
std::move(value),
};
}
};
template <class Inspector>
struct object_t {
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(object_name, *f) && ...)
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
}
};
// -- factory functions ------------------------------------------------------
template <class T>
static auto field(string_view name, T& x) {
return field_t<T>{name, &x};
}
template <class Get, class Set>
static auto field(string_view name, Get get, Set set) {
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};
}
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/error.hpp"
#include "caf/inspector_access.hpp"
#include "caf/sec.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Base type for inspectors that save objects to some output sink. Deriving
/// from this class enables the inspector DSL.
/// @note The derived type still needs to provide an `object()` member function
/// for the DSL.
class save_inspector {
public:
// -- contants ---------------------------------------------------------------
/// Convenience constant to indicate success of a processing step.
static constexpr bool ok = true;
/// Convenience constant to indicate that a processing step failed and no
/// further processing steps should take place.
static constexpr bool stop = false;
/// Enables dispatching on the inspector type.
static constexpr bool is_loading = false;
/// A save inspector only reads the state of an object.
static constexpr bool reads_state = true;
/// A load inspector never modifies the state of an object.
static constexpr bool writes_state = false;
// -- DSL types for regular fields -------------------------------------------
template <class T>
struct field_t {
string_view field_name;
T* val;
template <class Inspector>
bool operator()(string_view, Inspector& f) {
if constexpr (inspector_access_traits<T>::is_optional) {
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>
field_t& fallback(Unused&&) {
return *this;
}
template <class Predicate>
field_t invariant(Predicate&&) {
return *this;
}
};
// -- DSL types for virtual fields (getter and setter access) ----------------
template <class T, class Get>
struct virt_field_t {
string_view field_name;
Get get;
template <class Inspector>
bool operator()(string_view, Inspector& f) {
if (!f.begin_field(field_name))
return stop;
auto&& value = get();
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>
virt_field_t& fallback(Unused&&) {
return *this;
}
template <class Predicate>
virt_field_t invariant(Predicate&&) {
return *this;
}
};
template <class Inspector>
struct object_t {
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(object_name, *f) && ...)
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
}
};
// -- factory functions ------------------------------------------------------
template <class T>
static auto field(string_view name, T& x) {
return field_t<T>{name, &x};
}
template <class Get, class Set>
static auto field(string_view name, Get get, Set&&) {
using field_type = std::decay_t<decltype(get())>;
return virt_field_t<field_type, Get>{name, get};
}
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE load_inspector
#include "caf/load_inspector.hpp"
#include "caf/test/dsl.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace caf {
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;
namespace {
using string_list = std::vector<std::string>;
struct point_3d {
static inline string_view tname = "point_3d";
int32_t x;
int32_t y;
int32_t z;
};
template <class Inspector>
bool inspect(Inspector& f, point_3d& x) {
return f.object(x).fields(f.field("x", x.x), f.field("y", x.y),
f.field("z", x.z));
}
struct line {
static inline string_view tname = "line";
point_3d p1;
point_3d p2;
};
template <class Inspector>
bool inspect(Inspector& f, line& x) {
return f.object(x).fields(f.field("p1", x.p1), f.field("p2", x.p2));
}
struct duration {
static inline string_view tname = "duration";
std::string unit;
double count;
};
bool valid_time_unit(const std::string& unit) {
return unit == "seconds" || unit == "minutes";
}
template <class Inspector>
bool inspect(Inspector& f, duration& x) {
return f.object(x).fields(
f.field("unit", x.unit).fallback("seconds").invariant(valid_time_unit),
f.field("count", x.count));
}
struct person {
static inline string_view tname = "person";
std::string name;
optional<std::string> phone;
};
template <class Inspector>
bool inspect(Inspector& f, person& x) {
return f.object(x).fields(f.field("name", x.name), f.field("phone", x.phone));
}
class foobar {
public:
static inline string_view tname = "foobar";
const std::string& foo() {
return foo_;
}
void foo(std::string value) {
foo_ = std::move(value);
}
const std::string& bar() {
return bar_;
}
void bar(std::string value) {
bar_ = std::move(value);
}
private:
std::string foo_;
std::string bar_;
};
template <class Inspector>
bool inspect(Inspector& f, foobar& x) {
auto get_foo = [&x]() -> decltype(auto) { return x.foo(); };
auto set_foo = [&x](std::string value) {
x.foo(std::move(value));
return true;
};
auto get_bar = [&x]() -> decltype(auto) { return x.bar(); };
auto set_bar = [&x](std::string value) {
x.bar(std::move(value));
return true;
};
return f.object(x).fields(f.field("foo", get_foo, set_foo),
f.field("bar", get_bar, set_bar));
}
struct testee : load_inspector {
std::string log;
error err;
void set_error(error x) {
err = std::move(x);
}
size_t indent = 0;
void new_line() {
log += '\n';
log.insert(log.end(), indent, ' ');
}
template <class T>
auto object(T&) {
return object_t<testee>{T::tname, this};
}
bool begin_object(string_view object_name) {
new_line();
indent += 2;
log += "begin object ";
log.insert(log.end(), object_name.begin(), object_name.end());
return ok;
}
bool end_object() {
indent -= 2;
new_line();
log += "end object";
return ok;
}
bool begin_field(string_view name) {
new_line();
indent += 2;
log += "begin field ";
log.insert(log.end(), name.begin(), name.end());
return ok;
}
bool begin_field(string_view name, bool& is_present) {
new_line();
indent += 2;
log += "begin optional field ";
log.insert(log.end(), name.begin(), name.end());
is_present = false;
return ok;
}
bool end_field() {
indent -= 2;
new_line();
log += "end field";
return ok;
}
template <class T>
bool value(T& x) {
new_line();
log += type_name_v<T>;
log += " value";
x = T{};
return ok;
}
};
struct fixture {
testee f;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(load_inspector_tests, fixture)
CAF_TEST(load inspectors can visit simple POD types) {
point_3d p{1, 1, 1};
CAF_CHECK_EQUAL(inspect(f, p), true);
CAF_CHECK_EQUAL(p.x, 0);
CAF_CHECK_EQUAL(p.y, 0);
CAF_CHECK_EQUAL(p.z, 0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object)_");
}
CAF_TEST(load inspectors recurse into members) {
line l{point_3d{1, 1, 1}, point_3d{1, 1, 1}};
CAF_CHECK_EQUAL(inspect(f, l), true);
CAF_CHECK_EQUAL(l.p1.x, 0);
CAF_CHECK_EQUAL(l.p1.y, 0);
CAF_CHECK_EQUAL(l.p1.z, 0);
CAF_CHECK_EQUAL(l.p2.x, 0);
CAF_CHECK_EQUAL(l.p2.y, 0);
CAF_CHECK_EQUAL(l.p2.z, 0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object line
begin field p1
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
begin field p2
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
end object)_");
}
CAF_TEST(load inspectors support fields with fallbacks and invariants) {
duration d{"minutes", 42};
CAF_CHECK_EQUAL(inspect(f, d), true);
CAF_CHECK_EQUAL(d.unit, "seconds");
CAF_CHECK_EQUAL(d.count, 0.0);
CAF_CHECK_EQUAL(f.log, R"_(
begin object duration
begin optional field unit
end field
begin field count
double value
end field
end object)_");
}
CAF_TEST(load inspectors support fields with optional values) {
person p{"Bruce Almighty", std::string{"776-2323"}};
CAF_CHECK_EQUAL(inspect(f, p), true);
CAF_CHECK_EQUAL(p.name, "");
CAF_CHECK_EQUAL(p.phone, none);
CAF_CHECK_EQUAL(f.log, R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_");
}
CAF_TEST(load inspectors support fields with getters and setters) {
foobar fb;
fb.foo("hello");
fb.bar("world");
CAF_CHECK_EQUAL(inspect(f, fb), true);
CAF_CHECK_EQUAL(fb.foo(), "");
CAF_CHECK_EQUAL(fb.bar(), "");
CAF_CHECK_EQUAL(f.log, R"_(
begin object foobar
begin field foo
std::string value
end field
begin field bar
std::string value
end field
end object)_");
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE save_inspector
#include "caf/save_inspector.hpp"
#include "caf/test/dsl.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace caf {
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;
namespace {
using string_list = std::vector<std::string>;
struct point_3d {
static inline string_view tname = "point_3d";
int32_t x;
int32_t y;
int32_t z;
};
template <class Inspector>
bool inspect(Inspector& f, point_3d& x) {
return f.object(x).fields(f.field("x", x.x), f.field("y", x.y),
f.field("z", x.z));
}
struct line {
static inline string_view tname = "line";
point_3d p1;
point_3d p2;
};
template <class Inspector>
bool inspect(Inspector& f, line& x) {
return f.object(x).fields(f.field("p1", x.p1), f.field("p2", x.p2));
}
struct duration {
static inline string_view tname = "duration";
std::string unit;
double count;
};
bool valid_time_unit(const std::string& unit) {
return unit == "seconds" || unit == "minutes";
}
template <class Inspector>
bool inspect(Inspector& f, duration& x) {
return f.object(x).fields(
f.field("unit", x.unit).fallback("seconds").invariant(valid_time_unit),
f.field("count", x.count));
}
struct person {
static inline string_view tname = "person";
std::string name;
optional<std::string> phone;
};
template <class Inspector>
bool inspect(Inspector& f, person& x) {
return f.object(x).fields(f.field("name", x.name), f.field("phone", x.phone));
}
class foobar {
public:
static inline string_view tname = "foobar";
const std::string& foo() {
return foo_;
}
void foo(std::string value) {
foo_ = std::move(value);
}
const std::string& bar() {
return bar_;
}
void bar(std::string value) {
bar_ = std::move(value);
}
private:
std::string foo_;
std::string bar_;
};
template <class Inspector>
bool inspect(Inspector& f, foobar& x) {
auto get_foo = [&x]() -> decltype(auto) { return x.foo(); };
auto set_foo = [&x](std::string value) {
x.foo(std::move(value));
return true;
};
auto get_bar = [&x]() -> decltype(auto) { return x.bar(); };
auto set_bar = [&x](std::string value) {
x.bar(std::move(value));
return true;
};
return f.object(x).fields(f.field("foo", get_foo, set_foo),
f.field("bar", get_bar, set_bar));
}
struct testee : save_inspector {
std::string log;
error err;
void set_error(error x) {
err = std::move(x);
}
size_t indent = 0;
void new_line() {
log += '\n';
log.insert(log.end(), indent, ' ');
}
template <class T>
auto object(T&) {
return object_t<testee>{T::tname, this};
}
bool begin_object(string_view object_name) {
new_line();
indent += 2;
log += "begin object ";
log.insert(log.end(), object_name.begin(), object_name.end());
return ok;
}
bool end_object() {
indent -= 2;
new_line();
log += "end object";
return ok;
}
bool begin_field(string_view name) {
new_line();
indent += 2;
log += "begin field ";
log.insert(log.end(), name.begin(), name.end());
return ok;
}
bool begin_field(string_view name, bool) {
new_line();
indent += 2;
log += "begin optional field ";
log.insert(log.end(), name.begin(), name.end());
return ok;
}
bool end_field() {
indent -= 2;
new_line();
log += "end field";
return ok;
}
template <class T>
bool value(const T&) {
new_line();
log += type_name_v<T>;
log += " value";
return ok;
}
};
struct fixture {
testee f;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(load_inspector_tests, fixture)
CAF_TEST(save inspectors can visit simple POD types) {
point_3d p{1, 1, 1};
CAF_CHECK_EQUAL(inspect(f, p), true);
CAF_CHECK_EQUAL(p.x, 1);
CAF_CHECK_EQUAL(p.y, 1);
CAF_CHECK_EQUAL(p.z, 1);
CAF_CHECK_EQUAL(f.log, R"_(
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object)_");
}
CAF_TEST(save inspectors recurse into members) {
line l{point_3d{1, 1, 1}, point_3d{1, 1, 1}};
CAF_CHECK_EQUAL(inspect(f, l), true);
CAF_CHECK_EQUAL(l.p1.x, 1);
CAF_CHECK_EQUAL(l.p1.y, 1);
CAF_CHECK_EQUAL(l.p1.z, 1);
CAF_CHECK_EQUAL(l.p2.x, 1);
CAF_CHECK_EQUAL(l.p2.y, 1);
CAF_CHECK_EQUAL(l.p2.z, 1);
CAF_CHECK_EQUAL(f.log, R"_(
begin object line
begin field p1
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
begin field p2
begin object point_3d
begin field x
int32_t value
end field
begin field y
int32_t value
end field
begin field z
int32_t value
end field
end object
end field
end object)_");
}
CAF_TEST(save inspectors support fields with fallbacks and invariants) {
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 field unit
std::string value
end field
begin field count
double value
end field
end object)_");
}
CAF_TEST(save inspectors support fields with optional values) {
person p1{"Eduard Example", none};
CAF_CHECK_EQUAL(inspect(f, p1), true);
CAF_CHECK_EQUAL(f.log, R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_");
f.log.clear();
person p2{"Bruce Almighty", std::string{"776-2323"}};
CAF_CHECK_EQUAL(inspect(f, p2), true);
CAF_CHECK_EQUAL(f.log, R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
std::string value
end field
end object)_");
}
CAF_TEST(save inspectors support fields with getters and setters) {
foobar fb;
fb.foo("hello");
fb.bar("world");
CAF_CHECK_EQUAL(inspect(f, fb), true);
CAF_CHECK_EQUAL(fb.foo(), "hello");
CAF_CHECK_EQUAL(fb.bar(), "world");
CAF_CHECK_EQUAL(f.log, R"_(
begin object foobar
begin field foo
std::string value
end field
begin field bar
std::string value
end field
end object)_");
}
CAF_TEST_FIXTURE_SCOPE_END()
...@@ -229,7 +229,7 @@ are usually detected at runtime and thus have no hard-coded default. ...@@ -229,7 +229,7 @@ are usually detected at runtime and thus have no hard-coded default.
.. literalinclude:: /examples/caf-application.conf .. literalinclude:: /examples/caf-application.conf
:language: none :language: none
.. _add-custom-message-type: .. _custom-message-types:
Adding Custom Message Types Adding Custom Message Types
--------------------------- ---------------------------
...@@ -294,7 +294,7 @@ Adding the calculator actor type to our config is achieved by calling ...@@ -294,7 +294,7 @@ Adding the calculator actor type to our config is achieved by calling
``add_actor_type``. After calling this in our config, we can spawn the ``add_actor_type``. After calling this in our config, we can spawn the
``calculator`` anywhere in the distributed actor system (assuming all nodes use ``calculator`` anywhere in the distributed actor system (assuming all nodes use
the same config). Note that the handle type still requires a type ID (see the same config). Note that the handle type still requires a type ID (see
add-custom-message-type_). custom-message-types_).
Our final example illustrates how to spawn a ``calculator`` locally by Our final example illustrates how to spawn a ``calculator`` locally by
using its type name. Because the dynamic type name lookup can fail and the using its type name. Because the dynamic type name lookup can fail and the
......
.. _type-inspection: .. _type-inspection:
Type Inspection (Serialization and String Conversion) Type Inspection
===================================================== ===============
CAF is designed with distributed systems in mind. Hence, all message types must We designed CAF with distributed systems in mind. Hence, all message types must
be serializable and need a platform-neutral, unique name that is configured at be serializable. Using a message type that is not serializable causes a compiler
startup (see :ref:`add-custom-message-type`). Using a message type that is not error unless explicitly listed as unsafe message type by the user (see
serializable causes a compiler error (see :ref:`unsafe-message-type`). CAF :ref:`unsafe-message-type`).
serializes individual elements of a message by using the inspection API. This
API allows users to provide code for serialization as well as string conversion The inspection API allows CAF to deconstruct C++ objects into fields and values.
with a single free function. The signature for a class ``my_class`` is always as Users can either provide free functions named ``inspect`` that CAF picks up via
follows: `ADL <https://en.wikipedia.org/wiki/Argument-dependent_name_lookup>`_ or
specialize ``caf::inspector_access``.
In both cases, users call members and member functions on an ``Inspector`` that
provides a domain-specific language (DSL) for describing the structure of a C++
object.
POD Types
---------
Plain old data (POD) types always declare all member variables *public*. An
``inspect`` overload for PODs simply passes all member variables as fields to
the *inspector*. For example, consider the following POD type ``point_3d``:
.. code-block:: C++ .. code-block:: C++
template <class Inspector> struct point_3d {
typename Inspector::result_type inspect(Inspector& f, my_class& x) { int x;
return f(...); int y;
} int z;
};
The function ``inspect`` passes meta information and data fields to the To allow CAF to properly serialize and deserialize the POD type, the simplest
variadic call operator of the inspector. The following example illustrates an way is to write a free ``inspect`` function:
implementation for ``inspect`` for a simple POD struct.
.. literalinclude:: /examples/custom_type/custom_types_1.cpp .. code-block:: C++
:language: C++
:lines: 23-33 template <class Inspector>
bool inspect(Inspector& f, point_3d& x) {
return f.object().fields(f.field("x", x.x),
f.field("y", x.y),
f.field("z", x.z));
}
The inspector recursively inspects all data fields and has builtin support for After providing this function as well as listing ``point_3d`` in a type ID
(1) ``std::tuple``, (2) ``std::pair``, (3) C arrays, (4) any block, CAF can save and load our custom POD type. Of course, this is a recursive
container type with ``x.size()``, ``x.empty()``, process that allows us to use ``point_3d`` as member variables of other types.
``x.begin()`` and ``x.end()``.
We consciously made the inspect API as generic as possible to allow for Working with 3rd party libraries usually rules out adding free functions for
extensibility. This allows users to use CAF's types in other contexts, to existing classes, because the namespace belongs to another project. Hence, CAF
implement parsers, etc. also allows specializing ``inspector_access`` instead. This requires slightly
more boilerplate code, but follows the same pattern:
.. code-block:: C++
Inspector Concept namespace caf {
----------------- template <>
struct inspector_access<point_3d> {
template <class Inspector>
static bool apply(Inspector& f, point_3d& x) {
return f.object(x).fields(f.field("x", x.x),
f.field("y", x.y),
f.field("z", x.z));
}
};
}
The following concept class shows the requirements for inspectors. The After listing ``point_3d`` in a type ID block and either providing a free
placeholder ``T`` represents any user-defined type. For example, ``inspect`` overload or specializing ``inspector_access``, CAF is able to:
``error`` when performing I/O operations or some integer type when
implementing a hash function. - Serialize and deserialize ``point_3d`` objects to/from Byte sequences.
- Render a ``point_3d`` as a human-readable string via ``caf::deep_to_string``.
- Read ``point_3d`` objects from a configuration file.
Types with Getter and Setter Access
-----------------------------------
Types that declare their fields *private* and only grant access via getter and
setter cannot pass references to the member variables to the inspector. Instead,
they can pass a pair of function objects to the inspector to read and write the
field.
Consider the following non-POD type ``foobar``:
.. code-block:: C++ .. code-block:: C++
Inspector { class foobar {
using result_type = T; public:
const std::string& foo() {
return foo_;
}
void foo(std::string value) {
foo_ = std::move(value);
}
const std::string& bar() {
return bar_;
}
void bar(std::string value) {
bar_ = std::move(value);
}
private:
std::string foo_;
std::string bar_;
};
Since ``foo_`` and ``bar_`` are not accessible from outside the class, the
inspector has to use the getter and setter functions. However, C++ has no
formalized API for getters and setters. Moreover, not all setters are so trivial
as in the example above. Setters may enforce invariants, for example, and thus
may fail.
In order to work with any flair of getter and setter functions, CAF requires
users to wrap these member functions calls into two function objects. The first
one wraps the getter, takes no arguments, and returns the underlying value
(either by reference or by value). The second one wraps the setter, takes
exactly one argument (the new value), and returns a ``bool`` that indicates
whether the operation succeeded (by returning ``true``) or failed (``by
returning false``).
The example below shows a possible ``inspect`` implementation for the ``fobar``
class shown before:
static constexpr bool reads_state = ...; .. code-block:: C++
template <class Inspector>
bool inspect(Inspector& f, foobar& x) {
auto get_foo = [&x]() -> decltype(auto) { return x.foo(); };
auto set_foo = [&x](std::string value) {
x.foo(std::move(value));
return true;
};
auto get_bar = [&x]() -> decltype(auto) { return x.bar(); };
auto set_bar = [&x](std::string value) {
x.bar(std::move(value));
return true;
};
return f.object(x).fields(f.field("foo", get_foo, set_foo),
f.field("bar", get_bar, set_bar));
}
.. note::
For classes that lie in the responsibility of the same developers that
implement the ``inspect`` function, implementing ``inspect`` as friend
function inside the class usually can avoid going through the getter and
setter functions.
Inspector DSL
-------------
As shown in previous examples, type inspection with an inspector ``f`` always
starts with ``f.object(...)`` as entry point. After that, users may set a pretty
type name with ``pretty_name`` that inspectors may use when generating
human-readable output. Afterwards, the inspectors expect users to call
``fields`` with all member variables of the object.
The following pseudo code illustrates how the inspector DSL is structured in
terms of types involved and member function interfaces.
static constexpr bool writes_state = ...;
template <class... Ts> .. code-block:: C++
result_type operator()(Ts&&...);
} class Inspector {
public:
Object object(T obj);
A saving ``Inspector`` is required to handle constant lvalue and rvalue Field field(string_view name, T& ref);
references. A loading ``Inspector`` must only accept mutable lvalue
references to data fields, but still allow for constant lvalue references and
rvalue references to annotations.
Annotations Field field(string_view name, function<T()> get, function<void(T)> set);
----------- };
Annotations allow users to fine-tune the behavior of inspectors by providing class FieldWithFallbackAndInvariant;
addition meta information about a type. All annotations live in the namespace
``caf::meta`` and derive from ``caf::meta::annotation``. An
inspector can query whether a type ``T`` is an annotation with
``caf::meta::is_annotation<T>::value``. Annotations are passed to the
call operator of the inspector along with data fields. The following list shows
all annotations supported by CAF:
* ``type_name(n)``: Display type name as ``n`` in human-friendly output (position before data fields). class FieldWithFallback {
* ``hex_formatted()``: Format the following data field in hex format. public:
* ``omittable()``: Omit the following data field in human-friendly output. FieldWithFallbackAndInvariant invariant(function<bool>(T));
* ``omittable_if_empty()``: Omit the following data field if it is empty in human-friendly output. };
* ``omittable_if_none()``: Omit the following data field if it equals ``none`` in human-friendly output.
* ``save_callback(f)``: Call ``f`` when serializing (position after data fields).
* ``load_callback(f)``: Call ``f`` after deserializing all data fields (position after data fields).
Backwards and Third-party Compatibility class FieldWithInvariant;
---------------------------------------
CAF evaluates common free function other than ``inspect`` in order to class Field {
simplify users to integrate CAF into existing code bases. public:
FieldWithFallback fallback(T);
Serializers and deserializers call user-defined ``serialize`` FieldWithInvariant invariant(function<bool>(T));
functions. Both types support ``operator&`` as well as };
``operator()`` for individual data fields. A ``serialize``
function has priority over ``inspect``.
When converting a user-defined type to a string, CAF calls user-defined class FieldsInspector {
``to_string`` functions and prefers those over ``inspect``. public:
bool fields(...);
};
class Object : public FieldsInspector {
public:
FieldsInspector pretty_name(string_view);
};
The ``Inspector`` has the ``object`` member variable to set things in motion,
but it also serves as a factory for ``Field`` definitions. Fields always have a
name and users can either bind a reference to the member variable or provide
getter and setter functions. Optionally, users can equip fields with a fallback
value or an invariant predicate. Providing a fallback value automatically makes
fields optional. For example, consider the following class ``duration`` and its
implementation for ``inspect``:
.. code-block:: C++
struct duration {
string unit;
double count;
};
bool valid_time_unit(const string& unit) {
return unit == "seconds" || unit == "minutes";
}
template <class Inspector>
bool inspect(Inspector& f, duration& x) {
return f.object(x).fields(
f.field("unit", x.unit).fallback("seconds").invariant(valid_time_unit),
f.field("count", x.count));
}
Real code probably would not use a ``string`` to store the time unit. However,
with the fallback, we have enabled CAF to use ``"seconds"`` whenever the input
contains no value for the ``unit`` field. Further, the invariant makes sure that
we verify our input before accepting it.
Whether optional fields are supported depends on the format / inspector. For
example, the binary serialization protocol in CAF has no notion of optional
fields. Hence, the default binary serializers simply read and write fields in
the order they apear in. However, the inspectors for the configuration framework
do allow optional fields. With our ``inspect`` overload for ``duration``, we
could configure a parameter named ``example-app.request-timeout`` as follows:
.. code-block:: none
# example 1: ok, falls back to "seconds"
example-app {
request-timeout {
count = 1.3
}
}
# example 2: ok, explicit definition of the time unit
example-app {
request-timeout {
count = 1.3
unit = "minutes"
}
}
# example 3: error, "parsecs" is not a time unit (invariant does not hold)
example-app {
request-timeout {
count = 12
unit = "parsecs"
}
}
Inspector Traits
----------------
When writing custom ``inspect`` functions, providing a single overload for all
inspectors may result in undesired tradeoffs or convoluted code. For example,
inspection code may be much cleaner when split into a ``save`` and a ``load``
function. The kind of output format may also play a role. When reading and
writing human-readable data, we might want to improve the user experience by
using the constant names in enumeration types rather than using the underlying
integer values.
To enable static dispatching based on the inspector kind, all inspectors provide
the following ``static constexpr bool`` constants:
- ``is_loading``: If ``true``, tags the inspector as a deserializer that
overrides the state of an object. Otherwise, the inspector is *saving*, i.e.,
it visits the state of an object without modifying it.
- ``has_human_readable_format``: If ``true``, tags the inspector as reading and
writing a data format that may be consumed or generated by humans. Otherwise,
the ``inspect`` overload can assume a binary data format and optimize for
space rather than user experience. For example, an ``inspect`` overload for
enumeration types may use the constant names for a human-readable format but
otherwise use the underlying integer values.
Using theses constants, users can easily split ``inspect`` overloads into *load*
and *safe*:
.. code-block:: C++
template <class Inspector>
bool inspect(Inspector& f, my_class& x) {
if constexpr (Inspector:is_loading)
return load(f, x);
else
return save(f, x);
}
.. _unsafe-message-type: .. _unsafe-message-type:
Whitelisting Unsafe Message Types Unsafe Message Types
--------------------------------- --------------------
Message types that are not serializable cause compile time errors when used in Message types that do not provide serialization code cause compile time errors
actor communication. When using CAF for concurrency only, this errors can be when used in actor communication. When using CAF for concurrency only, this
suppressed by whitelisting types with errors can be suppressed by explicitly allowing types via
``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows. ``CAF_ALLOW_UNSAFE_MESSAGE_TYPE``. The macro is defined as follows.
Splitting Save and Load Operations .. code-block:: C++
----------------------------------
#define CAF_ALLOW_UNSAFE_MESSAGE_TYPE(type_name) \
If loading and storing cannot be implemented in a single function, users can namespace caf { \
query whether the inspector is loading or storing. For example, consider the template <> \
following class ``foo`` with getter and setter functions and no public struct allowed_unsafe_message_type<type_name> : std::true_type {}; \
access to its members. }
.. literalinclude:: /examples/custom_type/custom_types_3.cpp Keep in mind that *unsafe* means that your program runs into undefined behavior
:language: C++ (or segfaults) when you break your promise and try to serialize messages that
:start-after: --(rst-foo-begin)-- contain unsafe message types.
:end-before: --(rst-foo-end)--
.. note::
Since there is no access to the data fields ``a_`` and ``b_``
(and assuming no changes to ``foo`` are possible), we need to split our Even *unsafe* messages types still require a :ref:`type ID
implementation of ``inspect`` as shown below. <custom-message-types>`.
.. literalinclude:: /examples/custom_type/custom_types_3.cpp .. _custom-inspectors:
:language: C++
:start-after: --(rst-inspect-begin)-- Custom Inspectors (Serializers and Deserializers)
:end-before: --(rst-inspect-end)-- -------------------------------------------------
The purpose of the scope guard in the example above is to write the content of Writing custom serializers and deserializers enables users to add support for
the temporaries back to ``foo`` at scope exit automatically. Storing alternative wire formats such as `Google Protocol Buffers
the result of ``f(...)`` in a temporary first and then writing the <https://developers.google.com/protocol-buffers>`_ or `MessagePack
changes to ``foo`` is not possible, because ``f(...)`` can <https://msgpack.org/index.html>`_ as well as supporting non-binary formats such
return ``void``. as `XML <https://www.w3.org/XML>`_ or `JSON
<https://www.json.org/json-en.html>`_.
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