Unverified Commit c525c605 authored by Joseph Noir's avatar Joseph Noir Committed by GitHub

Merge pull request #931

Fix several issues with config values
parents 11c4eaca 760b0989
...@@ -415,14 +415,14 @@ private: ...@@ -415,14 +415,14 @@ private:
const settings& content(const actor_system_config& cfg); const settings& content(const actor_system_config& cfg);
/// Tries to retrieve the value associated to `name` from `cfg`. /// Tries to retrieve the value associated to `name` from `cfg`.
/// @relates config_value /// @relates actor_system_config
template <class T> template <class T>
optional<T> get_if(const actor_system_config* cfg, string_view name) { optional<T> get_if(const actor_system_config* cfg, string_view name) {
return get_if<T>(&content(*cfg), name); return get_if<T>(&content(*cfg), name);
} }
/// Retrieves the value associated to `name` from `cfg`. /// Retrieves the value associated to `name` from `cfg`.
/// @relates config_value /// @relates actor_system_config
template <class T> template <class T>
T get(const actor_system_config& cfg, string_view name) { T get(const actor_system_config& cfg, string_view name) {
return get<T>(content(cfg), name); return get<T>(content(cfg), name);
...@@ -430,7 +430,7 @@ T get(const actor_system_config& cfg, string_view name) { ...@@ -430,7 +430,7 @@ T get(const actor_system_config& cfg, string_view name) {
/// Retrieves the value associated to `name` from `cfg` or returns /// Retrieves the value associated to `name` from `cfg` or returns
/// `default_value`. /// `default_value`.
/// @relates config_value /// @relates actor_system_config
template <class T, class = typename std::enable_if< template <class T, class = typename std::enable_if<
!std::is_pointer<T>::value !std::is_pointer<T>::value
&& !std::is_convertible<T, string_view>::value>::type> && !std::is_convertible<T, string_view>::value>::type>
...@@ -440,10 +440,17 @@ T get_or(const actor_system_config& cfg, string_view name, T default_value) { ...@@ -440,10 +440,17 @@ T get_or(const actor_system_config& cfg, string_view name, T default_value) {
/// Retrieves the value associated to `name` from `cfg` or returns /// Retrieves the value associated to `name` from `cfg` or returns
/// `default_value`. /// `default_value`.
/// @relates config_value /// @relates actor_system_config
inline std::string get_or(const actor_system_config& cfg, string_view name, inline std::string get_or(const actor_system_config& cfg, string_view name,
string_view default_value) { string_view default_value) {
return get_or(content(cfg), name, default_value); return get_or(content(cfg), name, default_value);
} }
/// Returns whether `xs` associates a value of type `T` to `name`.
/// @relates actor_system_config
template <class T>
bool holds_alternative(const actor_system_config& cfg, string_view name) {
return holds_alternative<T>(content(cfg), name);
}
} // namespace caf } // namespace caf
...@@ -259,6 +259,7 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(bool, "boolean"); ...@@ -259,6 +259,7 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(bool, "boolean");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(double, "real64"); CAF_DEFAULT_CONFIG_VALUE_ACCESS(double, "real64");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(atom_value, "atom"); CAF_DEFAULT_CONFIG_VALUE_ACCESS(atom_value, "atom");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(timespan, "timespan"); CAF_DEFAULT_CONFIG_VALUE_ACCESS(timespan, "timespan");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(uri, "uri");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::list, "list"); CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::list, "list");
CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary, "dictionary"); CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary, "dictionary");
......
...@@ -33,12 +33,6 @@ public: ...@@ -33,12 +33,6 @@ public:
// -- observers -------------------------------------------------------------- // -- observers --------------------------------------------------------------
/// Checks whether `x` matches the type of this field.
virtual bool type_check(const config_value& x) const noexcept = 0;
/// Returns whether this field in `object` contains valid data.
virtual bool valid(const Object& object) const noexcept = 0;
/// Returns whether this field has a default value. /// Returns whether this field has a default value.
virtual bool has_default() const noexcept = 0; virtual bool has_default() const noexcept = 0;
...@@ -48,11 +42,14 @@ public: ...@@ -48,11 +42,14 @@ public:
/// Returns the value of this field in `object` as config value. /// Returns the value of this field in `object` as config value.
virtual config_value get(const Object& object) const = 0; virtual config_value get(const Object& object) const = 0;
/// Returns whether calling `set` with `x` would succeed.
virtual bool valid_input(const config_value& x) const = 0;
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Sets this field in `object` to `x`. /// Tries to set this field in `object` to `x`.
/// @pre `can_set(x)` /// @returns `true` on success, `false` otherwise.
virtual void set(Object& object, const config_value& x) const = 0; virtual bool set(Object& object, const config_value& x) const = 0;
/// Restores the default value for this field in `object`. /// Restores the default value for this field in `object`.
/// @pre `has_default()` /// @pre `has_default()`
......
...@@ -48,17 +48,19 @@ struct config_value_object_access { ...@@ -48,17 +48,19 @@ struct config_value_object_access {
if (!dict) if (!dict)
return false; return false;
for (auto field : Trait::fields()) { for (auto field : Trait::fields()) {
auto value = caf::get_if(dict, field->name()); if (auto value = caf::get_if(dict, field->name())) {
if (!value) { if (dst) {
if (!field->set(*dst, *value))
return false;
} else {
if (!field->valid_input(*value))
return false;
}
} else {
if (!field->has_default()) if (!field->has_default())
return false; return false;
if (dst) if (dst)
field->set_default(*dst); field->set_default(*dst);
} else if (field->type_check(*value)) {
if (dst)
field->set(*dst, *value);
} else {
return false;
} }
} }
return true; return true;
...@@ -156,10 +158,6 @@ struct config_value_object_access { ...@@ -156,10 +158,6 @@ struct config_value_object_access {
ps.code = pec::unexpected_eof; ps.code = pec::unexpected_eof;
return; return;
} }
if (!fptr->valid(x)) {
ps.code = pec::illegal_argument;
return;
}
result[fptr->name()] = fptr->get(x); result[fptr->name()] = fptr->get(x);
} while (ps.consume(',')); } while (ps.consume(','));
if (!ps.consume('}')) { if (!ps.consume('}')) {
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/config_value.hpp"
#include "caf/config_value_field.hpp"
#include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/optional.hpp"
namespace caf {
namespace detail {
template <class Object, class Value>
class config_value_field_base : public config_value_field<Object> {
public:
using super = config_value_field<Object>;
using object_type = typename super::object_type;
using value_type = Value;
using predicate_type = bool (*)(const value_type&);
config_value_field_base(string_view name, optional<value_type> default_value,
predicate_type predicate)
: name_(name),
default_value_(std::move(default_value)),
predicate_(predicate) {
// nop
}
config_value_field_base(config_value_field_base&&) = default;
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
}
string_view name() const noexcept override {
return name_;
}
config_value get(const object_type& object) const override {
using access = caf::select_config_value_access_t<value_type>;
return config_value{access::convert(get_value(object))};
}
bool valid_input(const config_value& x) const override {
if (!predicate_)
return holds_alternative<value_type>(x);
if (auto value = get_if<value_type>(&x))
return predicate_(*value);
return false;
}
bool set(object_type& x, const config_value& y) const override {
if (auto value = get_if<value_type>(&y)) {
if (predicate_ && !predicate_(*value))
return false;
set_value(x, move_if_optional(value));
return true;
}
return false;
}
void set_default(object_type& x) const override {
set_value(x, *default_value_);
}
void parse_cli(string_parser_state& ps, object_type& x,
const char* char_blacklist) const override {
value_type tmp;
dispatch_parse_cli(ps, tmp, char_blacklist);
if (ps.code <= pec::trailing_character) {
if (predicate_ && !predicate_(tmp))
ps.code = pec::illegal_argument;
else
set_value(x, std::move(tmp));
}
}
virtual const value_type& get_value(const object_type& object) const = 0;
virtual void set_value(object_type& object, value_type value) const = 0;
protected:
string_view name_;
optional<value_type> default_value_;
predicate_type predicate_;
};
} // namespace detail
} // namespace caf
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/config_value_field.hpp" #include "caf/config_value_field.hpp"
#include "caf/detail/config_value_field_base.hpp"
#include "caf/detail/dispatch_parse_cli.hpp" #include "caf/detail/dispatch_parse_cli.hpp"
#include "caf/detail/parse.hpp" #include "caf/detail/parse.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
...@@ -38,157 +39,110 @@ class config_value_field_impl; ...@@ -38,157 +39,110 @@ class config_value_field_impl;
// A config value with direct access to a field via member object pointer. // A config value with direct access to a field via member object pointer.
template <class Value, class Object> template <class Value, class Object>
class config_value_field_impl<Value Object::*> class config_value_field_impl<Value Object::*>
: public config_value_field<Object> { : public config_value_field_base<Object, Value> {
public: public:
using super = config_value_field_base<Object, Value>;
using member_pointer = Value Object::*; using member_pointer = Value Object::*;
using object_type = Object; using object_type = Object;
using value_type = Value; using value_type = Value;
using predicate_pointer = bool (*)(const value_type&); using predicate_type = bool (*)(const value_type&);
constexpr config_value_field_impl(string_view name, member_pointer ptr, constexpr config_value_field_impl(string_view name, member_pointer ptr,
optional<value_type> default_value = none, optional<value_type> default_value = none,
predicate_pointer predicate = nullptr) predicate_type predicate = nullptr)
: name_(name), : super(name, std::move(default_value), predicate), ptr_(ptr) {
ptr_(ptr),
default_value_(std::move(default_value)),
predicate_(predicate) {
// nop // nop
} }
constexpr config_value_field_impl(config_value_field_impl&&) = default; constexpr config_value_field_impl(config_value_field_impl&&) = default;
bool type_check(const config_value& x) const noexcept override { const value_type& get_value(const object_type& x) const override {
return holds_alternative<value_type>(x); return x.*ptr_;
}
bool valid(const object_type& x) const noexcept override {
return predicate_ ? predicate_(x.*ptr_) : true;
}
void set(object_type& x, const config_value& y) const override {
x.*ptr_ = caf::get<value_type>(y);
}
config_value get(const object_type& x) const override {
using access = select_config_value_access_t<value_type>;
return config_value{access::convert(x.*ptr_)};
} }
void parse_cli(string_parser_state& ps, object_type& x, void set_value(object_type& x, value_type y) const override {
const char* char_blacklist) const override { x.*ptr_ = std::move(y);
detail::dispatch_parse_cli(ps, x.*ptr_, char_blacklist);
}
string_view name() const noexcept override {
return name_;
}
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
}
void set_default(object_type& x) const override {
x.*ptr_ = *default_value_;
} }
private: private:
string_view name_;
member_pointer ptr_; member_pointer ptr_;
optional<value_type> default_value_;
predicate_pointer predicate_;
}; };
template <class Get> template <class Get>
struct config_value_field_base { struct config_value_field_trait {
using trait = get_callable_trait_t<Get>; using trait = get_callable_trait_t<Get>;
static_assert(trait::num_args == 1, static_assert(trait::num_args == 1,
"Get must take exactly one argument (the object)"); "Get must take exactly one argument (the object)");
using arg_type = tl_head_t<typename trait::arg_types>; using get_argument_type = tl_head_t<typename trait::arg_types>;
using value_type = decay_t<typename trait::result_type>; using object_type = decay_t<get_argument_type>;
using type = config_value_field<decay_t<arg_type>>; using get_result_type = typename trait::result_type;
};
template <class Get> using value_type = decay_t<get_result_type>;
using config_value_field_base_t = typename config_value_field_base<Get>::type; };
// A config value with access to a field via getter and setter. // A config value with access to a field via getter and setter.
template <class Get, class Set> template <class Get, class Set>
class config_value_field_impl<std::pair<Get, Set>> class config_value_field_impl<std::pair<Get, Set>>
: public config_value_field_base_t<Get> {
: public config_value_field_base<
typename config_value_field_trait<Get>::object_type,
typename config_value_field_trait<Get>::value_type> {
public: public:
using super = config_value_field_base_t<Get>; using trait = config_value_field_trait<Get>;
using object_type = typename trait::object_type;
using object_type = typename super::object_type; using get_result_type = typename trait::get_result_type;
using get_trait = get_callable_trait_t<Get>; using value_type = typename trait::value_type;
using value_type = decay_t<typename get_trait::result_type>; using predicate_type = bool (*)(const value_type&);
using predicate_pointer = bool (*)(const value_type&); using super = config_value_field_base<object_type, value_type>;
constexpr config_value_field_impl(string_view name, Get getter, Set setter, constexpr config_value_field_impl(string_view name, Get getter, Set setter,
optional<value_type> default_value = none, optional<value_type> default_value = none,
predicate_pointer predicate = nullptr) predicate_type predicate = nullptr)
: name_(name), : super(name, std::move(default_value), predicate),
get_(std::move(getter)), get_(std::move(getter)),
set_(std::move(setter)), set_(std::move(setter)) {
default_value_(std::move(default_value)),
predicate_(predicate) {
// nop // nop
} }
constexpr config_value_field_impl(config_value_field_impl&&) = default; constexpr config_value_field_impl(config_value_field_impl&&) = default;
bool type_check(const config_value& x) const noexcept override { const value_type& get_value(const object_type& x) const override {
return holds_alternative<value_type>(x); bool_token<std::is_lvalue_reference<get_result_type>::value> token;
} return get_value_impl(x, token);
void set(object_type& x, const config_value& y) const override {
set_(x, caf::get<value_type>(y));
}
config_value get(const object_type& x) const override {
using access = select_config_value_access_t<value_type>;
return config_value{access::convert(get_(x))};
}
bool valid(const object_type& x) const noexcept override {
return predicate_ ? predicate_(get_(x)) : true;
} }
void parse_cli(string_parser_state& ps, object_type& x, void set_value(object_type& x, value_type y) const override {
const char* char_blacklist) const override { set_(x, std::move(y));
value_type tmp;
detail::dispatch_parse_cli(ps, tmp, char_blacklist);
if (ps.code <= pec::trailing_character)
set_(x, std::move(tmp));
} }
string_view name() const noexcept override { private:
return name_; template <class O>
} const value_type& get_value_impl(const O& x, std::true_type) const {
return get_(x);
bool has_default() const noexcept override {
return static_cast<bool>(default_value_);
} }
void set_default(object_type& x) const override { template <class O>
set_(x, *default_value_); const value_type& get_value_impl(const O& x, std::false_type) const {
dummy_ = get_(x);
return dummy_;
} }
private:
string_view name_;
Get get_; Get get_;
Set set_; Set set_;
optional<value_type> default_value_; mutable value_type dummy_;
predicate_pointer predicate_;
}; };
} // namespace detail } // namespace detail
......
...@@ -107,30 +107,11 @@ void parse_element(string_parser_state& ps, std::string& x, ...@@ -107,30 +107,11 @@ void parse_element(string_parser_state& ps, std::string& x,
template <class T> template <class T>
enable_if_t<!is_pair<T>::value> parse_element(string_parser_state& ps, T& x, enable_if_t<!is_pair<T>::value> parse_element(string_parser_state& ps, T& x,
const char*) { const char*);
parse(ps, x);
}
template <class First, class Second, size_t N> template <class First, class Second, size_t N>
void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp, void parse_element(string_parser_state& ps, std::pair<First, Second>& kvp,
const char (&char_blacklist)[N]) { const char (&char_blacklist)[N]);
static_assert(N > 0, "empty array");
// TODO: consider to guard the blacklist computation with
// `if constexpr (is_same_v<First, string>)` when switching to C++17.
char key_blacklist[N + 1];
if (N > 1)
memcpy(key_blacklist, char_blacklist, N - 1);
key_blacklist[N - 1] = '=';
key_blacklist[N] = '\0';
parse_element(ps, kvp.first, key_blacklist);
if (ps.code > pec::trailing_character)
return;
if (!ps.consume('=')) {
ps.code = pec::unexpected_character;
return;
}
parse_element(ps, kvp.second, char_blacklist);
}
template <class T> template <class T>
enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) { enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
...@@ -177,6 +158,33 @@ enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) { ...@@ -177,6 +158,33 @@ enable_if_tt<is_iterable<T>> parse(string_parser_state& ps, T& xs) {
ps.code = ps.at_end() ? pec::success : pec::trailing_character; ps.code = ps.at_end() ? pec::success : pec::trailing_character;
} }
template <class T>
enable_if_t<!is_pair<T>::value> parse_element(string_parser_state& ps, T& x,
const char*) {
parse(ps, x);
}
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]) {
static_assert(N > 0, "empty array");
// TODO: consider to guard the blacklist computation with
// `if constexpr (is_same_v<First, string>)` when switching to C++17.
char key_blacklist[N + 1];
if (N > 1)
memcpy(key_blacklist, char_blacklist, N - 1);
key_blacklist[N - 1] = '=';
key_blacklist[N] = '\0';
parse_element(ps, kvp.first, key_blacklist);
if (ps.code > pec::trailing_character)
return;
if (!ps.consume('=')) {
ps.code = pec::unexpected_character;
return;
}
parse_element(ps, kvp.second, char_blacklist);
}
// -- convenience functions ---------------------------------------------------- // -- convenience functions ----------------------------------------------------
template <class T> template <class T>
......
This diff is collapsed.
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "caf/test/dsl.hpp" #include "caf/test/dsl.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/config_option_set.hpp" #include "caf/config_option_set.hpp"
#include "caf/config_value_object_access.hpp" #include "caf/config_value_object_access.hpp"
...@@ -130,15 +131,12 @@ struct fixture { ...@@ -130,15 +131,12 @@ struct fixture {
foobar x; foobar x;
CAF_CHECK_EQUAL(foo_field.name(), "foo"); CAF_CHECK_EQUAL(foo_field.name(), "foo");
CAF_REQUIRE(foo_field.has_default()); CAF_REQUIRE(foo_field.has_default());
CAF_CHECK(foo_field.valid(x));
CAF_CHECK_EQUAL(foo_field.get(x), config_value(0)); CAF_CHECK_EQUAL(foo_field.get(x), config_value(0));
foo_field.set_default(x); foo_field.set_default(x);
CAF_CHECK_EQUAL(foo_field.get(x), config_value(42)); CAF_CHECK_EQUAL(foo_field.get(x), config_value(42));
CAF_CHECK(!foo_field.type_check(config_value(1.))); CAF_CHECK(!foo_field.valid_input(config_value(1.)));
CAF_CHECK(foo_field.type_check(config_value(-1))); CAF_CHECK(!foo_field.valid_input(config_value(-1)));
foo_field.set(x, config_value(-1)); CAF_CHECK(!foo_field.set(x, config_value(-1)));
CAF_CHECK_EQUAL(foo_field.get(x), config_value(-1));
CAF_CHECK(!foo_field.valid(x));
string_view input = "123"; string_view input = "123";
string_parser_state ps{input.begin(), input.end()}; string_parser_state ps{input.begin(), input.end()};
foo_field.parse_cli(ps, x); foo_field.parse_cli(ps, x);
...@@ -257,4 +255,65 @@ CAF_TEST(object access from CLI arguments - foobar_foobar) { ...@@ -257,4 +255,65 @@ CAF_TEST(object access from CLI arguments - foobar_foobar) {
fbfb({123, "hello"}, {1, "world!"})); fbfb({123, "hello"}, {1, "world!"}));
} }
namespace {
constexpr const char* config_text = R"__(
arg1 = {
foo = 42
bar = "Don't panic!"
}
arg2 = {
x = {
foo = 1
bar = "hello"
}
y = {
foo = 2
bar = "world"
}
}
)__";
struct test_config : actor_system_config {
test_config() {
opt_group{custom_options_, "global"}
.add(fb, "arg1,1", "some foobar")
.add(fbfb, "arg2,2", "somme foobar-foobar");
}
foobar fb;
foobar_foobar fbfb;
};
} // namespace
CAF_TEST(object access from actor system config - file input) {
test_config cfg;
std::istringstream in{config_text};
if (auto err = cfg.parse(0, nullptr, in))
CAF_FAIL("cfg.parse failed: " << cfg.render(err));
CAF_CHECK_EQUAL(cfg.fb.foo, 42);
CAF_CHECK_EQUAL(cfg.fb.bar, "Don't panic!");
CAF_CHECK_EQUAL(cfg.fbfb.x.foo, 1);
CAF_CHECK_EQUAL(cfg.fbfb.y.foo, 2);
CAF_CHECK_EQUAL(cfg.fbfb.x.bar, "hello");
CAF_CHECK_EQUAL(cfg.fbfb.y.bar, "world");
}
CAF_TEST(object access from actor system config - file input and arguments) {
std::vector<std::string> args{
"-2",
"{y = {bar = CAF, foo = 20}, x = {foo = 10, bar = hello}}",
};
test_config cfg;
std::istringstream in{config_text};
if (auto err = cfg.parse(std::move(args), in))
CAF_FAIL("cfg.parse failed: " << cfg.render(err));
CAF_CHECK_EQUAL(cfg.fb.foo, 42);
CAF_CHECK_EQUAL(cfg.fb.bar, "Don't panic!");
CAF_CHECK_EQUAL(cfg.fbfb.x.foo, 10);
CAF_CHECK_EQUAL(cfg.fbfb.y.foo, 20);
CAF_CHECK_EQUAL(cfg.fbfb.x.bar, "hello");
CAF_CHECK_EQUAL(cfg.fbfb.y.bar, "CAF");
}
CAF_TEST_FIXTURE_SCOPE_END() 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