Introduce acceptor function in ConfigDefinition

pull/1186/head
Stephen Shelton 4 years ago
parent 60d0bf2a9b
commit 02e31f3867
No known key found for this signature in database
GPG Key ID: EE4BADACCE8B631C

@ -47,8 +47,10 @@ Configuration::addConfigValue(string_view section, string_view name, string_view
void
Configuration::validateRequiredFields()
{
visitSections([&](const std::string& section, const DefinitionMap&) {
visitDefinitions(section, [&](const std::string&, const ConfigDefinition_ptr& def) {
visitSections([&](const std::string& section, const DefinitionMap&)
{
visitDefinitions(section, [&](const std::string&, const ConfigDefinition_ptr& def)
{
if (def->required and def->numFound < 1)
{
throw std::invalid_argument(stringify(
@ -56,7 +58,19 @@ Configuration::validateRequiredFields()
}
// should be handled earlier in ConfigDefinition::parseValue()
assert(def->numFound == 1 or def->multiValued);
assert(def->numFound <= 1 or def->multiValued);
});
});
}
void
Configuration::acceptAllOptions()
{
visitSections([&](const std::string& section, const DefinitionMap&)
{
visitDefinitions(section, [&](const std::string&, const ConfigDefinition_ptr& def)
{
def->tryAccept();
});
});
}

@ -47,6 +47,11 @@ namespace llarp
virtual std::string
valueAsString(bool useDefault) = 0;
/// Subclassess should call their acceptor, if present. See ConfigDefinition for more details.
///
/// @throws if the acceptor throws or the option is required but missing
virtual void tryAccept() const = 0;
std::string section;
std::string name;
bool required = false;
@ -63,19 +68,25 @@ namespace llarp
template<typename T>
struct ConfigDefinition : public ConfigDefinitionBase
{
/// Constructor. Arguments are passed directly to ConfigDefinitionBase. Also accepts a default
/// value, which is used in the following situations:
/// Constructor. Arguments are passed directly to ConfigDefinitionBase.
///
/// @param defaultValue_ is used in the following situations:
/// 1) as the return value for getValue() if there is no parsed value and required==false
/// 2) as the output in defaultValueAsString(), used to generate config files
/// 3) as the output in valueAsString(), used to generate config files
///
/// @param acceptor_ is an optional function whose purpose is to both validate the parsed
/// input and internalize it (e.g. copy it for runtime use). The acceptor should throw
/// an exception with a useful message if it is not acceptable.
ConfigDefinition(std::string section_,
std::string name_,
bool required_,
bool multiValued_,
nonstd::optional<T> defaultValue_)
nonstd::optional<T> defaultValue_,
std::function<void(T)> acceptor_ = nullptr)
: ConfigDefinitionBase(section_, name_, required_, multiValued_)
, defaultValue(defaultValue_)
, acceptor(acceptor_)
{
}
@ -140,8 +151,32 @@ namespace llarp
return oss.str();
}
/// Attempts to call the acceptor function, if present. This function may throw if the value is
/// not acceptable. Additionally, tryAccept should not be called if the option is required and
/// no value has been provided.
///
/// @throws if required and no value present or if the acceptor throws
void
tryAccept() const override
{
if (required and not parsedValue.has_value())
{
throw std::runtime_error(stringify("cannot call tryAccept() on [",
section, "]:", name, " when required but no value available"));
}
if (acceptor)
{
auto maybe = getValue();
assert(maybe.has_value()); // should be guaranteed by our earlier check
// TODO: avoid copies here if possible
acceptor(maybe.value());
}
}
nonstd::optional<T> defaultValue;
nonstd::optional<T> parsedValue; // needs to be set when parseValue() called
std::function<void(T)> acceptor;
};
@ -228,6 +263,14 @@ namespace llarp
void
validateRequiredFields();
/// Accept all options. This will call the acceptor (if present) on each option. Note that this
/// should only be called if all required fields are present (that is, validateRequiredFields()
/// has been or could be called without throwing).
///
/// @throws if any option's acceptor throws
void
acceptAllOptions();
/// Generate a config string from the current config definition, optionally using overridden
/// values. The generated config will preserve insertion order of both sections and their
/// definitions.

@ -59,6 +59,43 @@ TEST_CASE("ConfigDefinition multiple parses test", "[config]")
}
TEST_CASE("ConfigDefinition acceptor test", "[config]")
{
int test = -1;
llarp::ConfigDefinition<int> def("foo", "bar", false, false, 42, [&](int arg) {
test = arg;
});
CHECK_NOTHROW(def.tryAccept());
CHECK(test == 42);
def.parseValue("43");
CHECK_NOTHROW(def.tryAccept());
CHECK(test == 43);
}
TEST_CASE("ConfigDefinition acceptor throws test", "[config]")
{
llarp::ConfigDefinition<int> def("foo", "bar", false, false, 42, [&](int arg) {
(void)arg;
throw std::runtime_error("FAIL");
});
REQUIRE_THROWS_WITH(def.tryAccept(), "FAIL");
}
TEST_CASE("ConfigDefinition tryAccept missing option test", "[config]")
{
int unset = -1;
llarp::ConfigDefinition<int> def("foo", "bar", true, false, 1, [&](int arg) {
(void)arg;
unset = 0; // should never be called
});
REQUIRE_THROWS_WITH(def.tryAccept(),
"cannot call tryAccept() on [foo]:bar when required but no value available");
}
TEST_CASE("Configuration basic add/get test", "[config]")
{
llarp::Configuration config;
@ -139,3 +176,38 @@ TEST_CASE("Configuration section test", "[config]")
CHECK(config.getConfigValue<int>("foo", "bar") == 5);
CHECK(config.getConfigValue<int>("goo", "bar") == 6);
}
TEST_CASE("Configuration acceptAllOptions test", "[config]")
{
int fooBar = -1;
std::string fooBaz = "";
llarp::Configuration config;
config.addConfigOption(std::make_unique<llarp::ConfigDefinition<int>>(
"foo", "bar", false, false, 1, [&](int arg) {
fooBar = arg;
}));
config.addConfigOption(std::make_unique<llarp::ConfigDefinition<std::string>>(
"foo", "baz", false, false, "no", [&](std::string arg) {
fooBaz = arg;
}));
config.addConfigValue("foo", "baz", "yes");
REQUIRE_NOTHROW(config.validateRequiredFields());
REQUIRE_NOTHROW(config.acceptAllOptions());
CHECK(fooBar == 1);
CHECK(fooBaz == "yes");
}
TEST_CASE("Configuration acceptAllOptions exception propagation test", "[config]")
{
llarp::Configuration config;
config.addConfigOption(std::make_unique<llarp::ConfigDefinition<int>>(
"foo", "bar", false, false, 1, [&](int arg) {
(void)arg;
throw std::runtime_error("FAIL");
}));
REQUIRE_THROWS_WITH(config.acceptAllOptions(), "FAIL");
}

Loading…
Cancel
Save