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
ipv6_address
ipv6_endpoint
ipv6_subnet
load_inspector
local_group
logger
mailbox_element
......@@ -302,6 +303,7 @@ caf_add_test_suites(caf-core-test
policy.select_any
request_timeout
result
save_inspector
selective_streaming
serial_reply
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.
.. literalinclude:: /examples/caf-application.conf
:language: none
.. _add-custom-message-type:
.. _custom-message-types:
Adding Custom Message Types
---------------------------
......@@ -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
``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
add-custom-message-type_).
custom-message-types_).
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
......
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