Commit 66a00503 authored by Dominik Charousset's avatar Dominik Charousset

Add config_value adaptors for complex user types

The recently added config_value_object_access makes it simple to add
custom types where each field maps naturally to a key/value entry.
However, not each type fits that model. For example, a custom duration
type might internally store only a single integer but use two key/value
entries in the config: a count and the resolution.

The new config_value_adaptor API enables such mappings by converting
between the user-defined type and a tuple that models the representation
in configuration files or on the CLI.
parent 7dd737e7
...@@ -50,11 +50,15 @@ ...@@ -50,11 +50,15 @@
#include "caf/config_option.hpp" #include "caf/config_option.hpp"
#include "caf/config_option_adder.hpp" #include "caf/config_option_adder.hpp"
#include "caf/config_value.hpp" #include "caf/config_value.hpp"
#include "caf/config_value_adaptor.hpp"
#include "caf/config_value_adaptor_access.hpp"
#include "caf/config_value_adaptor_field.hpp"
#include "caf/config_value_field.hpp" #include "caf/config_value_field.hpp"
#include "caf/config_value_object_access.hpp" #include "caf/config_value_object_access.hpp"
#include "caf/deep_to_string.hpp" #include "caf/deep_to_string.hpp"
#include "caf/defaults.hpp" #include "caf/defaults.hpp"
#include "caf/deserializer.hpp" #include "caf/deserializer.hpp"
#include "caf/detail/config_value_adaptor_field_impl.hpp"
#include "caf/downstream_msg.hpp" #include "caf/downstream_msg.hpp"
#include "caf/duration.hpp" #include "caf/duration.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 <array>
#include <tuple>
#include <type_traits>
#include "caf/config_value_adaptor_field.hpp"
#include "caf/detail/config_value_adaptor_field_impl.hpp"
#include "caf/detail/int_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/optional.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Interfaces between a user-defined type and CAF config values by going
/// through intermediate values.
template <class... Ts>
class config_value_adaptor {
public:
using value_type = std::tuple<Ts...>;
using indices = typename detail::il_indices<value_type>::type;
using array_type = std::array<config_value_field<value_type>*, sizeof...(Ts)>;
template <class U,
class = detail::enable_if_t<
!std::is_same<detail::decay_t<U>, config_value_adaptor>::value>,
class... Us>
config_value_adaptor(U&& x, Us&&... xs)
: fields_(std::forward<U>(x), std::forward<Us>(xs)...) {
init(indices{});
}
config_value_adaptor(config_value_adaptor&&) = default;
span<typename array_type::value_type> fields() {
return make_span(ptr_fields_);
}
private:
template <long... Pos>
void init(detail::int_list<Pos...>) {
ptr_fields_ = array_type{{&std::get<Pos>(fields_)...}};
}
typename detail::select_adaptor_fields<value_type, indices>::type fields_;
array_type ptr_fields_;
};
template <class... Ts>
config_value_adaptor<typename Ts::value_type...>
make_config_value_adaptor(Ts... fields) {
return {std::move(fields)...};
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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/config_value_object_access.hpp"
#include "caf/parser_state.hpp"
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Enables user-defined types in config files and on the CLI by converting
/// them to and from tuples. Wraps a `config_value_object_access` in order to
/// allow CAF to interact with the underlying tuple.
///
/// ~~
/// struct trait {
/// using value_type = ...;
///
/// using tuple_type = ...;
///
/// static config_value_adaptor<...> adaptor_ref();
///
/// static span<config_value_field<object_type>*> fields();
///
/// static void convert(const value_type& src, tuple_type& dst);
///
/// static void convert(const tuple_type& src, value_type& dst);
/// };
/// ~~
template <class Trait>
struct config_value_adaptor_access {
struct object_trait {
using object_type = typename Trait::tuple_type;
static std::string type_name() {
Trait::type_name();
}
static caf::span<config_value_field<object_type>*> fields() {
return Trait::adaptor_ref().fields();
}
};
using tuple_access = config_value_object_access<object_trait>;
using value_type = typename Trait::value_type;
using tuple_type = typename Trait::tuple_type;
static std::string type_name() {
return Trait::type_name();
}
static bool is(const config_value& x) {
return tuple_access::is(x);
}
static optional<value_type> get_if(const config_value* x) {
if (auto tmp = tuple_access::get_if(x)) {
value_type result;
convert(*tmp, result);
return result;
}
return none;
}
static value_type get(const config_value& x) {
auto tmp = tuple_access::get(x);
value_type result;
convert(tmp, result);
return result;
}
static void parse_cli(string_parser_state& ps, value_type& x) {
tuple_type tmp;
tuple_access::parse_cli(ps, tmp);
if (ps.code <= pec::trailing_character)
convert(tmp, x);
}
static void convert(const value_type& src, tuple_type& dst) {
Trait::convert(src, dst);
}
static void convert(const tuple_type& src, value_type& dst) {
Trait::convert(src, dst);
}
static config_value::dictionary convert(const value_type& x) {
tuple_type tmp;
convert(x, tmp);
return tuple_access::convert(tmp);
}
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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/optional.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// Describes a field of type T of an adaptor.
template <class T>
struct config_value_adaptor_field {
using value_type = T;
using predicate_function = bool (*)(const T&);
string_view name;
optional<T> default_value;
predicate_function predicate;
};
template <class T>
config_value_adaptor_field<T>
make_config_value_adaptor_field(string_view name,
optional<T> default_value = none,
bool (*predicate)(const T&) = nullptr) {
return {name, std::move(default_value), predicate};
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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 <cstddef>
#include <tuple>
#include "caf/config_value.hpp"
#include "caf/config_value_adaptor_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/string_view.hpp"
namespace caf {
namespace detail {
template <class T, size_t Pos>
class config_value_adaptor_field_impl
: public config_value_field_base<T,
typename std::tuple_element<Pos, T>::type> {
public:
using object_type = T;
using value_type = typename std::tuple_element<Pos, T>::type;
using field_type = config_value_adaptor_field<value_type>;
using predicate_type = bool (*)(const value_type&);
using super = config_value_field_base<object_type, value_type>;
explicit config_value_adaptor_field_impl(field_type x)
: super(x.name, std::move(x.default_value), x.predicate) {
// nop
}
config_value_adaptor_field_impl(config_value_adaptor_field_impl&&) = default;
const value_type& get_value(const object_type& x) const override {
return std::get<Pos>(x);
}
void set_value(object_type& x, value_type y) const override {
std::get<Pos>(x) = std::move(y);
}
};
template <class T, class Ps>
struct select_adaptor_fields;
template <class T, long... Pos>
struct select_adaptor_fields<T, detail::int_list<Pos...>> {
using type = std::tuple<config_value_adaptor_field_impl<T, Pos>...>;
};
} // namespace detail
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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. *
******************************************************************************/
#define CAF_SUITE config_value_adaptor
#include "caf/config_value_adaptor.hpp"
#include "caf/test/dsl.hpp"
#include "caf/config_value_adaptor_access.hpp"
using namespace caf;
namespace {
// We want to configure this type as follows:
// my-duration = {
// count = 1
// resolution = "s"
// }
struct my_duration {
public:
constexpr my_duration() noexcept : ns_(0) {
// nop
}
constexpr my_duration(const my_duration&) noexcept = default;
my_duration& operator=(const my_duration&) noexcept = default;
int64_t ns() const {
return ns_;
}
int64_t us() const {
return ns() / 1000;
}
int64_t ms() const {
return us() / 1000;
}
int64_t s() const {
return ms() / 1000;
}
static my_duration from_ns(int64_t count) {
my_duration result;
result.ns_ = count;
return result;
}
static my_duration from_us(int64_t count) {
return from_ns(count * 1000);
}
static my_duration from_ms(int64_t count) {
return from_us(count * 1000);
}
static my_duration from_s(int64_t count) {
return from_ms(count * 1000);
}
private:
int64_t ns_;
};
bool operator==(my_duration x, my_duration y) {
return x.ns() == y.ns();
}
std::string to_string(my_duration x) {
return std::to_string(x.ns()) + "ns";
}
struct my_duration_adaptor {
using value_type = my_duration;
using tuple_type = std::tuple<int64_t, std::string>;
static std::string type_name() noexcept {
return "my-duration";
}
static bool resolution_valid(const std::string& str) {
static constexpr string_view whitelist[] = {"s", "ms", "us", "ns"};
auto matches = [&](string_view x) { return str == x; };
return std::any_of(std::begin(whitelist), std::end(whitelist), matches);
}
static config_value_adaptor<int64_t, std::string>& adaptor_ref() {
static auto singleton = make_config_value_adaptor(
make_config_value_adaptor_field<int64_t>("count"),
make_config_value_adaptor_field<std::string>("resolution", none,
resolution_valid));
return singleton;
}
static void convert(const value_type& src, tuple_type& dst) {
int count = src.ns();
if (count / 1000 != 0) {
dst = std::tie(count, "ns");
return;
}
count /= 1000;
if (count / 1000 != 0) {
dst = std::tie(count, "us");
return;
}
count /= 1000;
if (count / 1000 != 0) {
dst = std::tie(count, "ms");
return;
}
count /= 1000;
dst = std::tie(count, "s");
}
static void convert(const tuple_type& src, value_type& dst) {
auto count = std::get<0>(src);
const auto& resolution = std::get<1>(src);
if (resolution == "ns")
dst = my_duration::from_ns(count);
else if (resolution == "us")
dst = my_duration::from_us(count);
else if (resolution == "ms")
dst = my_duration::from_ms(count);
else
dst = my_duration::from_s(count);
}
};
struct fixture {
config_option_set opts;
template <class T>
expected<T> read(std::vector<std::string> args) {
settings cfg;
auto res = opts.parse(cfg, args);
if (res.first != pec::success)
return make_error(res.first, *res.second);
auto x = get_if<T>(&cfg, "value");
if (x == none)
return sec::invalid_argument;
return *x;
};
};
} // namespace
namespace caf {
template <>
struct config_value_access<my_duration>
: config_value_adaptor_access<my_duration_adaptor> {};
} // namespace caf
CAF_TEST_FIXTURE_SCOPE(config_value_adaptor_tests, fixture)
CAF_TEST(holds_alternative) {
auto make_value = [](int64_t count, std::string resolution) {
settings x;
put(x, "count", count);
put(x, "resolution", std::move(resolution));
return config_value{std::move(x)};
};
CAF_CHECK(holds_alternative<my_duration>(make_value(1, "s")));
CAF_CHECK(holds_alternative<my_duration>(make_value(1, "ms")));
CAF_CHECK(holds_alternative<my_duration>(make_value(1, "us")));
CAF_CHECK(holds_alternative<my_duration>(make_value(1, "ns")));
CAF_CHECK(!holds_alternative<my_duration>(make_value(1, "foo")));
}
CAF_TEST(access from dictionary) {
settings x;
put(x, "value.count", 42);
put(x, "value.resolution", "s");
auto value = x["value"];
CAF_REQUIRE(holds_alternative<my_duration>(value));
CAF_CHECK_EQUAL(get_if<my_duration>(&value), my_duration::from_s(42));
CAF_CHECK_EQUAL(get<my_duration>(value), my_duration::from_s(42));
}
namespace {
constexpr const char* config_text = R"__(
max-delay = {
count = 123
resolution = "s"
}
)__";
struct test_config : actor_system_config {
test_config() {
opt_group{custom_options_, "global"}.add(max_delay, "max-delay,m",
"maximum delay");
}
my_duration max_delay;
};
} // namespace
CAF_TEST(adaptor 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.max_delay, my_duration::from_s(123));
}
CAF_TEST(adaptor access from actor system config - file input and arguments) {
std::vector<std::string> args{
"--max-delay={count = 20, resolution = ms}",
};
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.max_delay, my_duration::from_ms(20));
}
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