#pragma once #include #include #include #include template < typename T > constexpr bool is_static_string_v = std::is_base_of_v< rublon::details::StaticStringBase, T >; static_assert(is_static_string_v< rublon::StaticString< 32 > >); namespace rublon { class ConfigurationFactory; enum class FailMode { bypass, deny }; class Configuration { public: // change to StaticString StaticString< 32 > systemToken{}; StaticString< 32 > secretKey{}; StaticString< 4096 > apiServer{}; int prompt{}; bool enablePasswdEmail{}; bool logging{}; bool autopushPrompt{}; FailMode failMode{}; }; namespace { template < class C, typename T > T member_ptr_t(T C::*v); template < typename T > tl::expected< T, ConfigurationError > to(std::string_view); template < class T > auto to_string(std::string_view arg) -> tl::expected< T, ConfigurationError > { T value{}; assert(arg.size() <= (value.size() - 1)); std::memcpy(value.data(), arg.data(), arg.size()); return value; } template <> auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > { return conv::to_bool(arg); } template <> auto to(std::string_view arg) -> tl::expected< int, ConfigurationError > { return conv::to_uint32(arg).value_or(0); } template <> auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > { if(arg == "bypass") return FailMode::bypass; if(arg == "deny") return FailMode::deny; return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}}; } template < typename T > auto parse(std::string_view arg) -> tl::expected< T, ConfigurationError > { if(arg.empty()) { return tl::unexpected{ConfigurationError::ErrorClass::Empty}; } else { if constexpr(is_static_string_v< T >) { return to_string< T >(arg); } else { return to< T >(arg); } } } } // namespace struct Entry { enum class Source { UserInput, DefaultValue }; template < auto member > static constexpr auto make_read_function() { using pType = decltype(member_ptr_t(member)); return [](const Entry * _this, Configuration * configuration, std::string_view userInput) -> tl::expected< Source, ConfigurationError > { const auto setDefaultValue = [&](const ConfigurationError & error) -> tl::expected< Source, ConfigurationError > { log(LogLevel::Warning, "applying user provided value for %s parameter, faild with %s", _this->name, error.what()); if(_this->defaultValue != nullptr) { configuration->*member = parse< pType >(_this->defaultValue).value(); return Source::DefaultValue; } else { log(LogLevel::Error, "parameter %s has not been found and has no default value", _this->name); if(userInput.empty()) return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound}; else return tl::unexpected{ConfigurationError::ErrorClass::BadInput}; } }; const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > { configuration->*member = value; return Source::UserInput; }; return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue); }; } const char * name; const char * defaultValue; tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration * configuration, std::string_view userInput); bool read(Configuration * configuration, std::optional< std::string_view > userInput) const { constexpr const auto emptyString = ""; const auto logStored = [&](const auto & source) -> tl::expected< Source, ConfigurationError > { rublon::log(LogLevel::Debug, "Configuration parameter '%s' was set to '%s'%s", this->name, this->defaultValue, source == Source::DefaultValue ? " (default)" : ""); return source; }; const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > { rublon::log(LogLevel::Error, "Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting", this->name); return tl::unexpected{error}; }; return _read(this, configuration, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value(); } }; template < auto member > constexpr auto make_entry(const char * name, const char * defaultValue) { return Entry{name, defaultValue, Entry::make_read_function< member >()}; } constexpr static inline std::array< Entry, 8 > configurationVariables = { // make_entry< &Configuration::logging >("logging", "true"), make_entry< &Configuration::systemToken >("systemToken", nullptr), make_entry< &Configuration::secretKey >("secretKey", nullptr), make_entry< &Configuration::apiServer >("rublonApiServer", nullptr), make_entry< &Configuration::prompt >("prompt", "1"), make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"), make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"), make_entry< &Configuration::failMode >("failMode", "deny")}; class ConfigurationFactory { public: ConfigurationFactory() = default; std::optional< Configuration > systemConfig() { memory::MonotonicStackResource< 8 * 1024 > stackResource; Configuration configuration{}; std::ifstream file(std::filesystem::path{"/etc/rublon.config"}); if(not file.good()) return std::nullopt; std::pmr::string line{&stackResource}; line.reserve(100); std::pmr::map< std::pmr::string, std::pmr::string > parameters{&stackResource}; const auto readParameterByName = [&](std::string_view name) -> std::optional< std::string_view > { return parameters.count(name.data()) ? std::optional< std::string_view >{parameters.at(name.data())} : std::nullopt; }; while(std::getline(file, line)) { std::pmr::string key{&stackResource}; std::pmr::string value{&stackResource}; if(!line.length()) continue; if(line[0] == '#' || line[0] == ';') continue; auto posEqual = line.find('='); key = line.substr(0, posEqual); value = line.substr(posEqual + 1); parameters[std::move(key)] = std::move(value); } for(const auto & entry : configurationVariables) { if(not entry.read(&configuration, readParameterByName(entry.name))) return std::nullopt; } return configuration; } }; } // namespace rublon