Commit 0913c491 authored by Dominik Charousset's avatar Dominik Charousset

Add support for STL types to the new inspector API

parent ee5ce710
......@@ -46,6 +46,7 @@ template <class> class stream_source;
template <class> class weak_intrusive_ptr;
template <class> struct inspector_access;
template <class> struct inspector_access_traits;
template <class> struct timeout_definition;
template <class> struct type_id;
......
......@@ -19,11 +19,14 @@
#pragma once
#include <tuple>
#include <utility>
#include "caf/allowed_unsafe_message_type.hpp"
#include "caf/detail/inspect.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/inspector_access_type.hpp"
#include "caf/optional.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
......@@ -32,10 +35,10 @@
namespace caf {
// -- utility function objects and functions for load and save inspectors ------
namespace detail {
// -- predicates ---------------------------------------------------------------
/// Utility class for predicates that always return `true`.
struct always_true_t {
template <class... Ts>
......@@ -47,6 +50,8 @@ struct always_true_t {
/// Predicate that always returns `true`.
constexpr auto always_true = always_true_t{};
// -- const conversions --------------------------------------------------------
/// Returns a mutable reference to `x`.
template <class T>
T& as_mutable_ref(T& x) {
......@@ -59,6 +64,8 @@ T& as_mutable_ref(const T& x) {
return const_cast<T&>(x);
}
// -- traits -------------------------------------------------------------------
/// Checks whether the inspector has a `value` overload for `T`.
template <class Inspector, class T>
class is_trivially_inspectable {
......@@ -76,8 +83,168 @@ public:
static constexpr bool value = sfinae_result::value;
};
// -- loading ------------------------------------------------------------------
template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::empty) {
return f.object(x).fields();
}
template <class Inspector, class T>
bool load_value(Inspector& f, T&, inspector_access_type::unsafe) {
f.set_error(sec::unsafe_type);
return false;
}
template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::integral) {
auto tmp = detail::squashed_int_t<T>{0};
if (f.value(tmp)) {
x = static_cast<T>(tmp);
return true;
}
return false;
}
template <class Inspector, class T, size_t N>
bool load_value(Inspector& f, T (&xs)[N], inspector_access_type::array) {
if (!f.begin_tuple(N))
return false;
for (size_t index = 0; index < N; ++index)
if (!inspect_value(f, xs[index]))
return false;
return f.end_tuple();
}
template <class Inspector, class T, size_t... Ns>
bool load_tuple(Inspector& f, T& xs, std::index_sequence<Ns...>) {
return f.begin_tuple(sizeof...(Ns)) //
&& (inspect_value(f, get<Ns>(xs)) && ...) //
&& f.end_tuple();
}
template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::tuple) {
return load_tuple(f, x,
std::make_index_sequence<std::tuple_size<T>::value>{});
}
template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::map) {
x.clear();
size_t size = 0;
if (!f.begin_sequence(size))
return false;
for (size_t i = 0; i < size; ++i) {
auto key = typename T::key_type{};
auto val = typename T::mapped_type{};
if (!(f.begin_tuple(2) && inspect_value(f, key) && inspect_value(f, val)
&& f.end_tuple()))
return false;
auto added = x.emplace(std::move(key), std::move(val)).second;
if (!added) {
f.set_error(make_error(sec::runtime_error, "multiple key definitions"));
return false;
}
}
return f.end_sequence();
}
template <class Inspector, class T>
bool load_value(Inspector& f, T& x, inspector_access_type::list) {
x.clear();
size_t size = 0;
if (!f.begin_sequence(size))
return false;
for (size_t i = 0; i < size; ++i) {
auto val = typename T::value_type{};
if (!inspect_value(f, val))
return false;
x.insert(x.end(), std::move(val));
}
return f.end_sequence();
}
// -- saving -------------------------------------------------------------------
template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::empty) {
return f.object(x).fields();
}
template <class Inspector, class T>
bool save_value(Inspector& f, T&, inspector_access_type::unsafe) {
f.set_error(sec::unsafe_type);
return false;
}
template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::integral) {
auto tmp = static_cast<detail::squashed_int_t<T>>(x);
return f.value(tmp);
}
template <class Inspector, class T, size_t N>
bool save_value(Inspector& f, T (&xs)[N], inspector_access_type::array) {
if (!f.begin_tuple(N))
return false;
for (size_t index = 0; index < N; ++index)
if (!inspect_value(f, xs[index]))
return false;
return f.end_tuple();
}
template <class Inspector, class T, size_t... Ns>
bool save_tuple(Inspector& f, T& xs, std::index_sequence<Ns...>) {
return f.begin_tuple(sizeof...(Ns)) //
&& (inspect_value(f, get<Ns>(xs)) && ...) //
&& f.end_tuple();
}
template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::tuple) {
return save_tuple(f, x,
std::make_index_sequence<std::tuple_size<T>::value>{});
}
template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::map) {
auto size = x.size();
if (!f.begin_sequence(size))
return false;
for (auto& kvp : x) {
if (!(f.begin_tuple(2) && inspect_value(f, as_mutable_ref(kvp.first))
&& inspect_value(f, kvp.second) && f.end_tuple()))
return false;
}
return f.end_sequence();
}
template <class Inspector, class T>
bool save_value(Inspector& f, T& x, inspector_access_type::list) {
auto size = x.size();
if (!f.begin_sequence(size))
return false;
for (auto& val : x)
if (!inspect_value(f, val))
return false;
return f.end_sequence();
}
} // namespace detail
/// Customization point for selecting a default inspector behavior for `T`.
/// @note User-provided `inspect` overloads always override this default.
template <class T>
struct inspector_access_traits {
using tag = decltype(guess_inspector_access_type<T>());
};
/// Expands to `inspector_access_traits::tag{}`.
template <class T>
constexpr auto inspector_access_tag_v =
typename inspector_access_traits<T>::tag{};
/// Calls `f.value(x)` if this is a valid expression,
/// `inspector_access<T>::apply_value(f, x)` otherwise.
template <class Inspector, class T>
......@@ -180,36 +347,39 @@ struct inspector_access_base {
/// Default implementation for @ref inspector_access.
template <class T>
struct default_inspector_access : inspector_access_base<T> {
// -- interface functions ----------------------------------------------------
/// Applies `x` as an object to `f`.
template <class Inspector>
[[nodiscard]] static bool apply_object(Inspector& f, T& x) {
using detail::inspect;
// User-provided `inspect` overloads come first.
// Dispatch to user-provided `inspect` overload or assume a trivial type.
if constexpr (detail::is_inspectable<Inspector, T>::value) {
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);
} else if constexpr (Inspector::is_loading) {
return load_object(f, x);
} else if constexpr (std::is_empty<T>::value) {
return f.object(x).fields();
} else {
return save_object(f, x);
return f.object(x).fields(f.field("value", x));
}
}
/// Applies `x` as a single value to `f`.
template <class Inspector>
[[nodiscard]] static bool apply_value(Inspector& f, T& x) {
return apply_object(f, x);
if constexpr (detail::is_inspectable<Inspector, T>::value)
return apply_object(f, x);
else if constexpr (Inspector::is_loading)
return detail::load_value(f, x, inspector_access_tag_v<T>);
else
return detail::save_value(f, x, inspector_access_tag_v<T>);
}
template <class Inspector>
[[nodiscard]] static bool load_object(Inspector& f, T& x) {
if constexpr (std::is_arithmetic<T>::value) {
return f.object(x).fields(f.field("value", x));
}
}
// -- deprecated API ---------------------------------------------------------
template <class Inspector>
[[deprecated("inspect() overloads should return bool")]] //
......@@ -229,6 +399,7 @@ struct default_inspector_access : inspector_access_base<T> {
template <class T>
struct inspector_access : default_inspector_access<T> {};
/*
template <class... Ts>
struct inspector_access<std::tuple<Ts...>> {
// -- boilerplate ------------------------------------------------------------
......@@ -346,6 +517,7 @@ struct inspector_access<std::tuple<Ts...>> {
tuple_indexes{});
}
};
*/
template <class T>
struct inspector_access<optional<T>> {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 "caf/detail/type_traits.hpp"
namespace caf {
/// Wraps tag types for static dispatching.
struct inspector_access_type {
/// Flags stateless message types.
struct empty {};
/// Flags allowed unsafe message types.
struct unsafe {};
/// Flags builtin integral types.
struct integral {};
/// Flags type with user-defined `inspect` overloads.
struct inspect {};
/// Flags native C array types.
struct array {};
/// Flags types with `std::tuple`-like API.
struct tuple {};
/// Flags types with `std::map`-like API.
struct map {};
/// Flags types with `std::vector`-like API.
struct list {};
};
/// @relates inspector_access_type
template <class T>
constexpr auto guess_inspector_access_type() {
if constexpr (std::is_empty<T>::value) {
return inspector_access_type::empty{};
} else if constexpr (is_allowed_unsafe_message_type_v<T>) {
return inspector_access_type::unsafe{};
} else if constexpr (std::is_integral<T>::value) {
return inspector_access_type::integral{};
} else if constexpr (std::is_array<T>::value) {
return inspector_access_type::array{};
} else if constexpr (detail::is_stl_tuple_type_v<T>) {
return inspector_access_type::tuple{};
} else if constexpr (detail::is_map_like_v<T>) {
return inspector_access_type::map{};
} else {
static_assert(detail::is_list_like_v<T>);
return inspector_access_type::list{};
}
}
} // namespace caf
......@@ -152,6 +152,8 @@ enum class sec : uint8_t {
field_value_synchronization_failed,
/// Deserialization failed, because the source announced an invalid type.
invalid_field_type,
/// Serialization failed because a type was flagged as unsafe message type.
unsafe_type,
};
/// @relates sec
......
......@@ -22,6 +22,7 @@
#include "caf/test/dsl.hpp"
#include <array>
#include <cstdint>
#include <string>
#include <vector>
......@@ -153,6 +154,29 @@ bool inspect(Inspector& f, fallback_dummy_message& x) {
return f.object(x).fields(f.field("content", x.content).fallback(42.0));
}
struct basics {
static inline string_view tname = "basics";
struct tag {
static inline string_view tname = "tag";
};
tag v1;
int32_t v2;
int32_t v3[4];
dummy_message v4[2];
std::array<int32_t, 2> v5;
std::tuple<int32_t, dummy_message> v6;
std::map<std::string, int32_t> v7;
std::vector<std::list<std::pair<std::string, std::array<int32_t, 3>>>> v8;
};
template <class Inspector>
bool inspect(Inspector& f, basics& x) {
return f.object(x).fields(f.field("v1", x.v1), f.field("v2", x.v2),
f.field("v3", x.v3), f.field("v4", x.v4),
f.field("v5", x.v5), f.field("v6", x.v6),
f.field("v7", x.v7), f.field("v8", x.v8));
}
struct testee : load_inspector {
std::string log;
......@@ -253,6 +277,22 @@ struct testee : load_inspector {
return ok;
}
bool begin_sequence(size_t& size) {
size = 0;
new_line();
indent += 2;
log += "begin sequence of size ";
log += std::to_string(size);
return ok;
}
bool end_sequence() {
indent -= 2;
new_line();
log += "end sequence";
return ok;
}
template <class T>
std::enable_if_t<std::is_arithmetic<T>::value, bool> value(T& x) {
new_line();
......@@ -516,4 +556,65 @@ begin object nasty
end object)_");
}
CAF_TEST(load inspectors support all basic STL types) {
basics x;
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object basics
begin field v1
begin object tag
end object
end field
begin field v2
int32_t value
end field
begin field v3
begin tuple of size 4
int32_t value
int32_t value
int32_t value
int32_t value
end tuple
end field
begin field v4
begin tuple of size 2
begin object dummy_message
begin variant field content
std::string value
end field
end object
begin object dummy_message
begin variant field content
std::string value
end field
end object
end tuple
end field
begin field v5
begin tuple of size 2
int32_t value
int32_t value
end tuple
end field
begin field v6
begin tuple of size 2
int32_t value
begin object dummy_message
begin variant field content
std::string value
end field
end object
end tuple
end field
begin field v7
begin sequence of size 0
end sequence
end field
begin field v8
begin sequence of size 0
end sequence
end field
end object)_");
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -127,6 +127,40 @@ bool inspect(Inspector& f, foobar& x) {
f.field("bar", get_bar, set_bar));
}
struct dummy_message {
static inline string_view tname = "dummy_message";
variant<std::string, double> content;
};
template <class Inspector>
bool inspect(Inspector& f, dummy_message& x) {
return f.object(x).fields(f.field("content", x.content));
}
struct basics {
static inline string_view tname = "basics";
struct tag {
static inline string_view tname = "tag";
};
tag v1;
int32_t v2;
int32_t v3[4];
dummy_message v4[2];
std::array<int32_t, 2> v5;
std::tuple<int32_t, dummy_message> v6;
std::map<std::string, int32_t> v7;
std::vector<std::list<std::pair<std::string, std::array<int32_t, 3>>>> v8;
};
template <class Inspector>
bool inspect(Inspector& f, basics& x) {
return f.object(x).fields(f.field("v1", x.v1), f.field("v2", x.v2),
f.field("v3", x.v3), f.field("v4", x.v4),
f.field("v5", x.v5), f.field("v6", x.v6),
f.field("v7", x.v7), f.field("v8", x.v8));
}
struct testee : save_inspector {
std::string log;
......@@ -227,6 +261,21 @@ struct testee : save_inspector {
return ok;
}
bool begin_sequence(size_t size) {
new_line();
indent += 2;
log += "begin sequence of size ";
log += std::to_string(size);
return ok;
}
bool end_sequence() {
indent -= 2;
new_line();
log += "end sequence";
return ok;
}
template <class T>
std::enable_if_t<std::is_arithmetic<T>::value, bool> value(const T&) {
new_line();
......@@ -496,4 +545,118 @@ begin object nasty
end object)_");
}
CAF_TEST(save inspectors support all basic STL types) {
basics x;
x.v7.emplace("one", 1);
x.v7.emplace("two", 2);
x.v7.emplace("three", 3);
using v8_list = decltype(x.v8);
using v8_nested_list = v8_list::value_type;
using array_3i = std::array<int32_t, 3>;
v8_nested_list v8_1;
v8_1.emplace_back("hello", array_3i{{1, 2, 3}});
v8_1.emplace_back("world", array_3i{{2, 3, 4}});
v8_nested_list v8_2;
v8_2.emplace_back("foo", array_3i{{0, 0, 0}});
x.v8.emplace_back(std::move(v8_1));
x.v8.emplace_back(std::move(v8_2));
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object basics
begin field v1
begin object tag
end object
end field
begin field v2
int32_t value
end field
begin field v3
begin tuple of size 4
int32_t value
int32_t value
int32_t value
int32_t value
end tuple
end field
begin field v4
begin tuple of size 2
begin object dummy_message
begin variant field content
std::string value
end field
end object
begin object dummy_message
begin variant field content
std::string value
end field
end object
end tuple
end field
begin field v5
begin tuple of size 2
int32_t value
int32_t value
end tuple
end field
begin field v6
begin tuple of size 2
int32_t value
begin object dummy_message
begin variant field content
std::string value
end field
end object
end tuple
end field
begin field v7
begin sequence of size 3
begin tuple of size 2
std::string value
int32_t value
end tuple
begin tuple of size 2
std::string value
int32_t value
end tuple
begin tuple of size 2
std::string value
int32_t value
end tuple
end sequence
end field
begin field v8
begin sequence of size 2
begin sequence of size 2
begin tuple of size 2
std::string value
begin tuple of size 3
int32_t value
int32_t value
int32_t value
end tuple
end tuple
begin tuple of size 2
std::string value
begin tuple of size 3
int32_t value
int32_t value
int32_t value
end tuple
end tuple
end sequence
begin sequence of size 1
begin tuple of size 2
std::string value
begin tuple of size 3
int32_t value
int32_t value
int32_t value
end tuple
end tuple
end sequence
end sequence
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