Commit ab7535c7 authored by Dominik Charousset's avatar Dominik Charousset

Implement new get_as function for config_value

parent dc04d789
......@@ -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>;
......@@ -115,8 +123,8 @@ public:
// -- 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.
......@@ -152,6 +160,53 @@ 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
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()};
}
private:
// -- properties -------------------------------------------------------------
......@@ -159,6 +214,10 @@ private:
// -- auto conversion of related types ---------------------------------------
void set(none_t) {
data_ = none;
}
void set(bool x) {
data_ = x;
}
......@@ -217,6 +276,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 +295,232 @@ 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 {
using value_type = std::decay_t<Fallback>;
if (auto val = get_as<value_type>(x))
return std::move(*val);
else
return std::forward<Fallback>(fallback);
}
}
// -- SumType-like access ------------------------------------------------------
template <class T>
......@@ -900,6 +1188,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 +1224,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 +1303,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 +1325,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 +1333,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 +1353,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;
}
......
......@@ -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;
......
......@@ -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
......@@ -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*
......
......@@ -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() {
......@@ -126,6 +147,238 @@ const char* config_value::type_name_at_index(size_t index) noexcept {
return type_names[index];
}
// -- utility ------------------------------------------------------------------
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) {
// 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{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 f = detail::make_overload(
no_conversions<list, none_t, bool, integer, real, timespan, uri>(),
[](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();
for (const auto& [key, val] : dict) {
list kvp;
kvp.reserve(2);
kvp.emplace_back(key);
kvp.emplace_back(val);
tmp.emplace_back(std::move(kvp));
}
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}; },
[](const dictionary& x) {
list tmp;
for (const auto& [key, val] : x) {
list kvp;
kvp.reserve(2);
kvp.emplace_back(key);
kvp.emplace_back(val);
tmp.emplace_back(std::move(kvp));
}
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,
list>(),
[](const std::string& x) {
dictionary tmp;
if (detail::parse(x, tmp, detail::require_opening_char) == none)
return result_type{std::move(tmp)};
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_);
}
// -- related free functions ---------------------------------------------------
bool operator<(const config_value& x, const config_value& y) {
......@@ -143,10 +396,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 +415,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 +460,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;
}
......
......@@ -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;
}
......
......@@ -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('.');
......
......@@ -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,606 @@ 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 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 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 floating point 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") {
config_value x{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}));
}
}
}
}
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>({"a", "b"}));
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);
}
}
}
}
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 +1039,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()
......@@ -108,8 +108,12 @@ struct i64_wrapper {
};
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",
"{",
......
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