Unverified Commit 3f59e8d4 authored by Noir's avatar Noir Committed by GitHub

Merge pull request #1192

Add new get_as and get_or utility functions
parents f5c450a6 3e1ae2ba
......@@ -35,7 +35,8 @@
namespace caf {
/// Deserializes objects from sequence of bytes.
/// Deserializes C++ objects from sequence of bytes. Does not perform run-time
/// type checks.
class CAF_CORE_EXPORT binary_deserializer
: public load_inspector_base<binary_deserializer> {
public:
......@@ -112,7 +113,7 @@ public:
bool fetch_next_object_type(type_id_t& type) noexcept;
constexpr bool begin_object(string_view) noexcept {
constexpr bool begin_object(type_id_t, string_view) noexcept {
return true;
}
......
......@@ -33,7 +33,10 @@
namespace caf {
/// Serializes objects into a sequence of bytes.
/// Serializes C++ objects into a sequence of bytes.
/// @note The binary data format may change between CAF versions and does not
/// perform any type checking at run-time. Thus the output of this
/// serializer is unsuitable for persistence layers.
class CAF_CORE_EXPORT binary_serializer
: public save_inspector_base<binary_serializer> {
public:
......@@ -95,9 +98,7 @@ public:
// -- interface functions ----------------------------------------------------
bool inject_next_object_type(type_id_t type);
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) noexcept {
return true;
}
......
......@@ -19,6 +19,8 @@
#pragma once
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <iterator>
......@@ -36,8 +38,10 @@
#include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/dictionary.hpp"
#include "caf/expected.hpp"
#include "caf/fwd.hpp"
#include "caf/inspector_access.hpp"
#include "caf/inspector_access_type.hpp"
#include "caf/optional.hpp"
#include "caf/raise_error.hpp"
#include "caf/string_algorithms.hpp"
......@@ -45,6 +49,7 @@
#include "caf/sum_type.hpp"
#include "caf/sum_type_access.hpp"
#include "caf/sum_type_token.hpp"
#include "caf/timespan.hpp"
#include "caf/timestamp.hpp"
#include "caf/uri.hpp"
#include "caf/variant.hpp"
......@@ -56,6 +61,11 @@ namespace caf {
/// contain lists of themselves.
class CAF_CORE_EXPORT config_value {
public:
// -- friends ----------------------------------------------------------------
template <class T>
friend expected<T> get_as(const config_value& value);
// -- member types -----------------------------------------------------------
using integer = int64_t;
......@@ -64,16 +74,14 @@ public:
using real = double;
using timespan = caf::timespan;
using string = std::string;
using list = std::vector<config_value>;
using dictionary = caf::dictionary<config_value>;
using types = detail::type_list<integer, boolean, real, timespan, uri, string,
list, dictionary>;
using types = detail::type_list<none_t, integer, boolean, real, timespan, uri,
string, list, dictionary>;
using variant_type = detail::tl_apply_t<types, variant>;
......@@ -106,23 +114,34 @@ public:
// -- parsing ----------------------------------------------------------------
/// Tries to parse a value from `str`.
/// Tries to parse a value from given characters.
static expected<config_value> parse(string_view::iterator first,
string_view::iterator last);
/// Tries to parse a value from `str`.
static expected<config_value> parse(string_view str);
/// Tries to parse a config value (list) from `str` and to convert it to an
/// allowed input message type for `Handle`.
template <class Handle>
static optional<message> parse_msg(string_view str, const Handle&) {
auto allowed = Handle::allowed_inputs();
return parse_msg_impl(str, allowed);
}
// -- properties -------------------------------------------------------------
/// Converts the value to a list with one element. Does nothing if the value
/// already is a list.
/// Converts the value to a list with one element (unless the config value
/// holds `nullptr`). Does nothing if the value already is a list.
void convert_to_list();
/// Returns the value as a list, converting it to one if needed.
list& as_list();
/// Returns the value as a dictionary, converting it to one if needed.
/// Returns the value as a dictionary, converting it to one if needed. The
/// only data structure that CAF can convert to a dictionary is a list of
/// lists, where each nested list contains exactly two elements (key and
/// value). In all other cases, the conversion results in an empty dictionary.
dictionary& as_dictionary();
/// Appends `x` to a list. Converts this config value to a list first by
......@@ -152,13 +171,80 @@ public:
return &data_;
}
/// Checks whether this config value is not null.
explicit operator bool() const noexcept {
return data_.index() != 0;
}
/// Checks whether this config value is null.
bool operator!() const noexcept {
return data_.index() == 0;
}
// -- utility ----------------------------------------------------------------
/// @private
type_id_t type_id() const noexcept;
/// @private
error_code<sec> default_construct(type_id_t);
/// @private
expected<bool> to_boolean() const;
/// @private
expected<integer> to_integer() const;
/// @private
expected<real> to_real() const;
/// @private
expected<timespan> to_timespan() const;
/// @private
expected<list> to_list() const;
/// @private
expected<dictionary> to_dictionary() const;
/// @private
bool can_convert_to_dictionary() const;
/// @private
template <class T, class Token>
expected<T> convert_to(Token token) const {
auto tmp = T{};
config_value_reader reader{this};
if (detail::load(reader, tmp, token))
return {std::move(tmp)};
else
return {reader.move_error()};
}
/// @experimental
template <class T>
error assign(const T& x) {
config_value_writer writer{this};
if (detail::save(writer, x))
return {};
else
return {writer.move_error()};
}
private:
// -- properties -------------------------------------------------------------
static const char* type_name_at_index(size_t index) noexcept;
static optional<message>
parse_msg_impl(string_view str, span<const type_id_list> allowed_types);
// -- auto conversion of related types ---------------------------------------
void set(none_t) {
data_ = none;
}
void set(bool x) {
data_ = x;
}
......@@ -217,6 +303,9 @@ private:
variant_type data_;
};
/// @relates config_value
CAF_CORE_EXPORT std::string to_string(const config_value& x);
// -- convenience constants ----------------------------------------------------
/// Type of the `top_level_cli_parsing` constant.
......@@ -233,6 +322,231 @@ using nested_cli_parsing_t = std::true_type;
/// `parse_cli`.
constexpr auto nested_cli_parsing = nested_cli_parsing_t{};
// -- conversion via get_as ----------------------------------------------------
template <class T>
expected<T> get_as(const config_value& value);
template <class T>
expected<T> get_as(const config_value&, inspector_access_type::none) {
static_assert(detail::always_false_v<T>,
"cannot convert to T: found no a suitable inspect overload");
}
template <class T>
expected<T> get_as(const config_value&, inspector_access_type::unsafe) {
static_assert(detail::always_false_v<T>,
"cannot convert types that are tagged as unsafe");
}
template <class T>
expected<T>
get_as(const config_value& x, inspector_access_type::specialization token) {
return x.convert_to<T>(token);
}
template <class T>
expected<T>
get_as(const config_value& x, inspector_access_type::inspect token) {
return x.convert_to<T>(token);
}
template <class T>
expected<T>
get_as(const config_value& x, inspector_access_type::builtin_inspect token) {
return x.convert_to<T>(token);
}
template <class T>
expected<T> get_as(const config_value& x, inspector_access_type::builtin) {
if constexpr (std::is_same<T, std::string>::value) {
return to_string(x);
} else if constexpr (std::is_same<T, bool>::value) {
return x.to_boolean();
} else if constexpr (std::is_integral<T>::value) {
if (auto result = x.to_integer()) {
if (detail::bounds_checker<T>::check(*result))
return *result;
else
return make_error(sec::conversion_failed, "narrowing error");
} else {
return std::move(result.error());
}
} else if constexpr (std::is_floating_point<T>::value) {
if (auto result = x.to_real()) {
if constexpr (sizeof(T) >= sizeof(config_value::real)) {
return *result;
} else {
auto narrowed = static_cast<T>(*result);
if (!std::isfinite(*result) || std::isfinite(narrowed)) {
return narrowed;
} else {
return make_error(sec::conversion_failed, "narrowing error");
}
}
} else {
return std::move(result.error());
}
} else {
static_assert(detail::always_false_v<T>,
"sorry, this conversion is not implemented yet");
}
}
template <class T>
expected<T> get_as(const config_value& x, inspector_access_type::empty) {
// Technically, we could always simply return T{} here. However,
// *semantically* it only makes sense to converts dictionaries to objects. So
// at least we check for this condition here.
if (x.can_convert_to_dictionary())
return T{};
else
return make_error(sec::conversion_failed,
"invalid element type: expected a dictionary");
}
template <class T, size_t... Is>
expected<T>
get_as_tuple(const config_value::list& x, std::index_sequence<Is...>) {
auto boxed = std::make_tuple(get_as<std::tuple_element_t<Is, T>>(x[Is])...);
if ((get<Is>(boxed) && ...))
return T{std::move(*get<Is>(boxed))...};
else
return make_error(sec::conversion_failed, "invalid element types");
}
template <class T>
expected<T> get_as(const config_value& x, inspector_access_type::tuple) {
static_assert(!std::is_array<T>::value,
"cannot return an array from a function");
if (auto wrapped_values = x.to_list()) {
static constexpr size_t n = std::tuple_size<T>::value;
if (wrapped_values->size() == n)
return get_as_tuple<T>(*wrapped_values, std::make_index_sequence<n>{});
else
return make_error(sec::conversion_failed, "wrong number of arguments");
} else {
return {std::move(wrapped_values.error())};
}
}
template <class T>
expected<T> get_as(const config_value& x, inspector_access_type::map) {
using key_type = typename T::key_type;
using mapped_type = typename T::mapped_type;
T result;
if (auto dict = x.to_dictionary()) {
for (auto&& [string_key, wrapped_value] : *dict) {
config_value wrapped_key{std::move(string_key)};
if (auto key = get_as<key_type>(wrapped_key)) {
if (auto val = get_as<mapped_type>(wrapped_value)) {
if (!result.emplace(std::move(*key), std::move(*val)).second) {
return make_error(sec::conversion_failed,
"ambiguous mapping of keys to key_type");
}
} else {
return make_error(sec::conversion_failed,
"failed to convert values to mapped_type");
}
} else {
return make_error(sec::conversion_failed,
"failed to convert keys to key_type");
}
}
return {std::move(result)};
} else {
return {std::move(dict.error())};
}
}
template <class T>
expected<T> get_as(const config_value& x, inspector_access_type::list) {
if (auto wrapped_values = x.to_list()) {
using value_type = typename T::value_type;
T result;
result.reserve(wrapped_values->size());
for (const auto& wrapped_value : *wrapped_values)
if (auto maybe_value = get_as<value_type>(wrapped_value))
result.emplace_back(std::move(*maybe_value));
else
return {std::move(maybe_value.error())};
return {std::move(result)};
} else {
return {std::move(wrapped_values.error())};
}
}
/// Converts a @ref config_value to builtin types or user-defined types that
/// opted into the type inspection API.
/// @relates config_value
template <class T>
expected<T> get_as(const config_value& value) {
if constexpr (std::is_same<T, timespan>::value) {
return value.to_timespan();
} else if constexpr (std::is_same<T, config_value::list>::value) {
return value.to_list();
} else if constexpr (std::is_same<T, config_value::dictionary>::value) {
return value.to_dictionary();
} else {
auto token = inspect_access_type<config_value_reader, T>();
return get_as<T>(value, token);
}
}
// -- conversion via get_or ----------------------------------------------------
/// Customization point for configuring automatic mappings from default value
/// types to deduced types. For example, `get_or(value, "foo"sv)` must return a
/// `string` rather than a `string_view`. However, user-defined overloads *must
/// not* specialize this class for any type from the namespaces `std` or `caf`.
template <class T>
struct get_or_deduction_guide {
using value_type = T;
template <class V>
static decltype(auto) convert(V&& x) {
return std::forward<V>(x);
}
};
template <>
struct get_or_deduction_guide<string_view> {
using value_type = std::string;
static value_type convert(string_view str) {
return {str.begin(), str.end()};
}
};
template <class T>
struct get_or_deduction_guide<span<T>> {
using value_type = std::vector<T>;
static value_type convert(span<T> buf) {
return {buf.begin(), buf.end()};
}
};
/// Configures @ref get_or to uses the @ref get_or_deduction_guide.
struct get_or_auto_deduce {};
/// Converts a @ref config_value to `To` or returns `fallback` if the conversion
/// fails.
/// @relates config_value
template <class To = get_or_auto_deduce, class Fallback>
auto get_or(const config_value& x, Fallback&& fallback) {
if constexpr (std::is_same<To, get_or_auto_deduce>::value) {
using guide = get_or_deduction_guide<std::decay_t<Fallback>>;
using value_type = typename guide::value_type;
if (auto val = get_as<value_type>(x))
return std::move(*val);
else
return guide::convert(std::forward<Fallback>(fallback));
} else {
if (auto val = get_as<To>(x))
return std::move(*val);
else
return To{std::forward<Fallback>(fallback)};
}
}
// -- SumType-like access ------------------------------------------------------
template <class T>
......@@ -900,6 +1214,26 @@ struct sum_type_access<config_value> {
}
};
/// @relates config_value
inline bool operator==(const config_value& x, std::nullptr_t) noexcept {
return x.get_data().index() == 0;
}
/// @relates config_value
inline bool operator==(std::nullptr_t, const config_value& x) noexcept {
return x.get_data().index() == 0;
}
/// @relates config_value
inline bool operator!=(const config_value& x, std::nullptr_t) noexcept {
return x.get_data().index() != 0;
}
/// @relates config_value
inline bool operator!=(std::nullptr_t, const config_value& x) noexcept {
return x.get_data().index() != 0;
}
/// @relates config_value
CAF_CORE_EXPORT bool operator<(const config_value& x, const config_value& y);
......@@ -916,9 +1250,6 @@ inline bool operator!=(const config_value& x, const config_value& y) {
return !(x == y);
}
/// @relates config_value
CAF_CORE_EXPORT std::string to_string(const config_value& x);
/// @relates config_value
CAF_CORE_EXPORT std::ostream& operator<<(std::ostream& out,
const config_value& x);
......@@ -998,10 +1329,11 @@ struct variant_inspector_traits<config_value> {
using value_type = config_value;
static constexpr type_id_t allowed_types[] = {
type_id_v<none_t>,
type_id_v<config_value::integer>,
type_id_v<config_value::boolean>,
type_id_v<config_value::real>,
type_id_v<config_value::timespan>,
type_id_v<timespan>,
type_id_v<uri>,
type_id_v<config_value::string>,
type_id_v<config_value::list>,
......@@ -1019,7 +1351,7 @@ struct variant_inspector_traits<config_value> {
template <class U>
static void assign(value_type& x, U&& value) {
x.get_data() = std::move(value);
x = std::move(value);
}
template <class F>
......@@ -1027,6 +1359,11 @@ struct variant_inspector_traits<config_value> {
switch (type) {
default:
return false;
case type_id_v<none_t>: {
auto tmp = config_value{};
continuation(tmp);
return true;
}
case type_id_v<config_value::integer>: {
auto tmp = config_value::integer{};
continuation(tmp);
......@@ -1042,8 +1379,8 @@ struct variant_inspector_traits<config_value> {
continuation(tmp);
return true;
}
case type_id_v<config_value::timespan>: {
auto tmp = config_value::timespan{};
case type_id_v<timespan>: {
auto tmp = timespan{};
continuation(tmp);
return true;
}
......
......@@ -23,6 +23,7 @@
#include "caf/dictionary.hpp"
#include "caf/fwd.hpp"
#include <memory>
#include <stack>
#include <vector>
......@@ -85,6 +86,10 @@ public:
~config_value_reader() override;
config_value_reader(const config_value_reader&) = delete;
config_value_reader& operator=(const config_value_reader&) = delete;
// -- stack access -----------------------------------------------------------
value_type& top() {
......@@ -99,7 +104,7 @@ public:
bool fetch_next_object_type(type_id_t& type) override;
bool begin_object(string_view name) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
......@@ -166,9 +171,14 @@ public:
bool value(span<byte> x) override;
private:
// Sets `type` according to the `@type` field in `obj` or to the type ID of
// `settings` as fallback if no such field exists.
bool fetch_object_type(const settings* obj, type_id_t& type);
stack_type st_;
// Stores on-the-fly converted values.
std::vector<std::unique_ptr<config_value>> scratch_space_;
};
} // namespace caf
......@@ -68,9 +68,7 @@ public:
// -- interface functions ----------------------------------------------------
bool inject_next_object_type(type_id_t type) override;
bool begin_object(string_view name) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
......@@ -140,7 +138,6 @@ private:
bool push(config_value&& x);
stack_type st_;
string_view type_hint_;
};
} // namespace caf
......@@ -62,13 +62,15 @@ public:
// -- interface functions ----------------------------------------------------
/// Reads run-time-type information for the next object. Requires that the
/// @ref serializer provided this information via
/// @ref serializer::inject_next_object_type.
/// Reads run-time-type information for the next object if available.
virtual bool fetch_next_object_type(type_id_t& type) = 0;
/// Begins processing of an object.
virtual bool begin_object(string_view type) = 0;
/// Begins processing of an object, may perform a type check depending on the
/// data format.
/// @param type 16-bit ID for known types, @ref invalid_type_id otherwise.
/// @param pretty_class_name Either the output of @ref type_name_or_anonymous
/// or the optionally defined pretty name.
virtual bool begin_object(type_id_t type, string_view pretty_class_name) = 0;
/// Ends processing of an object.
virtual bool end_object() = 0;
......
......@@ -33,6 +33,13 @@ struct bounds_checker {
}
};
template <>
struct bounds_checker<int64_t, false> {
static constexpr bool check(int64_t) noexcept {
return true;
}
};
template <class To>
struct bounds_checker<To, true> {
static constexpr bool check(int64_t x) noexcept {
......
......@@ -222,8 +222,15 @@ template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]);
template <class T>
enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
struct require_opening_char_t {};
constexpr auto require_opening_char = require_opening_char_t{};
struct allow_omitting_opening_char_t {};
constexpr auto allow_omitting_opening_char = allow_omitting_opening_char_t{};
template <class T, class Policy = allow_omitting_opening_char_t>
enable_if_tt<is_iterable<T>>
parse(string_parser_state& ps, T& xs, Policy = {}) {
using value_type = deconst_kvp_t<typename T::value_type>;
static constexpr auto is_map_type = is_pair<value_type>::value;
static constexpr auto opening_char = is_map_type ? '{' : '[';
......@@ -252,19 +259,23 @@ enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
}
return;
}
// An empty string simply results in an empty list/map.
if (ps.at_end())
return;
// List/map without [] or {}.
do {
char char_blacklist[] = {',', '\0'};
value_type tmp;
parse_element(ps, tmp, char_blacklist);
if (ps.code > pec::trailing_character)
if constexpr (std::is_same<Policy, require_opening_char_t>::value) {
ps.code = pec::unexpected_character;
} else {
// An empty string simply results in an empty list/map.
if (ps.at_end())
return;
*out++ = std::move(tmp);
} while (ps.consume(','));
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
// List/map without [] or {}.
do {
char char_blacklist[] = {',', '\0'};
value_type tmp;
parse_element(ps, tmp, char_blacklist);
if (ps.code > pec::trailing_character)
return;
*out++ = std::move(tmp);
} while (ps.consume(','));
ps.code = ps.at_end() ? pec::success : pec::trailing_character;
}
}
template <class T>
......@@ -306,4 +317,11 @@ auto parse(string_view str, T& x) {
return parse_result(ps, str);
}
template <class T, class Policy>
auto parse(string_view str, T& x, Policy policy) {
string_parser_state ps{str.begin(), str.end()};
parse(ps, x, policy);
return parse_result(ps, str);
}
} // namespace caf::detail
......@@ -18,6 +18,7 @@
#pragma once
#include "caf/none.hpp"
#include "caf/string_view.hpp"
#include <chrono>
......@@ -60,6 +61,13 @@ void print_escaped(Buffer& buf, string_view str) {
buf.push_back('"');
}
template <class Buffer>
void print(Buffer& buf, none_t) {
using namespace caf::literals;
auto str = "null"_sv;
buf.insert(buf.end(), str.begin(), str.end());
}
template <class Buffer>
void print(Buffer& buf, bool x) {
using namespace caf::literals;
......
......@@ -32,9 +32,7 @@ public:
size_t result = 0;
bool inject_next_object_type(type_id_t type) override;
bool begin_object(string_view) override;
bool begin_object(type_id_t, string_view) override;
bool end_object() override;
......
......@@ -58,7 +58,7 @@ public:
// -- serializer interface ---------------------------------------------------
bool begin_object(string_view name);
bool begin_object(type_id_t, string_view name);
bool end_object();
......
......@@ -330,14 +330,6 @@ private:
map_type xs_;
};
// -- free functions -----------------------------------------------------------
// @relates dictionary
template <class T>
std::string to_string(const dictionary<T>& xs) {
return deep_to_string(xs.container());
}
// -- operators ----------------------------------------------------------------
// @relates dictionary
......@@ -376,10 +368,4 @@ bool operator>=(const dictionary<T>& xs, const dictionary<T>& ys) {
return xs.container() >= ys.container();
}
// @relates dictionary
template <class T>
std::ostream& operator<<(std::ostream& out, const dictionary<T>& xs) {
return out << to_string(xs);
}
} // namespace caf
......@@ -53,7 +53,7 @@ public:
return false;
}
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -49,7 +49,7 @@ public:
return false;
}
constexpr bool begin_object(string_view) {
constexpr bool begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -275,6 +275,7 @@ public:
template <class Inspector, class LoadCallback>
struct object_with_load_callback_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
LoadCallback load_callback;
......@@ -282,7 +283,7 @@ public:
template <class... Fields>
bool fields(Fields&&... fs) {
using load_callback_result = decltype(load_callback());
if (!(f->begin_object(object_name) && (fs(*f) && ...)))
if (!(f->begin_object(object_type, object_name) && (fs(*f) && ...)))
return false;
if constexpr (std::is_same<load_callback_result, bool>::value) {
if (!load_callback()) {
......@@ -299,7 +300,7 @@ public:
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -310,16 +311,19 @@ public:
template <class Inspector>
struct object_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
return f->begin_object(object_type, object_name) //
&& (fs(*f) && ...) //
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -330,6 +334,7 @@ public:
template <class F>
auto on_load(F fun) && {
return object_with_load_callback_t<Inspector, F>{
object_type,
object_name,
f,
std::move(fun),
......
......@@ -38,7 +38,8 @@ public:
template <class T>
constexpr auto object(T&) noexcept {
return super::object_t<Subtype>{type_name_or_anonymous<T>(), dptr()};
return super::object_t<Subtype>{type_id_or_invalid<T>(),
type_name_or_anonymous<T>(), dptr()};
}
template <class T>
......
......@@ -171,6 +171,7 @@ public:
template <class Inspector, class SaveCallback>
struct object_with_save_callback_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
SaveCallback save_callback;
......@@ -178,7 +179,7 @@ public:
template <class... Fields>
bool fields(Fields&&... fs) {
using save_callback_result = decltype(save_callback());
if (!(f->begin_object(object_name) && (fs(*f) && ...)))
if (!(f->begin_object(object_type, object_name) && (fs(*f) && ...)))
return false;
if constexpr (std::is_same<save_callback_result, bool>::value) {
if (!save_callback()) {
......@@ -206,16 +207,19 @@ public:
template <class Inspector>
struct object_t {
type_id_t object_type;
string_view object_name;
Inspector* f;
template <class... Fields>
bool fields(Fields&&... fs) {
return f->begin_object(object_name) && (fs(*f) && ...) && f->end_object();
return f->begin_object(object_type, object_name) //
&& (fs(*f) && ...) //
&& f->end_object();
}
auto pretty_name(string_view name) && {
return object_t{name, f};
return object_t{object_type, name, f};
}
template <class F>
......@@ -226,6 +230,7 @@ public:
template <class F>
auto on_save(F fun) && {
return object_with_save_callback_t<Inspector, F>{
object_type,
object_name,
f,
std::move(fun),
......
......@@ -34,7 +34,8 @@ public:
template <class T>
constexpr auto object(T&) noexcept {
return super::object_t<Subtype>{type_name_or_anonymous<T>(), dptr()};
return super::object_t<Subtype>{type_id_or_invalid<T>(),
type_name_or_anonymous<T>(), dptr()};
}
template <class T>
......
......@@ -164,6 +164,12 @@ enum class sec : uint8_t {
conversion_failed,
/// A network connection was closed by the remote side.
connection_closed,
/// An operation failed because run-time type information diverged from the
/// expected type.
type_clash,
/// An operation failed because the callee does not implement this
/// functionality.
unsupported_operation,
};
/// @relates sec
......
......@@ -66,15 +66,10 @@ public:
// -- interface functions ----------------------------------------------------
/// Injects run-time-type information for the *next* object, i.e., causes the
/// next call to `begin_object` to write additional meta information. Allows a
/// @ref deserializer to retrieve the type for the next object via
/// @ref deserializer::fetch_next_object_type.
virtual bool inject_next_object_type(type_id_t type) = 0;
/// Begins processing of an object. Saves the type information
/// to the underlying storage.
virtual bool begin_object(string_view name) = 0;
/// Begins processing of an object. May save the type information to the
/// underlying storage to allow a @ref deserializer to retrieve and check the
/// type information for data formats that provide deserialization.
virtual bool begin_object(type_id_t type, string_view name) = 0;
/// Ends processing of an object.
virtual bool end_object() = 0;
......
......@@ -33,6 +33,9 @@ namespace caf {
/// @relates config_value
using settings = dictionary<config_value>;
/// @relates config_value
CAF_CORE_EXPORT std::string to_string(const settings& xs);
/// Tries to retrieve the value associated to `name` from `xs`.
/// @relates config_value
CAF_CORE_EXPORT const config_value*
......
......@@ -108,6 +108,15 @@ string_view type_name_or_anonymous() {
return "anonymous";
}
/// Returns `type_id_v<T>` if available, `invalid_type_id` otherwise.
template <class T>
type_id_t type_id_or_invalid() {
if constexpr (detail::is_complete<type_id<T>>)
return type_id<T>::value;
else
return invalid_type_id;
}
/// Returns the type name of given `type` or an empty string if `type` is an
/// invalid ID.
CAF_CORE_EXPORT string_view query_type_name(type_id_t type);
......
......@@ -88,6 +88,10 @@ public:
return begin() + size();
}
/// Returns the number of bytes that a buffer needs to allocate for storing a
/// type-erased tuple for the element types stored in this list.
size_t data_size() const noexcept;
private:
pointer data_;
};
......@@ -110,3 +114,22 @@ constexpr type_id_list make_type_id_list() {
CAF_CORE_EXPORT std::string to_string(type_id_list xs);
} // namespace caf
namespace caf::detail {
template <class F>
struct argument_type_id_list_factory;
template <class R, class... Ts>
struct argument_type_id_list_factory<R(Ts...)> {
static type_id_list make() {
return make_type_id_list<Ts...>();
}
};
template <class F>
type_id_list make_argument_type_id_list() {
return argument_type_id_list_factory<F>::make();
}
} // namespace caf::detail
......@@ -31,6 +31,7 @@
#include "caf/make_actor.hpp"
#include "caf/replies_to.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/type_id_list.hpp"
#include "caf/typed_actor_view_base.hpp"
#include "caf/typed_behavior.hpp"
#include "caf/typed_response_promise.hpp"
......@@ -246,6 +247,10 @@ public:
x.ptr_.reset();
}
static std::array<type_id_list, sizeof...(Sigs)> allowed_inputs() {
return {{detail::make_argument_type_id_list<Sigs>()...}};
}
/// @endcond
private:
......
......@@ -35,19 +35,23 @@ namespace {
template <class T>
bool int_value(binary_deserializer& source, T& x) {
auto tmp = std::make_unsigned_t<T>{};
if (!source.value(as_writable_bytes(make_span(&tmp, 1))))
if (source.value(as_writable_bytes(make_span(&tmp, 1)))) {
x = static_cast<T>(detail::from_network_order(tmp));
return true;
} else {
return false;
x = static_cast<T>(detail::from_network_order(tmp));
return true;
}
}
template <class T>
bool float_value(binary_deserializer& source, T& x) {
auto tmp = typename detail::ieee_754_trait<T>::packed_type{};
if (!int_value(source, tmp))
if (int_value(source, tmp)) {
x = detail::unpack754(tmp);
return true;
} else {
return false;
x = detail::unpack754(tmp);
return true;
}
}
// Does not perform any range checks.
......@@ -67,7 +71,10 @@ binary_deserializer::binary_deserializer(actor_system& sys) noexcept
}
bool binary_deserializer::fetch_next_object_type(type_id_t& type) noexcept {
return value(type);
type = invalid_type_id;
emplace_error(sec::unsupported_operation,
"the default binary format does not embed type information");
return false;
}
bool binary_deserializer::begin_sequence(size_t& list_size) noexcept {
......
......@@ -51,10 +51,6 @@ void binary_serializer::skip(size_t num_bytes) {
write_pos_ += num_bytes;
}
bool binary_serializer::inject_next_object_type(type_id_t type) {
return value(type);
}
bool binary_serializer::begin_sequence(size_t list_size) {
// Use varbyte encoding to compress sequence size on the wire.
// For 64-bit values, the encoded representation cannot get larger than 10
......
......@@ -19,31 +19,48 @@
#include "caf/config_value.hpp"
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <ostream>
#include "caf/deep_to_string.hpp"
#include "caf/detail/config_consumer.hpp"
#include "caf/detail/meta_object.hpp"
#include "caf/detail/overload.hpp"
#include "caf/detail/parse.hpp"
#include "caf/detail/parser/read_config.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/expected.hpp"
#include "caf/parser_state.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/string_view.hpp"
namespace caf {
namespace {
const char* type_names[] {
"integer",
"boolean",
"real",
"timespan",
"uri",
"string",
"list",
"dictionary",
};
const char* type_names[] = {"none", "integer", "boolean",
"real", "timespan", "uri",
"string", "list", "dictionary"};
template <class To, class From>
auto no_conversion() {
return [](const From&) {
std::string msg = "cannot convert ";
msg += type_names[detail::tl_index_of<config_value::types, From>::value];
msg += " to ";
msg += type_names[detail::tl_index_of<config_value::types, To>::value];
auto err = make_error(sec::conversion_failed, std::move(msg));
return expected<To>{std::move(err)};
};
}
template <class To, class... From>
auto no_conversions() {
return detail::make_overload(no_conversion<To, From>()...);
}
} // namespace
......@@ -94,12 +111,16 @@ expected<config_value> config_value::parse(string_view str) {
// -- properties ---------------------------------------------------------------
void config_value::convert_to_list() {
if (holds_alternative<list>(data_))
return;
using std::swap;
config_value tmp;
swap(*this, tmp);
data_ = std::vector<config_value>{std::move(tmp)};
if (holds_alternative<list>(data_)) {
; // nop
} else if (holds_alternative<none_t>(data_)) {
data_ = config_value::list{};
} else {
using std::swap;
config_value tmp;
swap(*this, tmp);
data_ = config_value::list{std::move(tmp)};
}
}
config_value::list& config_value::as_list() {
......@@ -108,9 +129,15 @@ config_value::list& config_value::as_list() {
}
config_value::dictionary& config_value::as_dictionary() {
if (!holds_alternative<dictionary>(*this))
*this = dictionary{};
return get<dictionary>(*this);
if (auto dict = get_if<config_value::dictionary>(&data_)) {
return *dict;
} else if (auto lifted = to_dictionary()) {
data_ = std::move(*lifted);
return get<config_value::dictionary>(data_);
} else {
data_ = config_value::dictionary{};
return get<config_value::dictionary>(data_);
}
}
void config_value::append(config_value x) {
......@@ -126,6 +153,304 @@ const char* config_value::type_name_at_index(size_t index) noexcept {
return type_names[index];
}
// -- utility ------------------------------------------------------------------
type_id_t config_value::type_id() const noexcept {
static constexpr type_id_t allowed_types[] = {
type_id_v<none_t>,
type_id_v<config_value::integer>,
type_id_v<config_value::boolean>,
type_id_v<config_value::real>,
type_id_v<timespan>,
type_id_v<uri>,
type_id_v<config_value::string>,
type_id_v<config_value::list>,
type_id_v<config_value::dictionary>,
};
return allowed_types[data_.index()];
}
error_code<sec> config_value::default_construct(type_id_t id) {
switch (id) {
case type_id_v<bool>:
set(false);
return sec::none;
case type_id_v<double>:
case type_id_v<float>:
case type_id_v<long double>:
set(0.0);
return sec::none;
case type_id_v<int16_t>:
case type_id_v<int32_t>:
case type_id_v<int64_t>:
case type_id_v<int8_t>:
case type_id_v<uint16_t>:
case type_id_v<uint32_t>:
case type_id_v<uint64_t>:
case type_id_v<uint8_t>:
set(0);
return sec::none;
case type_id_v<std::string>:
set(std::string{});
return sec::none;
case type_id_v<timespan>:
set(timespan{});
return sec::none;
case type_id_v<uri>:
set(uri{});
return sec::none;
default:
if (auto meta = detail::global_meta_object(id)) {
auto ptr = malloc(meta->padded_size);
auto free_guard = detail::make_scope_guard([ptr] { free(ptr); });
meta->default_construct(ptr);
auto destroy_guard
= detail::make_scope_guard([=] { meta->destroy(ptr); });
config_value_writer writer{this};
if (meta->save(writer, ptr)) {
return sec::none;
} else {
auto& err = writer.get_error();
if (err.category() == type_id_v<sec>)
return static_cast<sec>(err.code());
else
return sec::conversion_failed;
}
} else {
return sec::unknown_type;
}
}
}
expected<bool> config_value::to_boolean() const {
using result_type = expected<bool>;
auto f = detail::make_overload(
no_conversions<bool, none_t, integer, real, timespan, uri,
config_value::list, config_value::dictionary>(),
[](boolean x) { return result_type{x}; },
[](const std::string& x) {
if (x == "true") {
return result_type{true};
} else if (x == "false") {
return result_type{false};
} else {
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a boolean";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
}
});
return visit(f, data_);
}
expected<config_value::integer> config_value::to_integer() const {
using result_type = expected<integer>;
auto f = detail::make_overload(
no_conversions<integer, none_t, bool, timespan, uri, config_value::list,
config_value::dictionary>(),
[](integer x) { return result_type{x}; },
[](real x) {
using limits = std::numeric_limits<config_value::integer>;
if (std::isfinite(x) // never convert NaN & friends
&& std::fmod(x, 1.0) == 0.0 // only convert whole numbers
&& x <= static_cast<config_value::real>(limits::max())
&& x >= static_cast<config_value::real>(limits::min())) {
return result_type{static_cast<config_value::integer>(x)};
} else {
auto err = make_error(
sec::conversion_failed,
"cannot convert decimal or out-of-bounds real number to an integer");
return result_type{std::move(err)};
}
},
[](const std::string& x) {
auto tmp_int = config_value::integer{0};
if (detail::parse(x, tmp_int) == none)
return result_type{tmp_int};
auto tmp_real = 0.0;
if (detail::parse(x, tmp_real) == none)
if (auto ival = config_value{tmp_real}.to_integer())
return result_type{*ival};
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to an integer";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
});
return visit(f, data_);
}
expected<config_value::real> config_value::to_real() const {
using result_type = expected<real>;
auto f = detail::make_overload(
no_conversions<real, none_t, bool, timespan, uri, config_value::list,
config_value::dictionary>(),
[](integer x) {
// This cast may lose precision on the value. We could try and check that,
// but refusing to convert on loss of precision could also be unexpected
// behavior. So we rather always convert, even if it costs precision.
return result_type{static_cast<real>(x)};
},
[](real x) { return result_type{x}; },
[](const std::string& x) {
auto tmp = 0.0;
if (detail::parse(x, tmp) == none)
return result_type{tmp};
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a floating point number";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
});
return visit(f, data_);
}
expected<timespan> config_value::to_timespan() const {
using result_type = expected<timespan>;
auto f = detail::make_overload(
no_conversions<timespan, none_t, bool, integer, real, uri,
config_value::list, config_value::dictionary>(),
[](timespan x) { return result_type{x}; },
[](const std::string& x) {
auto tmp = timespan{};
if (detail::parse(x, tmp) == none)
return result_type{tmp};
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a timespan";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
});
return visit(f, data_);
}
expected<config_value::list> config_value::to_list() const {
using result_type = expected<list>;
auto dict_to_list = [](const dictionary& dict, list& result) {
for (const auto& [key, val] : dict) {
list kvp;
kvp.reserve(2);
kvp.emplace_back(key);
kvp.emplace_back(val);
result.emplace_back(std::move(kvp));
}
};
auto f = detail::make_overload(
no_conversions<list, none_t, bool, integer, real, timespan, uri>(),
[dict_to_list](const std::string& x) {
// Check whether we can parse the string as a list. If that fails, try
// whether we can parse it as a dictionary instead (and then convert that
// to a list).
config_value::list tmp;
if (detail::parse(x, tmp, detail::require_opening_char) == none)
return result_type{std::move(tmp)};
config_value::dictionary dict;
if (detail::parse(x, dict, detail::require_opening_char) == none) {
tmp.clear();
dict_to_list(dict, tmp);
return result_type{std::move(tmp)};
}
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a list";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const list& x) { return result_type{x}; },
[dict_to_list](const dictionary& x) {
list tmp;
dict_to_list(x, tmp);
return result_type{std::move(tmp)};
});
return visit(f, data_);
}
expected<config_value::dictionary> config_value::to_dictionary() const {
using result_type = expected<dictionary>;
auto f = detail::make_overload(
no_conversions<dictionary, none_t, bool, integer, timespan, real, uri>(),
[](const list& x) {
dictionary tmp;
auto lift = [&tmp](const config_value& element) {
auto ls = element.to_list();
if (ls && ls->size() == 2)
return tmp.emplace(to_string((*ls)[0]), std::move((*ls)[1])).second;
else
return false;
};
if (std::all_of(x.begin(), x.end(), lift)) {
return result_type{std::move(tmp)};
} else {
auto err = make_error(sec::conversion_failed,
"cannot convert list to dictionary unless each "
"element in the list is a key-value pair");
return result_type{std::move(err)};
}
},
[](const std::string& x) {
if (dictionary tmp; detail::parse(x, tmp) == none) {
return result_type{std::move(tmp)};
}
if (list tmp; detail::parse(x, tmp) == none) {
config_value ls{std::move(tmp)};
if (auto res = ls.to_dictionary())
return res;
}
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a dictionary";
return result_type{make_error(sec::conversion_failed, std::move(msg))};
},
[](const dictionary& x) { return result_type{x}; });
return visit(f, data_);
}
bool config_value::can_convert_to_dictionary() const {
auto f = detail::make_overload( //
[](const auto&) { return false; },
[this](const std::string&) {
// TODO: implement some dry-run mode and use it here to avoid creating an
// actual dictionary only to throw it away.
auto maybe_dict = to_dictionary();
return static_cast<bool>(maybe_dict);
},
[](const dictionary&) { return true; });
return visit(f, data_);
}
optional<message>
config_value::parse_msg_impl(string_view str,
span<const type_id_list> allowed_types) {
if (auto val = parse(str)) {
auto ls_size = val->as_list().size();
message result;
auto converts = [&val, &result, ls_size](type_id_list ls) {
if (ls.size() != ls_size)
return false;
config_value_reader reader{std::addressof(*val)};
auto unused = size_t{0};
reader.begin_sequence(unused);
CAF_ASSERT(unused == ls_size);
intrusive_ptr<detail::message_data> ptr;
if (auto vptr = malloc(sizeof(detail::message_data) + ls.data_size()))
ptr.reset(new (vptr) detail::message_data(ls), false);
else
return false;
auto pos = ptr->storage();
for (auto type : ls) {
auto meta = detail::global_meta_object(type);
CAF_ASSERT(meta != nullptr);
meta->default_construct(pos);
ptr->inc_constructed_elements();
if (!meta->load(reader, pos))
return false;
pos += meta->padded_size;
}
result.reset(ptr.release(), false);
return reader.end_sequence();
};
if (std::any_of(allowed_types.begin(), allowed_types.end(), converts))
return {std::move(result)};
}
return {};
}
// -- related free functions ---------------------------------------------------
bool operator<(const config_value& x, const config_value& y) {
......@@ -143,10 +468,17 @@ void to_string_impl(std::string& str, const config_value& x);
struct to_string_visitor {
std::string& str;
void operator()(const std::string& x) {
detail::print_escaped(str, x);
}
template <class T>
void operator()(const T& x) {
detail::stringification_inspector f{str};
f.value(x);
detail::print(str, x);
}
void operator()(none_t) {
str += "null";
}
void operator()(const uri& x) {
......@@ -155,36 +487,38 @@ struct to_string_visitor {
}
void operator()(const config_value::list& xs) {
if (xs.empty()) {
str += "[]";
return;
}
str += '[';
auto i = xs.begin();
to_string_impl(str, *i);
for (++i; i != xs.end(); ++i) {
str += ", ";
if (!xs.empty()) {
auto i = xs.begin();
to_string_impl(str, *i);
for (++i; i != xs.end(); ++i) {
str += ", ";
to_string_impl(str, *i);
}
}
str += ']';
}
void append_key(const std::string& key) {
if (std::all_of(key.begin(), key.end(), ::isalnum))
str.append(key.begin(), key.end());
else
(*this)(key);
}
void operator()(const config_value::dictionary& xs) {
if (xs.empty()) {
str += "{}";
return;
}
detail::stringification_inspector f{str};
str += '{';
auto i = xs.begin();
f.value(i->first);
str += " = ";
to_string_impl(str, i->second);
for (++i; i != xs.end(); ++i) {
str += ", ";
f.value(i->first);
if (!xs.empty()) {
auto i = xs.begin();
append_key(i->first);
str += " = ";
to_string_impl(str, i->second);
for (++i; i != xs.end(); ++i) {
str += ", ";
append_key(i->first);
str += " = ";
to_string_impl(str, i->second);
}
}
str += '}';
}
......@@ -198,8 +532,19 @@ void to_string_impl(std::string& str, const config_value& x) {
} // namespace
std::string to_string(const config_value& x) {
if (auto str = get_if<std::string>(std::addressof(x.get_data()))) {
return *str;
} else {
std::string result;
to_string_impl(result, x);
return result;
}
}
std::string to_string(const settings& xs) {
std::string result;
to_string_impl(result, x);
to_string_visitor f{result};
f(xs);
return result;
}
......
......@@ -122,11 +122,12 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
return false;
},
[this, &type](const config_value* val) {
if (auto obj = get_if<settings>(val); obj == nullptr) {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
auto tid = val->type_id();
if (tid != type_id_v<config_value::dictionary>) {
type = tid;
return true;
} else {
return fetch_object_type(obj, type);
return fetch_object_type(get_if<settings>(val), type);
}
},
[this](key_ptr) {
......@@ -146,11 +147,13 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
emplace_error(sec::runtime_error, "list index out of bounds");
return false;
}
if (auto obj = get_if<settings>(std::addressof(seq.current())); !obj) {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
auto& val = seq.current();
auto tid = val.type_id();
if (tid != type_id_v<config_value::dictionary>) {
type = tid;
return true;
} else {
return fetch_object_type(obj, type);
return fetch_object_type(get_if<settings>(&val), type);
}
},
[this](associative_array&) {
......@@ -162,7 +165,7 @@ bool config_value_reader::fetch_next_object_type(type_id_t& type) {
}
}
bool config_value_reader::begin_object(string_view) {
bool config_value_reader::begin_object(type_id_t type, string_view) {
if (st_.empty()) {
emplace_error(sec::runtime_error,
"tried to read multiple objects from the root object");
......@@ -176,10 +179,17 @@ bool config_value_reader::begin_object(string_view) {
},
[this](const config_value* val) {
if (auto obj = get_if<settings>(val)) {
// Morph into an object. This value gets "consumed" by
// begin_object/end_object.
// Unbox the dictionary.
st_.top() = obj;
return true;
} else if (auto dict = val->to_dictionary()) {
// Replace the actual config value on the stack with the on-the-fly
// converted dictionary.
auto ptr = std::make_unique<config_value>(std::move(*dict));
const settings* unboxed = std::addressof(get<settings>(*ptr));
st_.top() = unboxed;
scratch_space_.emplace_back(std::move(ptr));
return true;
} else {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
......@@ -216,7 +226,27 @@ bool config_value_reader::begin_object(string_view) {
"fetch_next_object_type called inside associative array");
return false;
});
return visit(f, st_.top());
if (visit(f, st_.top())) {
// Perform a type check if type is a valid ID and the object contains an
// "@type" field.
if (type != invalid_type_id) {
CAF_ASSERT(holds_alternative<const settings*>(st_.top()));
auto obj = get<const settings*>(st_.top());
auto want = query_type_name(type);
if (auto i = obj->find("@type"); i != obj->end()) {
if (auto got = get_if<std::string>(std::addressof(i->second))) {
if (want != *got) {
emplace_error(sec::type_clash, "expected type: " + to_string(want),
"found type: " + *got);
return false;
}
}
}
}
return true;
} else {
return false;
}
}
bool config_value_reader::end_object() {
......@@ -563,15 +593,15 @@ bool config_value_reader::value(span<byte> bytes) {
bool config_value_reader::fetch_object_type(const settings* obj,
type_id_t& type) {
if (auto str = get_if<std::string>(obj, "@type"); str == nullptr) {
emplace_error(sec::runtime_error,
"cannot fetch object type: no '@type' entry found");
return false;
} else if (auto id = query_type_id(*str); id == invalid_type_id) {
emplace_error(sec::runtime_error, "no such type: " + *str);
return false;
} else {
// fetch_next_object_type only calls this function
type = type_id_v<config_value::dictionary>;
return true;
} else if (auto id = query_type_id(*str); id != invalid_type_id) {
type = id;
return true;
} else {
emplace_error(sec::runtime_error, "unknown type: " + *str);
return false;
}
}
......
......@@ -66,18 +66,7 @@ config_value_writer::~config_value_writer() {
// -- interface functions ------------------------------------------------------
bool config_value_writer::inject_next_object_type(type_id_t type) {
CHECK_NOT_EMPTY();
type_hint_ = query_type_name(type);
if (type_hint_.empty()) {
emplace_error(sec::runtime_error,
"query_type_name returned an empty string for type ID");
return false;
}
return true;
}
bool config_value_writer::begin_object(string_view) {
bool config_value_writer::begin_object(type_id_t type, string_view) {
CHECK_NOT_EMPTY();
auto f = detail::make_overload(
[this](config_value* x) {
......@@ -118,10 +107,8 @@ bool config_value_writer::begin_object(string_view) {
});
if (!visit(f, st_.top()))
return false;
if (!type_hint_.empty()) {
put(*get<settings*>(st_.top()), "@type", type_hint_);
type_hint_ = string_view{};
}
if (type != invalid_type_id)
put(*get<settings*>(st_.top()), "@type", query_type_name(type));
return true;
}
......
......@@ -26,11 +26,7 @@
namespace caf::detail {
bool serialized_size_inspector::inject_next_object_type(type_id_t type) {
return value(type);
}
bool serialized_size_inspector::begin_object(string_view) {
bool serialized_size_inspector::begin_object(type_id_t, string_view) {
return true;
}
......
......@@ -49,7 +49,7 @@ void escape(std::string& result, char c) {
namespace caf::detail {
bool stringification_inspector::begin_object(string_view name) {
bool stringification_inspector::begin_object(type_id_t, string_view name) {
sep();
if (name != "std::string") {
result_.insert(result_.end(), name.begin(), name.end());
......@@ -130,15 +130,13 @@ bool stringification_inspector::value(float x) {
bool stringification_inspector::value(double x) {
sep();
auto str = std::to_string(x);
result_ += str;
detail::print(result_, x);
return true;
}
bool stringification_inspector::value(long double x) {
sep();
auto str = std::to_string(x);
result_ += str;
detail::print(result_, x);
return true;
}
......
......@@ -59,7 +59,7 @@ template <class Deserializer>
bool load_data(Deserializer& source, message::data_ptr& data) {
// For machine-to-machine data formats, we prefix the type information.
if (!source.has_human_readable_format()) {
GUARDED(source.begin_object("message"));
GUARDED(source.begin_object(type_id_v<message>, "message"));
GUARDED(source.begin_field("types"));
size_t msg_size = 0;
GUARDED(source.begin_sequence(msg_size));
......@@ -159,8 +159,6 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
using unique_void_ptr = std::unique_ptr<void, free_t>;
auto msg_size = size_t{0};
std::vector<object_ptr> objects;
GUARDED(source.begin_object("message"));
GUARDED(source.begin_field("values"));
GUARDED(source.begin_sequence(msg_size));
if (msg_size > 0) {
// Deserialize message elements individually.
......@@ -185,7 +183,7 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
STOP(sec::unknown_type);
}
}
GUARDED(source.end_field() && source.end_sequence());
GUARDED(source.end_sequence());
// Merge elements into a single message data object.
intrusive_ptr<detail::message_data> ptr;
if (auto vptr = malloc(sizeof(detail::message_data) + data_size)) {
......@@ -203,13 +201,11 @@ bool load_data(Deserializer& source, message::data_ptr& data) {
pos += x.meta->padded_size;
}
data.reset(ptr.release(), false);
return source.end_object();
return true;
} else {
data.reset();
return source.end_sequence();
}
return source.end_sequence() //
&& source.end_field() //
&& source.end_object();
}
} // namespace
......@@ -242,18 +238,18 @@ save_data(Serializer& sink, const message::data_ptr& data) {
if (!sink.has_human_readable_format()) {
if (data == nullptr) {
// Short-circuit empty tuples.
return sink.begin_object("message") //
&& sink.begin_field("types") //
&& sink.begin_sequence(0) //
&& sink.end_sequence() //
&& sink.end_field() //
&& sink.begin_field("values") //
&& sink.begin_tuple(0) //
&& sink.end_tuple() //
&& sink.end_field() //
return sink.begin_object(type_id_v<message>, "message") //
&& sink.begin_field("types") //
&& sink.begin_sequence(0) //
&& sink.end_sequence() //
&& sink.end_field() //
&& sink.begin_field("values") //
&& sink.begin_tuple(0) //
&& sink.end_tuple() //
&& sink.end_field() //
&& sink.end_object();
}
GUARDED(sink.begin_object("message"));
GUARDED(sink.begin_object(type_id_v<message>, "message"));
auto type_ids = data->types();
// Write type information.
GUARDED(sink.begin_field("types") && sink.begin_sequence(type_ids.size()));
......@@ -274,25 +270,17 @@ save_data(Serializer& sink, const message::data_ptr& data) {
// dynamically-typed objects.
if (data == nullptr) {
// Short-circuit empty tuples.
return sink.begin_object("message") //
&& sink.begin_field("values") //
&& sink.begin_sequence(0) //
&& sink.end_sequence() //
&& sink.end_field() //
&& sink.end_object();
return sink.begin_sequence(0) && sink.end_sequence();
}
auto type_ids = data->types();
GUARDED(sink.begin_object("message") //
&& sink.begin_field("values") //
&& sink.begin_sequence(type_ids.size()));
GUARDED(sink.begin_sequence(type_ids.size()));
auto storage = data->storage();
for (auto id : type_ids) {
auto& meta = gmos[id];
GUARDED(sink.inject_next_object_type(id) //
&& save(meta, sink, storage));
GUARDED(save(meta, sink, storage));
storage += meta.padded_size;
}
return sink.end_sequence() && sink.end_field() && sink.end_object();
return sink.end_sequence();
}
} // namespace
......
......@@ -142,6 +142,10 @@ std::string to_string(sec x) {
return "conversion_failed";
case sec::connection_closed:
return "connection_closed";
case sec::type_clash:
return "type_clash";
case sec::unsupported_operation:
return "unsupported_operation";
};
}
......@@ -335,6 +339,12 @@ bool from_string(string_view in, sec& out) {
} else if (in == "connection_closed") {
out = sec::connection_closed;
return true;
} else if (in == "type_clash") {
out = sec::type_clash;
return true;
} else if (in == "unsupported_operation") {
out = sec::unsupported_operation;
return true;
} else {
return false;
}
......@@ -409,6 +419,8 @@ bool from_integer(std::underlying_type_t<sec> in,
case sec::load_callback_failed:
case sec::conversion_failed:
case sec::connection_closed:
case sec::type_clash:
case sec::unsupported_operation:
out = result;
return true;
};
......
......@@ -22,6 +22,8 @@
namespace caf {
// note: to_string is implemented in config_value.cpp
const config_value* get_if(const settings* xs, string_view name) {
// Access the key directly unless the user specified a dot-separated path.
auto pos = name.find('.');
......
......@@ -41,22 +41,22 @@ namespace {
template <class Serializer>
bool serialize_impl(Serializer& sink, const tracing_data_ptr& x) {
if (!x) {
return sink.begin_object("tracing_data") //
&& sink.begin_field("value", false) //
&& sink.end_field() //
return sink.begin_object(invalid_type_id, "tracing_data") //
&& sink.begin_field("value", false) //
&& sink.end_field() //
&& sink.end_object();
}
return sink.begin_object("tracing_data") //
&& sink.begin_field("value", true) //
&& x->serialize(sink) //
&& sink.end_field() //
return sink.begin_object(invalid_type_id, "tracing_data") //
&& sink.begin_field("value", true) //
&& x->serialize(sink) //
&& sink.end_field() //
&& sink.end_object();
}
template <class Deserializer>
bool deserialize_impl(Deserializer& source, tracing_data_ptr& x) {
bool is_present = false;
if (!source.begin_object("tracing_data")
if (!source.begin_object(invalid_type_id, "tracing_data")
|| !source.begin_field("value", is_present))
return false;
if (!is_present)
......
......@@ -22,6 +22,15 @@
namespace caf {
size_t type_id_list::data_size() const noexcept {
auto result = size_t{0};
for (auto type : *this) {
auto meta_obj = detail::global_meta_object(type);
result += meta_obj->padded_size;
}
return result;
}
std::string to_string(type_id_list xs) {
if (!xs || xs.size() == 0)
return "[]";
......
......@@ -23,6 +23,7 @@
#include "core-test.hpp"
#include "nasty.hpp"
#include <cmath>
#include <list>
#include <map>
#include <set>
......@@ -41,6 +42,10 @@
#include "caf/string_view.hpp"
#include "caf/variant.hpp"
#include "caf/detail/bounds_checker.hpp"
#include "caf/detail/overload.hpp"
#include "caf/detail/parse.hpp"
using std::string;
using namespace caf;
......@@ -81,13 +86,751 @@ config_value cfg_lst(Ts&&... xs) {
return config_value{std::move(lst)};
}
struct fixture {
config_value cv_null;
config_value cv_true;
config_value cv_false;
config_value cv_empty_uri;
config_value cv_empty_list;
config_value cv_empty_dict;
config_value cv_caf_uri;
fixture()
: cv_true(true),
cv_false(false),
cv_empty_uri(uri{}),
cv_empty_list(config_value::list{}),
cv_empty_dict(config_value::dictionary{}) {
cv_caf_uri = unbox(make_uri("https://actor-framework.org"));
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(config_value_tests, fixture)
SCENARIO("get_as can convert config values to boolean") {
GIVEN("a config value x with value true or false") {
WHEN("using get_as with bool") {
THEN("conversion succeeds") {
CHECK_EQ(get_as<bool>(cv_true), true);
CHECK_EQ(get_as<bool>(cv_false), false);
}
}
}
GIVEN("a config value x with value \"true\" or \"false\"") {
WHEN("using get_as with bool") {
THEN("conversion succeeds") {
CHECK_EQ(get_as<bool>(config_value{"true"s}), true);
CHECK_EQ(get_as<bool>(config_value{"false"s}), false);
}
}
}
GIVEN("non-boolean config_values") {
WHEN("using get_as with bool") {
THEN("conversion fails") {
CHECK_EQ(get_as<bool>(cv_null), sec::conversion_failed);
CHECK_EQ(get_as<bool>(cv_empty_uri), sec::conversion_failed);
CHECK_EQ(get_as<bool>(cv_empty_list), sec::conversion_failed);
CHECK_EQ(get_as<bool>(cv_empty_dict), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{0}), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{1}), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{0.f}), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{1.f}), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{""s}), sec::conversion_failed);
CHECK_EQ(get_as<bool>(config_value{"1"s}), sec::conversion_failed);
}
}
}
}
SCENARIO("get_as can convert config values to integers") {
GIVEN("a config value x with value 32,768") {
auto x = config_value{32'768};
WHEN("using get_as with integer types") {
THEN("conversion fails if bounds checks fail") {
CHECK_EQ(get_as<uint64_t>(x), 32'768u);
CHECK_EQ(get_as<int64_t>(x), 32'768);
CHECK_EQ(get_as<uint32_t>(x), 32'768u);
CHECK_EQ(get_as<int32_t>(x), 32'768);
CHECK_EQ(get_as<uint16_t>(x), 32'768u);
CHECK_EQ(get_as<int16_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint8_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int8_t>(x), sec::conversion_failed);
}
}
}
GIVEN("a config value x with value -5") {
auto x = config_value{-5};
WHEN("using get_as with integer types") {
THEN("conversion fails for all unsigned types") {
CAF_CHECK_EQUAL(get_as<uint64_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<int64_t>(x), -5);
CAF_CHECK_EQUAL(get_as<uint32_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<int32_t>(x), -5);
CAF_CHECK_EQUAL(get_as<uint16_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<int16_t>(x), -5);
CAF_CHECK_EQUAL(get_as<uint8_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<int8_t>(x), -5);
}
}
}
GIVEN("a config value x with value \"50000\"") {
auto x = config_value{"50000"s};
WHEN("using get_as with integer types") {
THEN("CAF parses the string and performs a bound check") {
CAF_CHECK_EQUAL(get_as<uint64_t>(x), 50'000u);
CAF_CHECK_EQUAL(get_as<int64_t>(x), 50'000);
CAF_CHECK_EQUAL(get_as<uint32_t>(x), 50'000u);
CAF_CHECK_EQUAL(get_as<int32_t>(x), 50'000);
CAF_CHECK_EQUAL(get_as<uint16_t>(x), 50'000u);
CAF_CHECK_EQUAL(get_as<int16_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<uint8_t>(x), sec::conversion_failed);
CAF_CHECK_EQUAL(get_as<int8_t>(x), sec::conversion_failed);
}
}
}
GIVEN("a config value x with value 50.0") {
auto x = config_value{50.0};
WHEN("using get_as with integer types") {
THEN("CAF parses the string and performs a bound check") {
CHECK_EQ(get_as<uint64_t>(x), 50u);
CHECK_EQ(get_as<int64_t>(x), 50);
CHECK_EQ(get_as<uint32_t>(x), 50u);
CHECK_EQ(get_as<int32_t>(x), 50);
CHECK_EQ(get_as<uint16_t>(x), 50u);
CHECK_EQ(get_as<int16_t>(x), 50);
CHECK_EQ(get_as<uint8_t>(x), 50u);
CHECK_EQ(get_as<int8_t>(x), 50);
}
}
}
GIVEN("a config value x with value 50.05") {
auto x = config_value{50.05};
WHEN("using get_as with integer types") {
THEN("CAF fails to convert the real to an integer") {
CHECK_EQ(get_as<uint64_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint32_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int32_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint16_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int16_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint8_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int8_t>(x), sec::conversion_failed);
}
}
}
GIVEN("a config value x with value \"50.000\"") {
auto x = config_value{"50.000"s};
WHEN("using get_as with integer types") {
THEN("CAF parses the string and performs a bound check") {
CHECK_EQ(get_as<uint64_t>(x), 50u);
CHECK_EQ(get_as<int64_t>(x), 50);
CHECK_EQ(get_as<uint32_t>(x), 50u);
CHECK_EQ(get_as<int32_t>(x), 50);
CHECK_EQ(get_as<uint16_t>(x), 50u);
CHECK_EQ(get_as<int16_t>(x), 50);
CHECK_EQ(get_as<uint8_t>(x), 50u);
CHECK_EQ(get_as<int8_t>(x), 50);
}
}
}
GIVEN("a config value x with value \"50.05\"") {
auto x = config_value{"50.05"s};
WHEN("using get_as with integer types") {
THEN("CAF fails to convert the real to an integer") {
CHECK_EQ(get_as<uint64_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint32_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int32_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint16_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int16_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<uint8_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<int8_t>(x), sec::conversion_failed);
}
}
}
GIVEN("config_values of null, URI, boolean, list or dictionary") {
WHEN("using get_as with floating point types") {
THEN("conversion fails") {
CHECK_EQ(get_as<float>(cv_null), sec::conversion_failed);
CHECK_EQ(get_as<float>(cv_true), sec::conversion_failed);
CHECK_EQ(get_as<float>(cv_false), sec::conversion_failed);
CHECK_EQ(get_as<float>(cv_empty_uri), sec::conversion_failed);
CHECK_EQ(get_as<float>(cv_empty_list), sec::conversion_failed);
CHECK_EQ(get_as<float>(cv_empty_dict), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_null), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_true), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_false), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_empty_uri), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_empty_list), sec::conversion_failed);
CHECK_EQ(get_as<double>(cv_empty_dict), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_null), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_true), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_false), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_empty_uri), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_empty_list), sec::conversion_failed);
CHECK_EQ(get_as<long double>(cv_empty_dict), sec::conversion_failed);
}
}
}
}
SCENARIO("get_as can convert config values to floating point numbers") {
GIVEN("a config value x with value 1.79769e+308") {
auto x = config_value{1.79769e+308};
WHEN("using get_as with floating point types") {
THEN("conversion fails if bounds checks fail") {
CHECK_EQ(get_as<long double>(x), 1.79769e+308);
CHECK_EQ(get_as<double>(x), 1.79769e+308);
CHECK_EQ(get_as<float>(x), sec::conversion_failed);
}
}
}
GIVEN("a config value x with value \"3e7\"") {
auto x = config_value{"3e7"s};
WHEN("using get_as with floating point types") {
THEN("CAF parses the string and converts the value") {
CHECK_EQ(get_as<long double>(x), 3e7);
CHECK_EQ(get_as<double>(x), 3e7);
CHECK_EQ(get_as<float>(x), 3e7f);
}
}
}
GIVEN("a config value x with value 123") {
auto x = config_value{123};
WHEN("using get_as with floating point types") {
THEN("CAF converts the value") {
CHECK_EQ(get_as<long double>(x), 123.0);
CHECK_EQ(get_as<double>(x), 123.0);
CHECK_EQ(get_as<float>(x), 123.f);
}
}
}
GIVEN("config_values of null, URI, boolean, list or dictionary") {
WHEN("using get_as with integer types") {
THEN("conversion fails") {
CHECK_EQ(get_as<int64_t>(cv_null), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(cv_true), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(cv_false), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(cv_empty_uri), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(cv_empty_list), sec::conversion_failed);
CHECK_EQ(get_as<int64_t>(cv_empty_dict), sec::conversion_failed);
}
}
}
}
SCENARIO("get_as can convert config values to timespans") {
using namespace std::chrono_literals;
GIVEN("a config value with value 42s") {
auto x = config_value{timespan{42s}};
WHEN("using get_as with timespan") {
THEN("conversion succeeds") {
CHECK_EQ(get_as<timespan>(x), timespan{42s});
CHECK_EQ(get_as<std::string>(x), "42s");
}
}
WHEN("using get_as with type other than timespan or string") {
THEN("conversion fails") {
CHECK_EQ(get_as<int64_t>(x), sec::conversion_failed);
CHECK_EQ(get_as<double>(x), sec::conversion_failed);
CHECK_EQ(get_as<uri>(x), sec::conversion_failed);
CHECK_EQ(get_as<config_value::list>(x), sec::conversion_failed);
CHECK_EQ(get_as<config_value::dictionary>(x), sec::conversion_failed);
}
}
}
}
SCENARIO("get_as can convert config values to strings") {
using string = std::string;
GIVEN("any config value") {
WHEN("using get_as with string") {
THEN("CAF renders the value as string") {
CHECK_EQ(get_as<string>(cv_null), "null");
CHECK_EQ(get_as<string>(cv_true), "true");
CHECK_EQ(get_as<string>(cv_false), "false");
CHECK_EQ(get_as<string>(cv_empty_list), "[]");
CHECK_EQ(get_as<string>(cv_empty_dict), "{}");
CHECK_EQ(get_as<string>(config_value{42}), "42");
CHECK_EQ(get_as<string>(config_value{4.2}), "4.2");
CHECK_EQ(get_as<string>(config_value{timespan{4}}), "4ns");
CHECK_EQ(get_as<string>(cv_caf_uri), "https://actor-framework.org");
}
}
}
}
SCENARIO("get_as can convert config values to lists") {
using list = config_value::list;
GIVEN("a config value with value [1, 2, 3]") {
auto x = make_config_value_list(1, 2, 3);
WHEN("using get_as with config_value::list") {
THEN("conversion succeeds") {
auto maybe_res = get_as<list>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(get_as<int>(res[0]), 1);
CHECK_EQ(get_as<int>(res[1]), 2);
CHECK_EQ(get_as<int>(res[2]), 3);
}
}
}
WHEN("using get_as with vector<int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::vector<int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res[0], 1);
CHECK_EQ(res[1], 2);
CHECK_EQ(res[2], 3);
}
}
}
}
GIVEN("a config value with value \"[1, 2, 3]\"") {
auto x = config_value("[1, 2, 3]"s);
WHEN("using get_as with list") {
THEN("conversion succeeds") {
auto maybe_res = get_as<list>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(get_as<int>(res[0]), 1);
CHECK_EQ(get_as<int>(res[1]), 2);
CHECK_EQ(get_as<int>(res[2]), 3);
}
}
}
WHEN("using get_as with vector<int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::vector<int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res[0], 1);
CHECK_EQ(res[1], 2);
CHECK_EQ(res[2], 3);
}
}
}
}
}
SCENARIO("get_as can convert config values to dictionaries") {
using dictionary = config_value::dictionary;
auto dict = config_value::dictionary{
{"a", config_value{1}},
{"b", config_value{2}},
{"c", config_value{3}},
};
std::vector<config_value> given_values;
given_values.emplace_back(std::move(dict));
given_values.emplace_back("{a = 1, b = 2, c = 3}"s);
for (auto& x : given_values) {
GIVEN("the config value " << x) {
WHEN("using get_as with config_value::dictionary") {
THEN("conversion succeeds") {
auto maybe_res = get_as<dictionary>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(get_as<int>(res["a"]), 1);
CHECK_EQ(get_as<int>(res["b"]), 2);
CHECK_EQ(get_as<int>(res["c"]), 3);
}
}
}
WHEN("using get_as with config_value::list") {
THEN("CAF converts the dictionary to a list of lists") {
auto maybe_res = get_as<list>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
if (auto kvp = unbox(get_as<list>(res[0]));
CHECK_EQ(kvp.size(), 2u)) {
CHECK_EQ(get_as<std::string>(kvp[0]), "a");
CHECK_EQ(get_as<int>(kvp[1]), 1);
}
if (auto kvp = unbox(get_as<list>(res[1]));
CHECK_EQ(kvp.size(), 2u)) {
CHECK_EQ(get_as<std::string>(kvp[0]), "b");
CHECK_EQ(get_as<int>(kvp[1]), 2);
}
if (auto kvp = unbox(get_as<list>(res[2]));
CHECK_EQ(kvp.size(), 2u)) {
CHECK_EQ(get_as<std::string>(kvp[0]), "c");
CHECK_EQ(get_as<int>(kvp[1]), 3);
}
}
}
}
WHEN("using get_as with vector<tuple<string, int>>") {
THEN("CAF converts the dictionary to a list of tuples") {
using kvp_t = std::tuple<std::string, int>;
auto maybe_res = get_as<std::vector<kvp_t>>(x);
MESSAGE("maybe_res: " << maybe_res);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res[0], kvp_t("a", 1));
CHECK_EQ(res[1], kvp_t("b", 2));
CHECK_EQ(res[2], kvp_t("c", 3));
}
}
}
}
}
}
SCENARIO("get_as can convert config values to maps") {
auto dict = config_value::dictionary{
{"1", config_value{1}},
{"2", config_value{4}},
{"3", config_value{9}},
};
std::vector<config_value> given_values;
given_values.emplace_back(std::move(dict));
given_values.emplace_back("{1 = 1, 2 = 4, 3 = 9}"s);
for (auto& x : given_values) {
GIVEN("the config value " << x) {
WHEN("using get_as with map<string, int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::map<std::string, int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res["1"], 1);
CHECK_EQ(res["2"], 4);
CHECK_EQ(res["3"], 9);
}
}
}
WHEN("using get_as with unordered_map<string, int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::unordered_map<std::string, int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res["1"], 1);
CHECK_EQ(res["2"], 4);
CHECK_EQ(res["3"], 9);
}
}
}
WHEN("using get_as with map<int, int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::map<int, int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res[1], 1);
CHECK_EQ(res[2], 4);
CHECK_EQ(res[3], 9);
}
}
}
WHEN("using get_as with unordered_map<int, int>") {
THEN("conversion succeeds") {
auto maybe_res = get_as<std::unordered_map<int, int>>(x);
if (CHECK(maybe_res) && CHECK_EQ(maybe_res->size(), 3u)) {
auto& res = *maybe_res;
CHECK_EQ(res[1], 1);
CHECK_EQ(res[2], 4);
CHECK_EQ(res[3], 9);
}
}
}
}
}
}
SCENARIO("get_as can convert config values to custom types") {
std::vector<std::pair<weekday, std::string>> weekday_values{
{weekday::monday, "monday"s}, {weekday::tuesday, "tuesday"s},
{weekday::wednesday, "wednesday"s}, {weekday::thursday, "thursday"s},
{weekday::friday, "friday"s}, {weekday::saturday, "saturday"s},
{weekday::sunday, "sunday"s}};
for (const auto& [enum_val, str_val] : weekday_values) {
config_value x{str_val};
GIVEN("the config value " << x) {
WHEN("using get_as with weekday") {
THEN("CAF picks up the custom inspect_value overload for conversion") {
auto maybe_res = get_as<weekday>(x);
if (CHECK(maybe_res))
CHECK_EQ(*maybe_res, enum_val);
}
}
}
}
config_value::dictionary my_request_dict;
my_request_dict["a"] = config_value{10};
my_request_dict["b"] = config_value{20};
auto my_request_val = config_value{my_request_dict};
GIVEN("the config value " << my_request_val) {
WHEN("using get_as with my_request") {
THEN("CAF picks up the custom inspect overload for conversion") {
auto maybe_res = get_as<my_request>(my_request_val);
if (CHECK(maybe_res))
CHECK_EQ(*maybe_res, my_request(10, 20));
}
}
}
std::vector<config_value> obj_vals{config_value{my_request_val},
config_value{config_value::dictionary{}},
config_value{"{}"s}};
for (auto& x : obj_vals) {
GIVEN("the config value " << x) {
WHEN("using get_as with dummy_tag_type") {
THEN("CAF only checks whether the config value is dictionary-ish") {
CHECK(get_as<dummy_tag_type>(my_request_val));
}
}
}
}
std::vector<config_value> non_obj_vals{config_value{}, config_value{42},
config_value{"[1,2,3]"s}};
for (auto& x : non_obj_vals) {
GIVEN("the config value " << x) {
WHEN("using get_as with dummy_tag_type") {
THEN("conversion fails") {
CHECK_EQ(get_as<dummy_tag_type>(x), sec::conversion_failed);
}
}
}
}
}
SCENARIO("get_or converts or returns a fallback value") {
using namespace caf::literals;
GIVEN("the config value 42") {
auto x = config_value{42};
WHEN("using get_or with type int") {
THEN("CAF ignores the default value") {
CHECK_EQ(get_or(x, 10), 42);
}
}
WHEN("using get_or with type string") {
THEN("CAF ignores the default value") {
CHECK_EQ(get_or(x, "foo"s), "42"s);
}
}
WHEN("using get_or with type bool") {
THEN("CAF returns the default value") {
CHECK_EQ(get_or(x, false), false);
}
}
WHEN("using get_or with type span<int>") {
int fallback_arr[] = {10, 20, 30};
auto fallback = make_span(fallback_arr);
THEN("CAF returns the default value after converting it to vector<int>") {
auto result = get_or(x, fallback);
static_assert(std::is_same<decltype(result), std::vector<int>>::value);
CHECK_EQ(result, std::vector<int>({10, 20, 30}));
}
}
WHEN("using get_or with type i64_wrapper") {
THEN("CAF returns i64_wrapper{42}") {
auto result = get_or<i64_wrapper>(x, 10);
CHECK_EQ(result.value, 42);
}
}
}
GIVEN("the config value 'hello world'") {
auto x = config_value{"hello world"};
WHEN("using get_or with type i64_wrapper") {
THEN("CAF returns the fallback value") {
auto result = get_or<i64_wrapper>(x, 10);
CHECK_EQ(result.value, 10);
}
}
}
}
SCENARIO("config values can default-construct all registered types") {
auto from = [](type_id_t id) {
config_value result;
if (auto err = result.default_construct(id))
CAF_FAIL("default construction failed: " << err);
return result;
};
auto keys = [](const auto& dict) {
std::vector<std::string> result;
for (const auto& kvp : dict)
result.emplace_back(kvp.first);
return result;
};
GIVEN("a config value") {
WHEN("calling default_construct for any integral type") {
THEN("the config value becomes config_value::integer{0}") {
CHECK_EQ(from(type_id_v<int8_t>), config_value{0});
CHECK_EQ(from(type_id_v<int16_t>), config_value{0});
CHECK_EQ(from(type_id_v<int32_t>), config_value{0});
CHECK_EQ(from(type_id_v<int64_t>), config_value{0});
CHECK_EQ(from(type_id_v<uint8_t>), config_value{0});
CHECK_EQ(from(type_id_v<uint16_t>), config_value{0});
CHECK_EQ(from(type_id_v<uint32_t>), config_value{0});
CHECK_EQ(from(type_id_v<uint64_t>), config_value{0});
}
}
WHEN("calling default_construct for any floating point type") {
THEN("the config value becomes config_value::real{0}") {
CHECK_EQ(from(type_id_v<float>), config_value{0.0});
CHECK_EQ(from(type_id_v<double>), config_value{0.0});
CHECK_EQ(from(type_id_v<long double>), config_value{0.0});
}
}
WHEN("calling default_construct for std::string") {
THEN("the config value becomes \"\"") {
CHECK_EQ(from(type_id_v<std::string>), config_value{std::string{}});
}
}
WHEN("calling default_construct for caf::timespan") {
THEN("the config value becomes 0s") {
CHECK_EQ(from(type_id_v<timespan>), config_value{timespan{0}});
}
}
WHEN("calling default_construct for caf::uri") {
THEN("the config value becomes an empty URI") {
CHECK_EQ(from(type_id_v<uri>), config_value{uri{}});
}
}
WHEN("calling default_construct for any list-like type") {
THEN("the config value becomes a config_value::list") {
CHECK_EQ(from(type_id_v<std::vector<actor>>).get_data().index(), 7u);
CHECK_EQ(from(type_id_v<std::vector<bool>>).get_data().index(), 7u);
}
}
WHEN("calling default_construct for any custom non-list type") {
THEN("the config value becomes a dictionary") {
auto val = from(type_id_v<my_request>);
CHECK_EQ(val.get_data().index(), 8u);
auto& dict = val.as_dictionary();
CHECK_EQ(keys(dict), std::vector<std::string>({"@type", "a", "b"}));
CHECK_EQ(dict["@type"].get_data().index(), 6u);
CHECK_EQ(get_as<std::string>(dict["@type"]), "my_request"s);
CHECK_EQ(dict["a"].get_data().index(), 1u);
CHECK_EQ(get_as<int32_t>(dict["a"]), 0);
CHECK_EQ(dict["b"].get_data().index(), 1u);
CHECK_EQ(get_as<int32_t>(dict["b"]), 0);
}
}
}
}
#define CHECK_ROUNDTRIP(init_val, expected_str) \
do { \
config_value x; \
if (auto assign_failed = x.assign(init_val); CHECK(!assign_failed)) { \
auto str = to_string(x); \
CHECK_EQ(str, expected_str); \
auto parsed = config_value::parse(str); \
using init_val_type = decltype(init_val); \
if (CHECK(parsed)) { \
if constexpr (!std::is_same<init_val_type, message>::value) \
CHECK_EQ(get_as<init_val_type>(*parsed), init_val); \
else \
CHECK_EQ(to_string(*parsed), str); \
} \
} \
} while (false)
SCENARIO("config values can parse their own to_string output") {
GIVEN("a config value") {
WHEN("assigning a value and then calling to_string on it") {
THEN("then config_value::parse reconstitutes the original value") {
CHECK_ROUNDTRIP(0, "0");
CHECK_ROUNDTRIP("hello world"s, "hello world");
CHECK_ROUNDTRIP(std::vector<int>({1, 2, 3}), "[1, 2, 3]");
CHECK_ROUNDTRIP(my_request(1, 2),
R"_({"@type" = "my_request", a = 1, b = 2})_");
CHECK_ROUNDTRIP(std::make_tuple(add_atom_v, 1, 2),
R"_([{"@type" = "caf::add_atom"}, 1, 2])_");
CHECK_ROUNDTRIP(make_message(add_atom_v, 1, 2),
R"_([{"@type" = "caf::add_atom"}, 1, 2])_");
}
}
}
}
SCENARIO("config values can convert lists of tuples to dictionaries") {
GIVEN("a config value containing a list of key-value pairs (lists)") {
WHEN("calling as_dictionary on the object") {
THEN("the config value lifts the key-value pair list to a dictionary") {
auto x = make_config_value_list(make_config_value_list("one", 1),
make_config_value_list(2, "two"));
auto& dict = x.as_dictionary();
CHECK_EQ(dict.size(), 2u);
CHECK_EQ(dict["one"], 1);
CHECK_EQ(dict["2"], "two"s);
}
}
}
GIVEN("a config value containing a string representing a kvp list") {
WHEN("calling as_dictionary on the object") {
THEN("the config value lifts the key-value pair list to a dictionary") {
auto x = config_value{R"_([["one", 1], [2, "two"]])_"};
auto& dict = x.as_dictionary();
CHECK_EQ(dict.size(), 2u);
CHECK_EQ(dict["one"], 1);
CHECK_EQ(dict["2"], "two"s);
}
}
}
}
SCENARIO("config values can parse messages") {
using testee_t = typed_actor<result<void>(int16_t), //
result<void>(int32_t, int32_t), //
result<void>(my_request), //
result<void>(add_atom, int32_t, int32_t)>; //
auto parse = [](string_view str) {
testee_t testee;
return config_value::parse_msg(str, testee);
};
GIVEN("a typed actor handle and valid input strings") {
THEN("config_value::parse_msg generates matching message types") {
if (auto msg = parse("16000"); CHECK(msg)) {
if (CHECK((msg->match_elements<int16_t>()))) {
CHECK_EQ(msg->get_as<int16_t>(0), 16000);
}
}
if (auto msg = parse("[16000]"); CHECK(msg)) {
if (CHECK((msg->match_elements<int16_t>()))) {
CHECK_EQ(msg->get_as<int16_t>(0), 16000);
}
}
if (auto msg = parse("[1, 2]"); CHECK(msg)) {
if (CHECK((msg->match_elements<int32_t, int32_t>()))) {
CHECK_EQ(msg->get_as<int32_t>(0), 1);
CHECK_EQ(msg->get_as<int32_t>(1), 2);
}
}
if (auto msg = parse("{a = 1, b = 2}"); CHECK(msg)) {
if (CHECK((msg->match_elements<my_request>()))) {
CHECK_EQ(msg->get_as<my_request>(0), my_request(1, 2));
}
}
if (auto msg = parse("[{a = 1, b = 2}]"); CHECK(msg)) {
if (CHECK((msg->match_elements<my_request>()))) {
CHECK_EQ(msg->get_as<my_request>(0), my_request(1, 2));
}
}
if (auto msg = parse(R"_([{"@type" = "caf::add_atom"}, 1, 2])_");
CHECK(msg)) {
if (CHECK((msg->match_elements<add_atom, int32_t, int32_t>()))) {
CHECK_EQ(msg->get_as<int32_t>(1), 1);
CHECK_EQ(msg->get_as<int32_t>(2), 2);
}
}
}
}
GIVEN("a typed actor handle and invalid input strings") {
THEN("config_value::parse_msg returns nullopt") {
CHECK(!parse("65000"));
CHECK(!parse("[1, 2, 3]"));
CHECK(!parse("[{a = 1.1, b = 2.2}]"));
}
}
}
CAF_TEST(default_constructed) {
config_value x;
CAF_CHECK_EQUAL(holds_alternative<int64_t>(x), true);
CAF_CHECK_EQUAL(get<int64_t>(x), 0);
CAF_CHECK_EQUAL(x.type_name(), "integer"s);
CAF_CHECK_EQUAL(holds_alternative<none_t>(x), true);
CAF_CHECK_EQUAL(x.type_name(), "none"s);
}
CAF_TEST(positive integer) {
......@@ -441,70 +1184,4 @@ CAF_TEST(conversion to std::unordered_multimap) {
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
namespace {
struct point_3d {
int32_t x;
int32_t y;
int32_t z;
};
[[maybe_unused]] bool operator==(const point_3d& x, const point_3d& y) {
return std::tie(x.x, x.y, x.z) == std::tie(y.x, y.y, y.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 {
point_3d p1;
point_3d p2;
};
[[maybe_unused]] bool operator==(const line& x, const line& y) {
return std::tie(x.p1, x.p2) == std::tie(y.p1, y.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));
}
} // namespace
CAF_TEST(config values pick up user defined inspect overloads) {
CAF_MESSAGE("users can fill dictionaries with field contents");
{
config_value x;
auto& dict = x.as_dictionary();
put(dict, "p1.x", 1);
put(dict, "p1.y", 2);
put(dict, "p1.z", 3);
put(dict, "p2.x", 10);
put(dict, "p2.y", 20);
put(dict, "p2.z", 30);
auto l = get_if<line>(&x);
if (CAF_CHECK_NOT_EQUAL(l, none))
CAF_CHECK_EQUAL(*l, (line{{1, 2, 3}, {10, 20, 30}}));
}
CAF_MESSAGE("users can pass objects as dictionaries on the command line");
{
auto val = config_value::parse("{p1{x=1,y=2,z=3},p2{x=10,y=20,z=30}}");
CAF_CHECK(val);
if (val) {
auto l = get_if<line>(std::addressof(*val));
if (CAF_CHECK_NOT_EQUAL(l, none))
CAF_CHECK_EQUAL(*l, (line{{1, 2, 3}, {10, 20, 30}}));
}
}
CAF_MESSAGE("value readers appear as inspectors with human-readable format");
{
config_value x{std::string{"saturday"}};
auto val = get_if<weekday>(&x);
if (CAF_CHECK(val))
CAF_CHECK_EQUAL(*val, weekday::saturday);
}
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -83,7 +83,7 @@ struct i32_wrapper {
template <class Inspector>
friend bool inspect(Inspector& f, i32_wrapper& x) {
return f.object(x).fields(f.field("value", x.value));
return f.apply(x.value);
}
};
......@@ -97,19 +97,27 @@ struct i64_wrapper {
++instances;
}
explicit i64_wrapper(int64_t val) : value(val) {
++instances;
}
~i64_wrapper() {
--instances;
}
template <class Inspector>
friend bool inspect(Inspector& f, i64_wrapper& x) {
return f.object(x).fields(f.field("value", x.value));
return f.apply(x.value);
}
};
struct my_request {
int32_t a;
int32_t b;
int32_t a = 0;
int32_t b = 0;
my_request() = default;
my_request(int a, int b) : a(a), b(b) {
// nop
}
};
[[maybe_unused]] inline bool operator==(const my_request& x,
......
......@@ -145,7 +145,7 @@ const auto conf0_log = make_log(
"key: foo=bar",
"{",
"key: foo",
"value (string): \"bar\"",
"value (string): bar",
"}",
"key: 1group",
"{",
......@@ -162,7 +162,7 @@ const auto conf0_log = make_log(
"key: padding",
"value (integer): 10",
"key: file-name",
"value (string): \"foobar.ini\"",
"value (string): foobar.ini",
"}",
"key: scheduler",
"{",
......@@ -184,7 +184,7 @@ const auto conf0_log = make_log(
"value (integer): 23",
"value (integer): 2",
"value (integer): 4",
"value (string): \"abc\"",
"value (string): abc",
"]",
"key: some-map",
"{",
......@@ -193,7 +193,7 @@ const auto conf0_log = make_log(
"key: entry2",
"value (integer): 23",
"key: entry3",
"value (string): \"abc\"",
"value (string): abc",
"}",
"key: middleman",
"{",
......
......@@ -58,7 +58,7 @@ struct testee : deserializer {
return false;
}
bool begin_object(string_view object_name) override {
bool begin_object(type_id_t, string_view object_name) override {
new_line();
indent += 2;
log += "begin object ";
......
......@@ -49,15 +49,7 @@ struct testee : serializer {
log.insert(log.end(), indent, ' ');
}
bool inject_next_object_type(type_id_t type) override {
new_line();
log += "next object type: ";
auto tn = detail::global_meta_object(type)->type_name;
log.insert(log.end(), tn.begin(), tn.end());
return true;
}
bool begin_object(string_view object_name) override {
bool begin_object(type_id_t, string_view object_name) override {
new_line();
indent += 2;
log += "begin object ";
......@@ -734,18 +726,11 @@ end object)_");
f.set_has_human_readable_format(true);
CAF_CHECK(inspect(f, x));
CAF_CHECK_EQUAL(f.log, R"_(
begin object message
begin field values
begin sequence of size 3
next object type: int32_t
int32_t value
next object type: std::string
std::string value
next object type: double
double value
end sequence
end field
end object)_");
begin sequence of size 3
int32_t value
std::string value
double value
end sequence)_");
}
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