Unverified Commit 12bcfbc7 authored by Noir's avatar Noir Committed by GitHub

Merge pull request #1314

Fix handling of the global category in settings
parents 46a747ac 4d830beb
...@@ -27,6 +27,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -27,6 +27,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
destructor of the promise now checks for this case. destructor of the promise now checks for this case.
- Fix OpenSSL 3.0 warnings when building the OpenSSL module by switching to - Fix OpenSSL 3.0 warnings when building the OpenSSL module by switching to
newer EC-curve API. newer EC-curve API.
- When working with settings, `put`, `put_missing`, `get_if`, etc. now
gracefully handle the `global` category when explicitly using it.
### Changed ### Changed
......
...@@ -81,12 +81,7 @@ expected<T> get_as(const settings& xs, string_view name) { ...@@ -81,12 +81,7 @@ expected<T> get_as(const settings& xs, string_view name) {
} }
/// @private /// @private
CAF_CORE_EXPORT config_value& put_impl(settings& dict, CAF_CORE_EXPORT config_value& put_impl(settings& dict, string_view name,
const std::vector<string_view>& path,
config_value& value);
/// @private
CAF_CORE_EXPORT config_value& put_impl(settings& dict, string_view key,
config_value& value); config_value& value);
/// Converts `value` to a `config_value` and assigns it to `key`. /// Converts `value` to a `config_value` and assigns it to `key`.
......
...@@ -11,22 +11,32 @@ namespace caf { ...@@ -11,22 +11,32 @@ namespace caf {
// note: to_string is implemented in config_value.cpp // note: to_string is implemented in config_value.cpp
const config_value* get_if(const settings* xs, string_view name) { const config_value* get_if(const settings* xs, string_view name) {
// Access the key directly unless the user specified a dot-separated path. // The 'global' category is special in the sense that it refers back to the
auto pos = name.find('.'); // root. Somewhat like '::foo' in C++. This means we can just drop it here.
if (pos == std::string::npos) { using namespace caf::literals;
auto i = xs->find(name); auto gl = "global."_sv;
if (i == xs->end()) if (starts_with(name, gl))
return nullptr; name.remove_prefix(gl.size());
// We can't simply return the result here, because it might be a pointer. // Climb down the tree. In each step, we resolve `xs` and `name` to the next
return &i->second; // level until there is no category left to resolve. At that point it's a
// trivial name lookup.
for (;;) {
if (auto pos = name.find('.'); pos == std::string::npos) {
if (auto i = xs->find(name); i == xs->end())
return nullptr;
else
return &i->second;
} else {
auto category = name.substr(0, pos);
name.remove_prefix(pos + 1);
if (auto i = xs->find(category);
i == xs->end() || !holds_alternative<settings>(i->second)) {
return nullptr;
} else {
xs = std::addressof(get<settings>(i->second));
}
}
} }
// We're dealing with a `<category>.<key>`-formatted string, extract the
// sub-settings by category and recurse.
auto i = xs->find(name.substr(0, pos));
if (i == xs->end() || !holds_alternative<config_value::dictionary>(i->second))
return nullptr;
return get_if(&get<config_value::dictionary>(i->second),
name.substr(pos + 1));
} }
expected<std::string> get_or(const settings& xs, string_view name, expected<std::string> get_or(const settings& xs, string_view name,
...@@ -41,8 +51,7 @@ config_value& put_impl(settings& dict, const std::vector<string_view>& path, ...@@ -41,8 +51,7 @@ config_value& put_impl(settings& dict, const std::vector<string_view>& path,
config_value& value) { config_value& value) {
// Sanity check. // Sanity check.
CAF_ASSERT(!path.empty()); CAF_ASSERT(!path.empty());
// TODO: We implicitly swallow the `global.` suffix as a hotfix, but we // Like in get_if: we always drop a 'global.' suffix.
// actually should drop `global.` on the upper layers.
if (path.front() == "global") { if (path.front() == "global") {
std::vector<string_view> new_path{path.begin() + 1, path.end()}; std::vector<string_view> new_path{path.begin() + 1, path.end()};
return put_impl(dict, new_path, value); return put_impl(dict, new_path, value);
...@@ -67,10 +76,33 @@ config_value& put_impl(settings& dict, const std::vector<string_view>& path, ...@@ -67,10 +76,33 @@ config_value& put_impl(settings& dict, const std::vector<string_view>& path,
return iter->second; return iter->second;
} }
config_value& put_impl(settings& dict, string_view key, config_value& value) { config_value& put_impl(settings& dict, string_view name, config_value& value) {
std::vector<string_view> path; // Like in get_if: we always drop a 'global.' suffix.
split(path, key, "."); using namespace caf::literals;
return put_impl(dict, path, value); auto gl = "global."_sv;
if (starts_with(name, gl))
name.remove_prefix(gl.size());
// Climb down the tree, similar to get_if. Only this time, we create the
// necessary structure as we go until there is no category left to resolve. At
// that point it's a trivial insertion (override).
auto xs = &dict;
for (;;) {
if (auto pos = name.find('.'); pos == string_view::npos) {
return xs->insert_or_assign(name, std::move(value)).first->second;
} else {
auto category = name.substr(0, pos);
name.remove_prefix(pos + 1);
if (auto i = xs->find(category); i == xs->end()) {
auto j = xs->emplace(category, settings{}).first;
xs = std::addressof(get<settings>(j->second));
} else if (!holds_alternative<settings>(i->second)) {
i->second = settings{};
xs = std::addressof(get<settings>(i->second));
} else {
xs = std::addressof(get<settings>(i->second));
}
}
}
} }
config_value::list& put_list(settings& xs, std::string name) { config_value::list& put_list(settings& xs, std::string name) {
......
...@@ -171,4 +171,28 @@ CAF_TEST(read_config accepts the to_string output of settings) { ...@@ -171,4 +171,28 @@ CAF_TEST(read_config accepts the to_string output of settings) {
CHECK_EQ(x, y); CHECK_EQ(x, y);
} }
SCENARIO("put_missing normalizes 'global' suffixes") {
GIVEN("empty settings") {
settings uut;
WHEN("calling put_missing with and without 'global' suffix") {
THEN("put_missing drops the 'global' suffix") {
put_missing(uut, "global.foo", "bar"s);
CHECK_EQ(get_as<std::string>(uut, "foo"), "bar"s);
CHECK_EQ(get_as<std::string>(uut, "global.foo"), "bar"s);
}
}
}
GIVEN("settings with a value for 'foo'") {
settings uut;
uut["foo"] = "bar"s;
WHEN("calling put_missing 'global.foo'") {
THEN("the function call results in a no-op") {
put_missing(uut, "global.foo", "baz"s);
CHECK_EQ(get_as<std::string>(uut, "foo"), "bar"s);
CHECK_EQ(get_as<std::string>(uut, "global.foo"), "bar"s);
}
}
}
}
END_FIXTURE_SCOPE() END_FIXTURE_SCOPE()
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