Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Configuration management #22

Open
uristdwarf opened this issue Mar 11, 2024 · 0 comments
Open

refactor: Configuration management #22

uristdwarf opened this issue Mar 11, 2024 · 0 comments
Assignees
Labels
Priority: Low Documentation, minor bug refactoring Improve code quality

Comments

@uristdwarf
Copy link
Collaborator

uristdwarf commented Mar 11, 2024

Currently, each caller to cfg_get sets it's own defaults. This makes it difficult to gather information about running configurations, since there is no single source that sets the default configuration and it could get messy if two callers are setting their own defaults.

The idea is that each executable that uses the common library configuration portion sets all configuration options on startup and including the defaults as well. This way, the caller won't have to worry about defaults at all (unless it specifically wants to).

Related to #13.

An example draft from @lgsilva3087:

#pragma once

#include "common/platform.h"

#include <cstdint>
#include <memory>
#include <sstream>
#include <string>

#include "common/cfg.h"

/// Abstract base class for configuration options.
/// Used to trick the compiler into allowing a map of different types of
/// templated options.
class OptionBase {
public:
	/// Default constructor.
	explicit OptionBase(std::string name) : name_(std::move(name)) {}

	/// Copy and move constructors and assignment operators.
	OptionBase(const OptionBase &) = delete;
	OptionBase(OptionBase &&) = delete;
	OptionBase &operator=(const OptionBase &) = delete;
	OptionBase &operator=(OptionBase &&) = delete;

	/// Default virtual destructor, needed for polymorphism.
	virtual ~OptionBase() = default;

	/// Get the name of the option.
	std::string getName() const { return name_; }

	/// Should return an string representation of the option.
	virtual std::string toString() const = 0;

private:
	/// The name of the option.
	std::string name_;
};

template<typename T>
class GenericOption : public OptionBase {
public:
	/// Constructor with parameters.
	GenericOption(std::string name, T defaultValue)
	    : OptionBase(std::move(name)),
	      defaultValue_(defaultValue),
	      value_(defaultValue_) {}

	/// Default constructor.
	GenericOption() = delete;

	/// Not needed constructors/assignment operators.
	GenericOption(const GenericOption &) = delete;
	GenericOption(GenericOption &&) = delete;
	GenericOption &operator=(const GenericOption &) = delete;
	GenericOption &operator=(GenericOption &&) = delete;

	/// Default virtual destructor, needed for polymorphism.
	virtual ~GenericOption() = default;

	/// Should update the value of the option from the configuration file using
	/// the concrete type.
	virtual void updateValueFromFile() = 0;

	/// Get the value of the option.
	T getValue() const { return value_; }

	/// Set the value of the option.
	void setValue(T newValue) {
		value_ = newValue;
	}

	/// Get the default value of the option.
	T getDefaultValue() const { return defaultValue_; }

	/// Returns an string representation of the option.
	std::string toString() const override {
		std::stringstream result;

		result << getName() << " = " << value_
		       << "; // Default: " << defaultValue_;

		return result.str();
	}

private:
	/// The default value of the option.
	T defaultValue_;

	/// The value of the option.
	T value_;
};

/// Specialization of GenericOption to handle uint32_t
class OptionUint32 : public GenericOption<uint32_t> {
public:
	OptionUint32(std::string name, uint32_t defaultValue)
	    : GenericOption<uint32_t>(std::move(name), defaultValue) {}

	void updateValueFromFile() override {
		auto val = cfg_getuint32(getName().c_str(), getDefaultValue());
		setValue(val);
	}
};

/// Specialization of GenericOption to handle std::string
class OptionString : public GenericOption<std::string> {
public:
	OptionString(std::string name, std::string defaultValue)
	    : GenericOption<std::string>(std::move(name), std::move(defaultValue)) {
	}

	void updateValueFromFile() override {
		auto *val = cfg_getstr(getName().c_str(), getDefaultValue().c_str());
		setValue(val);
	}
};

// The rest of the concrete options should be added here until we move them to
// their own file. Some of them will be: OptionDouble, OptionRanged, etc..

/// Singleton class to store configuration parameters.
/// All the calls to the cfg_get* functions should be replaced by calls to the
/// getOption method of this class. This way, the map will contain the actual
/// effective value of the options in a single place per module.
class Configuration {
public:
	/// Get the instance of the Configuration class.
	static Configuration& instance() {
		static Configuration instance;
		return instance;
	}

	// Not needed methods
	Configuration(const Configuration &) = delete;
	Configuration &operator=(const Configuration &) = delete;
	Configuration(Configuration &&) = delete;
	Configuration &operator=(Configuration &&) = delete;

	/// Default destructor
	~Configuration() = default;

	/// Retrieves an option from the configuration file as uint32_t.
	/// \param optionName The name of the option to retrieve.
	/// In the future, the option will be cached and only read from the file
	/// once, unless reaload is requested.
	template<typename T>
	auto getOption(const std::string &name) {
		// Make sure the option exists
		assert(options_.find(name) != options_.end());

		// Cast to the concrete type
		auto option = static_cast<GenericOption<T> *>(options_.at(name).get());

		// Update the value (always until we have add support for reload option)
		option->updateValueFromFile();

		return option->getValue();
	}

	/// Adds an option to the map. All options should be added at start time.
	/// \param option The option to add.
	void addOption(std::shared_ptr<OptionBase> &option) {
		if (options_.find(option->getName()) != options_.end()) {
			safs_pretty_syslog(LOG_WARNING,
			                   "Option %s already exists in the configuration",
			                   option->getName().c_str());
			return;
		}

		options_.insert({option->getName(), option});
	}

	/// Prints all the configurations options using safs_pretty_syslog.
	/// Used only for debugging purposes (GUILLEX remove later).
	void printAllOptions() const {
		safs_pretty_syslog(LOG_NOTICE, "Configuration options:");
		for (const auto &option : options_) {
			safs_pretty_syslog(LOG_NOTICE, "%s", option.second->toString().c_str());
		}
	}

private:
	/// Private constructor for the singleton pattern
	Configuration() = default;

	/// Map with all the configuration options.
	std::map<std::string, std::shared_ptr<OptionBase>> options_;
};
@uristdwarf uristdwarf self-assigned this Mar 11, 2024
@uristdwarf uristdwarf added the refactoring Improve code quality label Mar 11, 2024
@uristdwarf uristdwarf added the Priority: Low Documentation, minor bug label Nov 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: Low Documentation, minor bug refactoring Improve code quality
Projects
None yet
Development

No branches or pull requests

1 participant