Add "undeclared value" handler to Configuration

This commit is contained in:
Stephen Shelton 2020-03-19 23:24:45 -06:00
parent e9708a5d1c
commit 9a1b7b20de
No known key found for this signature in database
GPG Key ID: EE4BADACCE8B631C
3 changed files with 166 additions and 3 deletions

View File

@ -36,12 +36,51 @@ Configuration::defineOption(ConfigDefinition_ptr def)
Configuration& Configuration&
Configuration::addConfigValue(string_view section, string_view name, string_view value) Configuration::addConfigValue(string_view section, string_view name, string_view value)
{ {
ConfigDefinition_ptr& definition = lookupDefinitionOrThrow(section, name); auto secItr = m_definitions.find(std::string(section));
if (secItr == m_definitions.end())
{
// fallback to undeclared handler if available
auto undItr = m_undeclaredHandlers.find(std::string(section));
if (undItr == m_undeclaredHandlers.end())
throw std::invalid_argument(stringify("no declared section [", section, "]"));
else
{
auto& handler = undItr->second;
handler(section, name, value);
return *this;
}
}
// section was valid, get definition by name
auto& sectionDefinitions = secItr->second;
auto defItr = sectionDefinitions.find(std::string(name));
if (defItr == sectionDefinitions.end())
throw std::invalid_argument(stringify("no declared option [", section, "]:", name));
ConfigDefinition_ptr& definition = defItr->second;
definition->parseValue(std::string(value)); definition->parseValue(std::string(value));
return *this; return *this;
} }
void
Configuration::addUndeclaredHandler(const std::string& section, UndeclaredValueHandler handler)
{
auto itr = m_undeclaredHandlers.find(section);
if (itr != m_undeclaredHandlers.end())
throw std::logic_error(stringify("section ", section, " already has a handler"));
m_undeclaredHandlers[section] = std::move(handler);
}
void
Configuration::removeUndeclaredHandler(const std::string& section)
{
auto itr = m_undeclaredHandlers.find(section);
if (itr != m_undeclaredHandlers.end())
m_undeclaredHandlers.erase(itr);
}
void void
Configuration::validateRequiredFields() Configuration::validateRequiredFields()
{ {
@ -110,7 +149,7 @@ Configuration::lookupDefinitionOrThrow(string_view section, string_view name) co
{ {
const auto sectionItr = m_definitions.find(std::string(section)); const auto sectionItr = m_definitions.find(std::string(section));
if (sectionItr == m_definitions.end()) if (sectionItr == m_definitions.end())
throw std::invalid_argument(stringify("No config section ", section)); throw std::invalid_argument(stringify("No config section [", section, "]"));
auto& sectionDefinitions = sectionItr->second; auto& sectionDefinitions = sectionItr->second;
const auto definitionItr = sectionDefinitions.find(std::string(name)); const auto definitionItr = sectionDefinitions.find(std::string(name));

View File

@ -177,6 +177,9 @@ namespace llarp
std::function<void(T)> acceptor; std::function<void(T)> acceptor;
}; };
using UndeclaredValueHandler
= std::function<void(string_view section, string_view name, string_view value)>;
using ConfigDefinition_ptr = std::unique_ptr<ConfigDefinitionBase>; using ConfigDefinition_ptr = std::unique_ptr<ConfigDefinitionBase>;
@ -201,7 +204,6 @@ namespace llarp
/// with defaults and optionally fields which have a specified value (values provided through /// with defaults and optionally fields which have a specified value (values provided through
/// calls to addConfigValue()). /// calls to addConfigValue()).
struct Configuration { struct Configuration {
SectionMap m_definitions;
/// Spefify the parameters and type of a configuration option. The parameters are members of /// Spefify the parameters and type of a configuration option. The parameters are members of
/// ConfigDefinitionBase; the type is inferred from ConfigDefinition's template parameter T. /// ConfigDefinitionBase; the type is inferred from ConfigDefinition's template parameter T.
@ -264,6 +266,25 @@ namespace llarp
return derived->getValue(); return derived->getValue();
} }
/// Add an "undeclared" handler for the given section. This is a handler that will be called
/// whenever a k:v pair is found that doesn't match a provided definition.
///
/// Any exception thrown by the handler will progagate back through the call to
/// addConfigValue().
///
/// @param section is the section for which any undeclared values will invoke the provided
/// handler
/// @param handler
/// @throws if there is already a handler for this section
void
addUndeclaredHandler(const std::string& section, UndeclaredValueHandler handler);
/// Removes an "undeclared" handler for the given section.
///
/// @param section is the section which we want to remove the handler for
void
removeUndeclaredHandler(const std::string& section);
/// Validate that all required fields are present. /// Validate that all required fields are present.
/// ///
/// @throws std::invalid_argument if configuration constraints are not met /// @throws std::invalid_argument if configuration constraints are not met
@ -303,6 +324,10 @@ namespace llarp
using DefVisitor = std::function<void(const std::string&, const ConfigDefinition_ptr&)>; using DefVisitor = std::function<void(const std::string&, const ConfigDefinition_ptr&)>;
void visitDefinitions(const std::string& section, DefVisitor visitor) const; void visitDefinitions(const std::string& section, DefVisitor visitor) const;
SectionMap m_definitions;
std::unordered_map<std::string, UndeclaredValueHandler> m_undeclaredHandlers;
// track insertion order // track insertion order
std::vector<std::string> m_sectionOrdering; std::vector<std::string> m_sectionOrdering;
std::unordered_map<std::string, std::vector<std::string>> m_definitionOrdering; std::unordered_map<std::string, std::vector<std::string>> m_definitionOrdering;

View File

@ -1,7 +1,10 @@
#include <config/definition.hpp> #include <config/definition.hpp>
#include <util/string_view.hpp>
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
using llarp::string_view;
TEST_CASE("ConfigDefinition int parse test", "[config]") TEST_CASE("ConfigDefinition int parse test", "[config]")
{ {
llarp::ConfigDefinition<int> def("foo", "bar", false, 42); llarp::ConfigDefinition<int> def("foo", "bar", false, 42);
@ -214,3 +217,99 @@ TEST_CASE("Configuration defineOptions passthrough test", "[config]")
config.defineOption<int>("foo", "bar", false, 1); config.defineOption<int>("foo", "bar", false, 1);
CHECK(config.getConfigValue<int>("foo", "bar") == 1); CHECK(config.getConfigValue<int>("foo", "bar") == 1);
} }
TEST_CASE("Configuration undeclared definition basic test", "[config]")
{
llarp::Configuration config;
bool invoked = false;
config.addUndeclaredHandler("foo", [&](string_view section, string_view name, string_view value) {
CHECK(section == "foo");
CHECK(name == "bar");
CHECK(value == "val");
invoked = true;
});
REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val"));
CHECK(invoked);
}
TEST_CASE("Configuration undeclared add more than once test", "[config]")
{
llarp::Configuration config;
std::string calledBy = "";
config.addUndeclaredHandler("foo", [&](string_view, string_view, string_view) {
calledBy = "a";
});
REQUIRE_THROWS_WITH(
config.addUndeclaredHandler("foo", [&](string_view, string_view, string_view) {
calledBy = "b";
}),
"section foo already has a handler");
REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val"));
CHECK(calledBy == "a");
}
TEST_CASE("Configuration undeclared add/remove test", "[config]")
{
llarp::Configuration config;
std::string calledBy = "";
// add...
REQUIRE_NOTHROW(config.addUndeclaredHandler("foo", [&](string_view, string_view, string_view) {
calledBy = "a";
}));
REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val"));
CHECK(calledBy == "a");
calledBy = "";
// ...then remove...
REQUIRE_NOTHROW(config.removeUndeclaredHandler("foo"));
CHECK_THROWS_WITH(
config.addConfigValue("foo", "bar", "val"),
"no declared section [foo]");
// ...then add again
REQUIRE_NOTHROW(config.addUndeclaredHandler("foo", [&](string_view, string_view, string_view) {
calledBy = "b";
}));
REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val"));
CHECK(calledBy == "b");
}
TEST_CASE("Configuration undeclared handler exception propagation test", "[config]")
{
llarp::Configuration config;
config.addUndeclaredHandler("foo", [](string_view, string_view, string_view) {
throw std::runtime_error("FAIL");
});
REQUIRE_THROWS_WITH(config.addConfigValue("foo", "bar", "val"), "FAIL");
}
TEST_CASE("Configuration undeclared handler wrong section", "[config]")
{
llarp::Configuration config;
config.addUndeclaredHandler("foo", [](string_view, string_view, string_view) {
throw std::runtime_error("FAIL");
});
REQUIRE_THROWS_WITH(config.addConfigValue("argle", "bar", "val"), "no declared section [argle]");
}