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
This diff is collapsed.
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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
......
This diff is collapsed.
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