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

Improve on-the-fly dictionary conversions

parent d3b18572
......@@ -130,7 +130,10 @@ public:
/// Returns the value as a list, converting it to one if needed.
list& as_list();
/// Returns the value as a dictionary, converting it to one if needed.
/// Returns the value as a dictionary, converting it to one if needed. The
/// only data structure that CAF can convert to a dictionary is a list of
/// lists, where each nested list contains exactly two elements (key and
/// value). In all other cases, the conversion results in an empty dictionary.
dictionary& as_dictionary();
/// Appends `x` to a list. Converts this config value to a list first by
......
......@@ -23,6 +23,7 @@
#include "caf/dictionary.hpp"
#include "caf/fwd.hpp"
#include <memory>
#include <stack>
#include <vector>
......@@ -169,6 +170,9 @@ private:
bool fetch_object_type(const settings* obj, type_id_t& type);
stack_type st_;
// Stores on-the-fly converted values.
std::vector<std::unique_ptr<config_value>> scratch_space_;
};
} // namespace caf
......@@ -129,9 +129,15 @@ config_value::list& config_value::as_list() {
}
config_value::dictionary& config_value::as_dictionary() {
if (!holds_alternative<dictionary>(*this))
*this = dictionary{};
return get<dictionary>(*this);
if (auto dict = get_if<config_value::dictionary>(&data_)) {
return *dict;
} else if (auto lifted = to_dictionary()) {
data_ = std::move(*lifted);
return get<config_value::dictionary>(data_);
} else {
data_ = config_value::dictionary{};
return get<config_value::dictionary>(data_);
}
}
void config_value::append(config_value x) {
......@@ -366,12 +372,34 @@ expected<config_value::list> config_value::to_list() const {
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) {
no_conversions<dictionary, none_t, bool, integer, timespan, real, uri>(),
[](const list& x) {
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)};
}
if (list tmp; detail::parse(x, tmp) == none) {
config_value ls{std::move(tmp)};
if (auto res = ls.to_dictionary())
return res;
}
std::string msg = "cannot convert ";
detail::print_escaped(msg, x);
msg += " to a dictionary";
......
......@@ -179,10 +179,17 @@ bool config_value_reader::begin_object(type_id_t type, string_view) {
},
[this](const config_value* val) {
if (auto obj = get_if<settings>(val)) {
// Morph into an object. This value gets "consumed" by
// begin_object/end_object.
// Unbox the dictionary.
st_.top() = obj;
return true;
} else if (auto dict = val->to_dictionary()) {
// Replace the actual config value on the stack with the on-the-fly
// converted dictionary.
auto ptr = std::make_unique<config_value>(std::move(*dict));
const settings* unboxed = std::addressof(get<settings>(*ptr));
st_.top() = unboxed;
scratch_space_.emplace_back(std::move(ptr));
return true;
} else {
emplace_error(sec::conversion_failed, "cannot read input as object");
return false;
......
......@@ -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) {
config_value x;
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