Commit 528e7e26 authored by Dominik Charousset's avatar Dominik Charousset

Allow setter to return bool, error or void

parent 2c696667
......@@ -5,11 +5,19 @@ is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
### Changed
- Setter functions for fields may now return either `bool`, `caf::error` or
`void`. Previously, CAF only allowed `bool`.
### Fixed
- Passing a getter and setter pair to an inspector via `apply` produced a
compiler error for non-builtin types. The inspection API now recursively
inspects user-defined types instead, as was the original intend (#1216).
- The handle type `typed_actor` now can construct from a `typed_actor_pointer`.
This resolves a compiler error when trying to initialize a handle for
`my_handle` from a self pointer of type `my_handle::pointer_view`.
`my_handle` from a self pointer of type `my_handle::pointer_view` (#1218).
## [0.18.0] - 2021-01-25
......
......@@ -78,6 +78,32 @@ constexpr bool has_static_apply_v = has_static_apply<T, Inspector, Obj>::value;
// -- loading ------------------------------------------------------------------
// Converts a setter that returns void, error or bool to a sync function object
// taking no arguments that always returns a bool.
template <class Inspector, class Set, class ValueType>
auto bind_setter(Inspector& f, Set& set, ValueType& tmp) {
using set_result_type = decltype(set(std::move(tmp)));
if constexpr (std::is_same<set_result_type, bool>::value) {
return [&] { return set(std::move(tmp)); };
} else if constexpr (std::is_same<set_result_type, error>::value) {
return [&] {
if (auto err = set(std::move(tmp)); !err) {
return true;
} else {
f.set_error(std::move(err));
return false;
}
};
} else {
static_assert(std::is_same<set_result_type, void>::value,
"a setter must return caf::error, bool or void");
return [&] {
set(std::move(tmp));
return true;
};
}
}
// TODO: remove with CAF 0.19
template <class Inspector, class T>
[[deprecated("please provide apply instead of apply_object/apply_value")]] //
......@@ -390,9 +416,8 @@ struct optional_inspector_access {
container_type& x, IsValid& is_valid,
SyncValue& sync_value) {
traits::emplace(x);
auto set_x = [&] { return sync_value(); };
auto reset = [&x] { x.reset(); };
return detail::load_field(f, field_name, *x, is_valid, set_x, reset);
return detail::load_field(f, field_name, *x, is_valid, sync_value, reset);
}
template <class Inspector, class IsValid, class SyncValue, class SetFallback>
......@@ -400,8 +425,8 @@ struct optional_inspector_access {
container_type& x, IsValid& is_valid,
SyncValue& sync_value, SetFallback& set_fallback) {
traits::emplace(x);
auto set_x = [&] { return sync_value(); };
return detail::load_field(f, field_name, *x, is_valid, set_x, set_fallback);
return detail::load_field(f, field_name, *x, is_valid, sync_value,
set_fallback);
}
};
......@@ -575,8 +600,9 @@ struct variant_inspector_access {
return false;
}
if (!sync_value()) {
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
if (!f.get_error())
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
return false;
}
return f.end_field();
......@@ -605,8 +631,9 @@ struct variant_inspector_access {
return false;
}
if (!sync_value()) {
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
if (!f.get_error())
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
return false;
}
} else {
......
......@@ -25,8 +25,9 @@ struct inspector_access_base {
return false;
}
if (!sync_value()) {
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
if (!f.get_error())
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
return false;
}
return f.end_field();
......@@ -52,8 +53,9 @@ struct inspector_access_base {
return false;
}
if (!sync_value()) {
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
if (!f.get_error())
f.emplace_error(sec::field_value_synchronization_failed,
to_string(field_name));
return false;
}
return f.end_field();
......
......@@ -155,7 +155,7 @@ public:
template <class Inspector>
bool operator()(Inspector& f) {
auto tmp = T{};
auto sync = [this, &tmp] { return set(std::move(tmp)); };
auto sync = detail::bind_setter(f, set, tmp);
auto reset = [this] { set(std::move(fallback)); };
return detail::load_field(f, field_name, tmp, predicate, sync, reset);
}
......@@ -170,7 +170,7 @@ public:
template <class Inspector>
bool operator()(Inspector& f) {
auto tmp = T{};
auto sync = [this, &tmp] { return set(std::move(tmp)); };
auto sync = detail::bind_setter(f, set, tmp);
auto reset = [this] { set(std::move(fallback)); };
return detail::load_field(f, field_name, tmp, detail::always_true, sync,
reset);
......@@ -196,7 +196,7 @@ public:
template <class Inspector>
bool operator()(Inspector& f) {
auto tmp = T{};
auto sync = [this, &tmp] { return set(std::move(tmp)); };
auto sync = detail::bind_setter(f, set, tmp);
return detail::load_field(f, field_name, tmp, predicate, sync);
}
......@@ -219,7 +219,7 @@ public:
template <class Inspector>
bool operator()(Inspector& f) {
auto tmp = T{};
auto sync = [this, &tmp] { return set(std::move(tmp)); };
auto sync = detail::bind_setter(f, set, tmp);
return detail::load_field(f, field_name, tmp, detail::always_true, sync);
}
......@@ -251,7 +251,7 @@ public:
template <class Inspector>
bool operator()(Inspector& f) {
auto tmp = T{};
auto sync = [this, &tmp] { return set(std::move(tmp)); };
auto sync = detail::bind_setter(f, set, tmp);
return detail::load_field(f, field_name, tmp, detail::always_true, sync,
reset);
}
......@@ -339,14 +339,43 @@ public:
template <class Get, class Set>
static auto field(string_view name, Get get, Set set) {
using field_type = std::decay_t<decltype(get())>;
return virt_field_t<field_type, Set>{name, set};
using setter_result = decltype(set(std::declval<field_type&&>()));
if constexpr (std::is_same<setter_result, error>::value
|| std::is_same<setter_result, bool>::value) {
return virt_field_t<field_type, Set>{name, std::move(set)};
} else {
static_assert(std::is_same<setter_result, void>::value,
"setter must return caf::error, bool or void");
auto set_fun = [f{std::move(set)}](field_type&& val) {
f(std::move(val));
return true;
};
return virt_field_t<field_type, decltype(set_fun)>{name,
std::move(set_fun)};
}
}
template <class IsPresent, class Get, class Reset, class Set>
static auto
field(string_view name, IsPresent&&, Get&& get, Reset reset, Set set) {
using field_type = std::decay_t<decltype(get())>;
return optional_virt_field_t<field_type, Reset, Set>{name, reset, set};
using setter_result = decltype(set(std::declval<field_type&&>()));
if constexpr (std::is_same<setter_result, error>::value
|| std::is_same<setter_result, bool>::value) {
return optional_virt_field_t<field_type, Reset, Set>{name,
std::move(reset),
std::move(set)};
} else {
static_assert(std::is_same<setter_result, void>::value,
"setter must return caf::error, bool or void");
auto set_fun = [f{std::move(set)}](field_type&& val) {
f(std::move(val));
return true;
};
return optional_virt_field_t<field_type, Reset, Set>{name,
std::move(reset),
std::move(set_fun)};
}
}
protected:
......
......@@ -10,6 +10,7 @@
#include "caf/inspector_access.hpp"
#include "caf/load_inspector.hpp"
#include "caf/sec.hpp"
namespace caf {
......@@ -61,7 +62,7 @@ public:
auto emplace_result = xs.emplace(std::move(key), std::move(val));
if constexpr (detail::is_pair<decltype(emplace_result)>::value) {
if (!emplace_result.second) {
dref().emplace_error(sec::runtime_error, "multiple key definitions");
super::emplace_error(sec::runtime_error, "multiple key definitions");
return false;
}
}
......@@ -106,17 +107,31 @@ public:
auto tmp = value_type{};
using setter_result = decltype(set(std::move(tmp)));
if constexpr (std::is_same<setter_result, bool>::value) {
if (dref().value(tmp))
return set(std::move(tmp));
else
if (dref().apply(tmp)) {
if (set(std::move(tmp))) {
return true;
} else {
super::emplace_error(sec::save_callback_failed);
return false;
}
} else {
return false;
}
} else if constexpr (std::is_same<setter_result, void>::value) {
if (dref().apply(tmp)) {
set(std::move(tmp));
return true;
} else {
return false;
}
} else {
static_assert(std::is_convertible<setter_result, error>::value);
if (dref().value(tmp)) {
static_assert(std::is_convertible<setter_result, error>::value,
"a setter must return caf::error, bool or void");
if (dref().apply(tmp)) {
if (auto err = set(std::move(tmp)); !err) {
return true;
} else {
this->emplace_error(std::move(err));
super::set_error(std::move(err));
return false;
}
} else {
......
......@@ -6,7 +6,7 @@
#include "caf/load_inspector.hpp"
#include "caf/test/dsl.hpp"
#include "core-test.hpp"
#include <array>
#include <cstdint>
......@@ -35,6 +35,12 @@ struct testee : deserializer {
size_t indent = 0;
void reset() {
log.clear();
indent = 0;
set_error(error{});
}
void new_line() {
log += '\n';
log.insert(log.end(), indent, ' ');
......@@ -567,4 +573,177 @@ CAF_TEST(load inspectors support messages) {
auto msg = make_message(1, "two", 3.0);
}
SCENARIO("load inspectors support apply with a getter and setter") {
std::string baseline = 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)_";
GIVEN("a line object") {
WHEN("passing a void setter") {
f.reset();
auto x = line{{10, 10, 10}, {20, 20, 20}};
auto get = [&x] { return x; };
auto set = [&x](line val) { x = val; };
THEN("the inspector overrides the state using the setter") {
CHECK(f.apply(get, set));
CHECK_EQ(f.log, baseline);
line default_line{{0, 0, 0}, {0, 0, 0}};
CHECK_EQ(x, default_line);
}
}
WHEN("passing a setter returning true") {
f.reset();
auto x = line{{10, 10, 10}, {20, 20, 20}};
auto get = [&x] { return x; };
auto set = [&x](line val) {
x = val;
return true;
};
THEN("the inspector overrides the state using the setter") {
CHECK(f.apply(get, set));
CHECK_EQ(f.log, baseline);
line default_line{{0, 0, 0}, {0, 0, 0}};
CHECK_EQ(x, default_line);
}
}
WHEN("passing a setter returning false") {
f.reset();
auto x = line{{10, 10, 10}, {20, 20, 20}};
auto get = [&x] { return x; };
auto set = [](line) { return false; };
THEN("the inspection fails") {
CHECK(!f.apply(get, set));
CHECK_EQ(f.get_error(), sec::save_callback_failed);
}
}
WHEN("passing a setter returning a default-constructed error") {
f.reset();
auto x = line{{10, 10, 10}, {20, 20, 20}};
auto get = [&x] { return x; };
auto set = [&x](line val) {
x = val;
return error{};
};
THEN("the inspector overrides the state using the setter") {
CHECK(f.apply(get, set));
CHECK_EQ(f.log, baseline);
line default_line{{0, 0, 0}, {0, 0, 0}};
CHECK_EQ(x, default_line);
}
}
WHEN("passing a setter returning an error") {
f.reset();
auto x = line{{10, 10, 10}, {20, 20, 20}};
auto get = [&x] { return x; };
auto set = [](line) { return error{sec::runtime_error}; };
THEN("the inspector overrides the state using the setter") {
CHECK(!f.apply(get, set));
CHECK_EQ(f.get_error(), sec::runtime_error);
}
}
}
}
SCENARIO("load inspectors support fields with a getter and setter") {
std::string baseline = R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_";
GIVEN("a person object") {
WHEN("passing a name setter returning void") {
f.reset();
auto x = person{"John Doe", {}};
auto get_name = [&x] { return x.name; };
auto set_name = [&x](std::string val) { x.name = std::move(val); };
THEN("the inspector overrides the state using the setter") {
CHECK(f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.log, baseline);
CHECK_EQ(x.name, "");
}
}
WHEN("passing a name setter returning true") {
f.reset();
auto x = person{"John Doe", {}};
auto get_name = [&x] { return x.name; };
auto set_name = [&x](std::string val) {
x.name = std::move(val);
return true;
};
THEN("the inspector overrides the state using the setter") {
CHECK(f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.log, baseline);
CHECK_EQ(x.name, "");
}
}
WHEN("passing a name setter returning false") {
f.reset();
auto x = person{"John Doe", {}};
auto get_name = [&x] { return x.name; };
auto set_name = [](std::string&&) { return false; };
THEN("the inspection fails") {
CHECK(!f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.get_error(), sec::field_value_synchronization_failed);
}
}
WHEN("passing a name setter returning a default-constructed error") {
f.reset();
auto x = person{"John Doe", {}};
auto get_name = [&x] { return x.name; };
auto set_name = [&x](std::string val) {
x.name = std::move(val);
return error{};
};
THEN("the inspector overrides the state using the setter") {
CHECK(f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.log, baseline);
CHECK_EQ(x.name, "");
}
}
WHEN("passing a name setter returning an error") {
f.reset();
auto x = person{"John Doe", {}};
auto get_name = [&x] { return x.name; };
auto set_name = [](std::string&&) { return error{sec::runtime_error}; };
THEN("the inspection fails") {
CHECK(!f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.get_error(), sec::runtime_error);
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -6,7 +6,7 @@
#include "caf/save_inspector.hpp"
#include "caf/test/dsl.hpp"
#include "core-test.hpp"
#include <cstdint>
#include <string>
......@@ -719,4 +719,68 @@ begin sequence of size 3
end sequence)_");
}
SCENARIO("save inspectors support apply with a getter and setter") {
GIVEN("a line object") {
line x{{10, 10, 10}, {20, 20, 20}};
WHEN("passing the line to a save inspector with a getter and setter pair") {
auto get = [&x] { return x; };
auto set = [&x](line val) { x = val; };
THEN("the inspector reads the state from the getter") {
CHECK(f.apply(get, set));
CHECK_EQ(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)_");
}
}
}
}
SCENARIO("load inspectors support fields with a getter and setter") {
GIVEN("a person object") {
auto x = person{"John Doe", {}};
WHEN("passing a setter and setter pair for the member name") {
auto get_name = [&x] { return x.name; };
auto set_name = [&x](std::string val) { x.name = std::move(val); };
THEN("the inspector reads the state from the getter") {
CHECK(f.object(x).fields(f.field("name", get_name, set_name),
f.field("phone", x.phone)));
CHECK_EQ(f.log, R"_(
begin object person
begin field name
std::string value
end field
begin optional field phone
end field
end object)_");
}
}
}
}
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