Commit 6962996a authored by Dominik Charousset's avatar Dominik Charousset

Improve on-the-fly dictionary conversions

parent d3b18572
...@@ -130,7 +130,10 @@ public: ...@@ -130,7 +130,10 @@ public:
/// Returns the value as a list, converting it to one if needed. /// Returns the value as a list, converting it to one if needed.
list& as_list(); 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(); dictionary& as_dictionary();
/// Appends `x` to a list. Converts this config value to a list first by /// Appends `x` to a list. Converts this config value to a list first by
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "caf/dictionary.hpp" #include "caf/dictionary.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include <memory>
#include <stack> #include <stack>
#include <vector> #include <vector>
...@@ -169,6 +170,9 @@ private: ...@@ -169,6 +170,9 @@ private:
bool fetch_object_type(const settings* obj, type_id_t& type); bool fetch_object_type(const settings* obj, type_id_t& type);
stack_type st_; stack_type st_;
// Stores on-the-fly converted values.
std::vector<std::unique_ptr<config_value>> scratch_space_;
}; };
} // namespace caf } // namespace caf
...@@ -129,9 +129,15 @@ config_value::list& config_value::as_list() { ...@@ -129,9 +129,15 @@ config_value::list& config_value::as_list() {
} }
config_value::dictionary& config_value::as_dictionary() { config_value::dictionary& config_value::as_dictionary() {
if (!holds_alternative<dictionary>(*this)) if (auto dict = get_if<config_value::dictionary>(&data_)) {
*this = dictionary{}; return *dict;
return get<dictionary>(*this); } 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) { void config_value::append(config_value x) {
...@@ -366,12 +372,34 @@ expected<config_value::list> config_value::to_list() const { ...@@ -366,12 +372,34 @@ expected<config_value::list> config_value::to_list() const {
expected<config_value::dictionary> config_value::to_dictionary() const { expected<config_value::dictionary> config_value::to_dictionary() const {
using result_type = expected<dictionary>; using result_type = expected<dictionary>;
auto f = detail::make_overload( auto f = detail::make_overload(
no_conversions<dictionary, none_t, bool, integer, timespan, real, uri, no_conversions<dictionary, none_t, bool, integer, timespan, real, uri>(),
list>(), [](const list& x) {
[](const std::string& x) {
dictionary tmp; dictionary tmp;
if (detail::parse(x, tmp, detail::require_opening_char) == none) 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)}; 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 "; std::string msg = "cannot convert ";
detail::print_escaped(msg, x); detail::print_escaped(msg, x);
msg += " to a dictionary"; msg += " to a dictionary";
......
...@@ -179,10 +179,17 @@ bool config_value_reader::begin_object(type_id_t type, string_view) { ...@@ -179,10 +179,17 @@ bool config_value_reader::begin_object(type_id_t type, string_view) {
}, },
[this](const config_value* val) { [this](const config_value* val) {
if (auto obj = get_if<settings>(val)) { if (auto obj = get_if<settings>(val)) {
// Morph into an object. This value gets "consumed" by // Unbox the dictionary.
// begin_object/end_object.
st_.top() = obj; st_.top() = obj;
return true; 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 { } else {
emplace_error(sec::conversion_failed, "cannot read input as object"); emplace_error(sec::conversion_failed, "cannot read input as object");
return false; return false;
......
...@@ -719,6 +719,32 @@ SCENARIO("config values can parse their own to_string output") { ...@@ -719,6 +719,32 @@ SCENARIO("config values can parse their own to_string output") {
} }
} }
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);
}
}
}
}
CAF_TEST(default_constructed) { CAF_TEST(default_constructed) {
config_value x; config_value x;
CAF_CHECK_EQUAL(holds_alternative<none_t>(x), true); CAF_CHECK_EQUAL(holds_alternative<none_t>(x), true);
......
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