llarp/config/definition.hpp
Namespaces
| Name |
|---|
| llarp [crypto.hpp] |
| llarp::config |
Classes
| Name | |
|---|---|
| struct | llarp::config::option_flag |
| struct | llarp::config::Required_t |
| struct | llarp::config::Hidden_t |
| struct | llarp::config::MultiValue_t |
| struct | llarp::config::RelayOnly_t |
| struct | llarp::config::ClientOnly_t |
| struct | llarp::config::Deprecated_t |
| struct | llarp::config::Default Wrapper to specify a default value to an OptionDefinition. |
| struct | llarp::config::Comment Adds one or more comment lines to the option definition. |
| struct | llarp::config::Env Fetches config from environmental variable. |
| struct | llarp::OptionDefinitionBase A base class for specifying config options and their constraints. |
| struct | llarp::OptionDefinition The primary type-aware implementation of OptionDefinitionBase, this templated class allows for implementations which can use fmt::format for conversion to string and std::istringstream for input from string. |
| struct | llarp::ConfigDefinition A ConfigDefinition holds an ordered set of OptionDefinitions defining the allowable values and their constraints (specified through calls to defineOption()). |
Source code
#pragma once
#include <fmt/core.h>
#include <initializer_list>
#include <type_traits>
#include <llarp/util/str.hpp>
#include <llarp/util/fs.hpp>
#include <iostream>
#include <memory>
#include <set>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
#include <vector>
#include <functional>
#include <optional>
#include <cassert>
namespace llarp
{
namespace config
{
// Base class for the following option flag types
struct option_flag
{};
struct Required_t : option_flag
{};
struct Hidden_t : option_flag
{};
struct MultiValue_t : option_flag
{};
struct RelayOnly_t : option_flag
{};
struct ClientOnly_t : option_flag
{};
struct Deprecated_t : option_flag
{};
inline constexpr Required_t Required{};
inline constexpr Hidden_t Hidden{};
inline constexpr MultiValue_t MultiValue{};
inline constexpr RelayOnly_t RelayOnly{};
inline constexpr ClientOnly_t ClientOnly{};
inline constexpr Deprecated_t Deprecated{};
template <typename T>
struct Default
{
T val;
constexpr explicit Default(T val) : val{std::move(val)}
{}
};
struct Comment
{
std::vector<std::string> comments;
explicit Comment(std::initializer_list<std::string> comments) : comments{std::move(comments)}
{}
};
struct Env
{
std::function<std::optional<std::string_view>(std::string_view)> get_env;
std::string varname;
template <typename GetEnv>
constexpr Env(std::string_view _varname, const GetEnv& _get_env) : varname{_varname}
{
get_env = [&_get_env](auto val) -> auto { return _get_env(val); };
}
std::optional<std::string_view>
operator()() const
{
return get_env(varname);
}
};
template <typename T>
auto
AssignmentAcceptor(T& ref)
{
return [&ref](T arg) { ref = std::move(arg); };
}
// C++20 backport:
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T>
constexpr bool is_default = false;
template <typename T>
constexpr bool is_default<Default<T>> = true;
template <typename U>
constexpr bool is_default<U&> = is_default<remove_cvref_t<U>>;
template <typename T>
constexpr bool is_default_array = false;
template <typename T, size_t N>
constexpr bool is_default_array<std::array<Default<T>, N>> = true;
template <typename U>
constexpr bool is_default_array<U&> = is_default_array<remove_cvref_t<U>>;
template <typename T>
constexpr bool is_env_v = std::is_same_v<Env, remove_cvref_t<T>>;
template <typename T, typename Option>
constexpr bool is_option = std::is_base_of_v<option_flag, remove_cvref_t<Option>>
or std::is_same_v<Comment, Option> or is_default<Option> or is_default_array<Option>
or std::is_invocable_v<remove_cvref_t<Option>, T> or is_env_v<Option>;
} // namespace config
struct OptionDefinitionBase
{
template <typename... T>
OptionDefinitionBase(std::string section_, std::string name_, const T&...)
: section(std::move(section_))
, name(std::move(name_))
, required{(std::is_same_v<T, config::Required_t> || ...)}
, multiValued{(std::is_same_v<T, config::MultiValue_t> || ...)}
, deprecated{(std::is_same_v<T, config::Deprecated_t> || ...)}
, hidden{deprecated || (std::is_same_v<T, config::Hidden_t> || ...)}
, relayOnly{(std::is_same_v<T, config::RelayOnly_t> || ...)}
, clientOnly{(std::is_same_v<T, config::ClientOnly_t> || ...)}
{}
virtual ~OptionDefinitionBase() = default;
virtual std::vector<std::string>
defaultValuesAsString() = 0;
virtual void
parseValue(const std::string& input) = 0;
virtual size_t
getNumberFound() const = 0;
virtual std::vector<std::string>
valuesAsString() = 0;
virtual void
tryAccept() const = 0;
std::string section;
std::string name;
bool required = false;
bool multiValued = false;
bool deprecated = false;
bool hidden = false;
bool relayOnly = false;
bool clientOnly = false;
// Temporarily holds comments given during construction until the option is actually added to
// the owning ConfigDefinition.
std::vector<std::string> comments;
};
template <typename T>
struct OptionDefinition : public OptionDefinitionBase
{
template <
typename... Options,
std::enable_if_t<(config::is_option<T, Options> && ...), int> = 0>
OptionDefinition(std::string section_, std::string name_, Options&&... opts)
: OptionDefinitionBase(section_, name_, opts...)
{
constexpr bool has_default =
((config::is_default_array<Options> || config::is_default<Options>) || ...);
constexpr bool has_required =
(std::is_same_v<config::remove_cvref_t<Options>, config::Required_t> || ...);
constexpr bool has_hidden =
(std::is_same_v<config::remove_cvref_t<Options>, config::Hidden_t> || ...);
static_assert(
not(has_default and has_required), "Default{...} and Required are mutually exclusive");
static_assert(not(has_hidden and has_required), "Hidden and Required are mutually exclusive");
(extractDefault(std::forward<Options>(opts)), ...);
(extractAcceptor(std::forward<Options>(opts)), ...);
(extractComments(std::forward<Options>(opts)), ...);
(extractEnv(std::forward<Options>(opts)), ...);
}
template <typename U>
void
extractEnv(U&& envValue)
{
if constexpr (config::is_env_v<U>)
{
if (auto maybe = envValue())
{
for (auto part : split(*maybe, ","))
{
trim(part);
envValues.emplace_back(fromString(std::string{part}));
}
}
}
}
template <typename U>
void
extractDefault(U&& defaultValue_)
{
if constexpr (config::is_default_array<U>)
{
if (!multiValued)
throw std::logic_error{"Array config defaults require multiValue mode"};
defaultValues.clear();
defaultValues.reserve(defaultValue_.size());
for (const auto& def : defaultValue_)
defaultValues.push_back(def.val);
}
else if constexpr (config::is_default<U>)
{
static_assert(
std::is_convertible_v<decltype(std::forward<U>(defaultValue_).val), T>,
"Cannot convert given llarp::config::Default to the required value type");
defaultValues = {std::forward<U>(defaultValue_).val};
}
}
template <typename U>
void
extractAcceptor(U&& acceptor_)
{
if constexpr (std::is_invocable_v<U, T>)
acceptor = std::forward<U>(acceptor_);
}
template <typename U>
void
extractComments(U&& comment)
{
if constexpr (std::is_same_v<config::remove_cvref_t<U>, config::Comment>)
comments = std::forward<U>(comment).comments;
}
std::optional<T>
getValue() const
{
// prefer env vars.
if (not envValues.empty())
return envValues.front();
if (parsedValues.empty())
{
if (required || defaultValues.empty())
return std::nullopt;
return defaultValues.front();
}
return parsedValues.front();
}
size_t
getNumberFound() const override
{
if (envValues.empty())
return parsedValues.size();
return envValues.size();
}
std::vector<std::string>
defaultValuesAsString() override
{
if (defaultValues.empty())
return {};
if constexpr (std::is_same_v<fs::path, T>)
return {{defaultValues.front().string()}};
else
{
std::vector<std::string> def_strs;
def_strs.reserve(defaultValues.size());
for (const auto& v : defaultValues)
{
if constexpr (std::is_same_v<bool, T>)
def_strs.push_back(fmt::format("{}", (bool)v));
else
def_strs.push_back(fmt::format("{}", v));
}
return def_strs;
}
}
void
parseValue(const std::string& input) override
{
if (not multiValued and parsedValues.size() > 0)
{
throw std::invalid_argument{fmt::format("duplicate value for {}", name)};
}
parsedValues.emplace_back(fromString(input));
}
T
fromString(const std::string& input)
{
if constexpr (std::is_same_v<T, std::string>)
{
return input;
}
else
{
std::istringstream iss(input);
T t;
iss >> t;
if (iss.fail())
throw std::invalid_argument{fmt::format("{} is not a valid {}", input, typeid(T).name())};
return t;
}
}
std::vector<std::string>
valuesAsString() override
{
if (parsedValues.empty())
return {};
std::vector<std::string> result;
result.reserve(parsedValues.size());
for (const auto& v : parsedValues)
{
if constexpr (std::is_same_v<bool, T>)
result.push_back(fmt::format("{}", (bool)v));
else
result.push_back(fmt::format("{}", v));
}
return result;
}
void
tryAccept() const override
{
if (required and parsedValues.empty() and envValues.empty())
{
throw std::runtime_error{fmt::format(
"cannot call tryAccept() on [{}]:{} when required but no value available",
section,
name)};
}
if (acceptor)
{
if (multiValued)
{
if (envValues.empty())
{
// add default value in multi value mode
if (parsedValues.empty() and not defaultValues.empty())
for (const auto& v : defaultValues)
acceptor(v);
for (auto value : parsedValues)
{
acceptor(value);
}
}
else
{
for (auto value : envValues)
{
acceptor(value);
}
}
}
else
{
auto maybe = getValue();
if (maybe)
acceptor(*maybe);
}
}
}
std::vector<T> defaultValues;
std::vector<T> parsedValues;
std::vector<T> envValues;
std::function<void(T)> acceptor;
};
template <>
bool
OptionDefinition<bool>::fromString(const std::string& input);
using UndeclaredValueHandler =
std::function<void(std::string_view section, std::string_view name, std::string_view value)>;
using OptionDefinition_ptr = std::unique_ptr<OptionDefinitionBase>;
// map of k:v pairs
using DefinitionMap = std::unordered_map<std::string, OptionDefinition_ptr>;
// map of section-name to map-of-definitions
using SectionMap = std::unordered_map<std::string, DefinitionMap>;
struct ConfigDefinition
{
explicit ConfigDefinition(bool relay) : relay{relay}
{}
ConfigDefinition&
defineOption(OptionDefinition_ptr def);
template <typename T, typename... Params>
ConfigDefinition&
defineOption(Params&&... args)
{
return defineOption(std::make_unique<OptionDefinition<T>>(std::forward<Params>(args)...));
}
ConfigDefinition&
addConfigValue(std::string_view section, std::string_view name, std::string_view value);
// provided
template <typename T>
std::optional<T>
getConfigValue(std::string_view section, std::string_view name)
{
OptionDefinition_ptr& definition = lookupDefinitionOrThrow(section, name);
auto derived = dynamic_cast<const OptionDefinition<T>*>(definition.get());
if (not derived)
throw std::invalid_argument{
fmt::format("{} is the incorrect type for [{}]:{}", typeid(T).name(), section, name)};
return derived->getValue();
}
void
addUndeclaredHandler(const std::string& section, UndeclaredValueHandler handler);
void
removeUndeclaredHandler(const std::string& section);
void
validateRequiredFields();
void
acceptAllOptions();
inline void
process()
{
validateRequiredFields();
acceptAllOptions();
}
void
addSectionComments(const std::string& section, std::vector<std::string> comments);
void
addOptionComments(
const std::string& section, const std::string& name, std::vector<std::string> comments);
std::string
generateINIConfig(bool useValues = false);
private:
// If true skip client-only options; if false skip relay-only options.
bool relay;
OptionDefinition_ptr&
lookupDefinitionOrThrow(std::string_view section, std::string_view name);
const OptionDefinition_ptr&
lookupDefinitionOrThrow(std::string_view section, std::string_view name) const;
using SectionVisitor = std::function<void(const std::string&, const DefinitionMap&)>;
void
visitSections(SectionVisitor visitor) const;
using DefVisitor = std::function<void(const std::string&, const OptionDefinition_ptr&)>;
void
visitDefinitions(const std::string& section, DefVisitor visitor) const;
SectionMap m_definitions;
std::unordered_map<std::string, UndeclaredValueHandler> m_undeclaredHandlers;
// track insertion order. the vector<string>s are ordered list of section/option names.
std::vector<std::string> m_sectionOrdering;
std::unordered_map<std::string, std::vector<std::string>> m_definitionOrdering;
// comments for config file generation
using CommentList = std::vector<std::string>;
using CommentsMap = std::unordered_map<std::string, CommentList>;
CommentsMap m_sectionComments;
std::unordered_map<std::string, CommentsMap> m_definitionComments;
};
} // namespace llarp
Updated on 2026-01-10 at 22:49:45 +0000