mirror of https://github.com/oxen-io/lokinet
Introduce acceptor function in ConfigDefinition
This commit is contained in:
parent
60d0bf2a9b
commit
02e31f3867
|
@ -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…
Reference in New Issue