From 09702ab0a0278f7506d1c27d0dedce49d7587fe7 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 20 Feb 2019 13:53:20 +0200 Subject: [PATCH] MXS-2346 Provide new configuration mechanism The configuration mechanism consists of the following concepts: Specification Specifies the available configuration parameters of a module, their names and their types. Param Specifies a parameter, its name and its type. Type Specifies the type of a configuration parameters; Bool, Size, Count, etc. Configuration Specifies the configuration values of a particular instance of the module. Configuration walks hand in hand with Specification, the latter specifies what the former should contain. A Specification is capable of configuring a Configuration from a MXS_CONFIG_PARAMETER, checking in the process that all parameters are of the correct type and that the required parameters are present. A Specification is capable of persisting itself so that it later can be read back. The mechanism is closed for modification but open for extension in the sense that if a module requires a custom parameter, all it needs to do is to derive one class from Param and another from Type. The canonical way for using this mechanism is as follows. Consider a module xyx that has three parameters; a parameter called "enabled" that is of boolean type, a parameter called "period" that is of duration type, and a parameter "cache" that is of size type. That would be declared as follows: // xyz.hh class XYZSession; class XYZ : public maxscale::Filter { public: static XYZ* create(const char* zName, MXS_CONFIG_PARAMETER* pParams); private: XYZ(); static config::Specification s_specification; static config::ParamBool s_enabled; static config::ParamDuration s_period; static config::ParamSize s_cache; config::Configuration m_configuration; config::Bool m_enabled; config::Duration m_period; config::Size m_cache; }; // xyz.cc config::Specification XYZ::s_specification(MXS_MODULE_NAME); config::ParamBool XYZ::s_enabled( &s_specification, "enabled", "Specifies whether ... should be enabled or not." ); config::ParamDuration XYZ::s_period( &s_specification, "period", "Specifies the period. Rounded to the nearest second." ); config::ParamSize XYZ::s_cache( &s_specification, "cache", "Specifies the size of the internal cache." ); XYZ::XYZ() : m_configuration(&s_specification) , m_enabled(&m_configuration, &s_enabled) , m_period(&m_configuration, &s_period) , m_cache(&m_configuration, &s_cache) { } XYZ* XYZ::create(const char* zName, MXS_CONFIG_PARAMETER* pParams) { XYZ* pXyz = new XYZ; if (!s_specification.configure(pXyz->m_configuration, pParams)) { delete pXyz; pXyz = nullptr; } return pXyz; } --- include/maxscale/config.hh | 22 + include/maxscale/config2.hh | 1349 ++++++++++++++++++++++++++++++ server/core/CMakeLists.txt | 1 + server/core/config.cc | 3 +- server/core/config2.cc | 718 ++++++++++++++++ server/core/internal/config.hh | 19 - server/core/test/CMakeLists.txt | 3 + server/core/test/test_config2.cc | 318 +++++++ 8 files changed, 2413 insertions(+), 20 deletions(-) create mode 100644 include/maxscale/config2.hh create mode 100644 server/core/config2.cc create mode 100644 server/core/test/test_config2.cc diff --git a/include/maxscale/config.hh b/include/maxscale/config.hh index 854585c75..6042b5594 100644 --- a/include/maxscale/config.hh +++ b/include/maxscale/config.hh @@ -665,3 +665,25 @@ inline bool config_is_valid_name(const std::string& name, std::string* reason = { return config_is_valid_name(name.c_str()); } + +// TEMPORARILY EXPOSED. +bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value); + +/** + * Converts a string into a duration, intepreting in a case-insensitive manner + * an 'h'-suffix to indicate hours, an 'm'-suffix to indicate minutes, an + * 's'-suffix to indicate seconds and an 'ms'-suffix to indicate milliseconds. + * + * @param zValue A numerical string, possibly suffixed by 'h', 'm', + * 's' or 'ms'. + * @param interpretation How a value lacking a specific suffix should be interpreted. + * @param pDuration Pointer, if non-NULL, where the result is stored. + * @param pUnit Pointer, if non-NULL, where the detected unit is stored. + * + * @return True on success, false on invalid input in which case @c pUnit and + * @c pDuration will not be modified. + */ +bool get_suffixed_duration(const char* zValue, + mxs::config::DurationInterpretation interpretation, + std::chrono::milliseconds* pDuration, + mxs::config::DurationUnit* pUnit = nullptr); diff --git a/include/maxscale/config2.hh b/include/maxscale/config2.hh new file mode 100644 index 000000000..ddeaa2fb1 --- /dev/null +++ b/include/maxscale/config2.hh @@ -0,0 +1,1349 @@ +/* + * Copyright (c) 2018 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace config +{ + +class Configuration; +class Param; +class Type; + +// An instance of Specification specifies what parameters a particular module expects +// and of what type they are. +class Specification +{ +public: + using ParamsByName = std::map; + using const_iterator = ParamsByName::const_iterator; + using value_type = ParamsByName::value_type; + + /** + * Constructor + * + * @param zModule The the name of the module, e.g. "cachefilter". + */ + Specification(const char* zModule); + ~Specification(); + + /** + * @return The module name of this specification. + */ + const std::string& module() const; + + /** + * Validate parameters + * + * @param params Parameters as found in the configuration file. + * + * @return True, if they represent valid parameters - all mandatory are present, + * all present ones are of corrent type - for this configuration. + */ + bool validate(const MXS_CONFIG_PARAMETER& params) const; + + /** + * Configure configuration + * + * @param configuration The configuration that should be configured. + * @param params The parameters that should be used, will be validated. + * + * @return True if could be configured. + */ + bool configure(Configuration& configuration, const MXS_CONFIG_PARAMETER& params) const; + + /** + * Find given parameter of the specification. + * + * @param name The name of the parameter. + * + * @return The corresponding parameter object or NULL if the name is not a + * parameter of the specification. + */ + const Param* find_param(const std::string& name) const; + + /** + * Document this specification. + * + * @param out The stream the documentation should be written to. + * + * @return @c out + */ + std::ostream& document(std::ostream& out) const; + + /** + * Populate legacy parameter definition. + * + * @note Only for a transitionary period. + * + * @param module The module description to be populated with parameters. + */ + void populate(MXS_MODULE& module) const; + + /** + * @return The number of parameters in the specification. + */ + size_t size() const; + + /** + * @return Const iterator to first parameter. + */ + const_iterator cbegin() const + { + return m_params.cbegin(); + } + + /** + * @return Const iterator to one past last parameter. + */ + const_iterator cend() const + { + return m_params.cend(); + } + +private: + friend Param; + + void insert(Param* pParam); + void remove(Param* pParam); + +private: + std::string m_module; + ParamsByName m_params; +}; + + +/** + * A instance of Param specifies a parameter of a module, that is, its name, + * type, default value and whether it is mandatory or optional. + */ +class Param +{ +public: + enum Kind + { + MANDATORY, + OPTIONAL + }; + + ~Param(); + + /** + * @return The name of the parameter. + */ + const std::string& name() const; + + /** + * @return The type of the parameter (human readable). + */ + virtual std::string type() const = 0; + + /** + * @return The description of the parameter. + */ + const std::string& description() const; + + /** + * Document the parameter. + * + * The documentation of a parameters consists of its name, its type, + * whether it is mandatory or optional (default value documented in + * that case), and its description. + * + * @return The documentation. + */ + std::string documentation() const; + + /** + * @return The kind - mandatory or optional - of the parameter. + */ + Kind kind() const; + + /** + * @return True, if the parameter is mandatory. + */ + bool is_mandatory() const; + + /** + * @return True, if the parameter is optional. + */ + bool is_optional() const; + + /** + * Synonym for @c is_optional. + * + * @return True, if the parameter has a default value. + */ + bool has_default_value() const; + + /** + * @return Default value as string. + * + * @note Meaningful only if @c has_default_value returns true. + */ + virtual std::string default_to_string() const = 0; + + /** + * Validate a string. + * + * @param value_as_string The string to validate. + * + * @return True, if @c value_as_string can be converted into a value of this type. + */ + virtual bool validate(const std::string& value_as_string, std::string* pMessage) const = 0; + + /** + * Set setting value with value from configuration file. + * + * @param value The @c Type to configure. + * @param value_as_string The string value to configure it with. + * + * @return True, if it could be configured, false otherwise. The + * function will fail only if @c value_as_string is invalid. + */ + virtual bool set(Type& value, const std::string& value_as_string) const = 0; + + /** + * Populate a legacy parameter specification with data. + * + * @param param The legacy parameter specification to be populated. + */ + virtual void populate(MXS_MODULE_PARAM& param) const; + +protected: + Param(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + mxs_module_param_type legacy_type); + +private: + Specification& m_specification; + std::string m_name; + std::string m_description; + Kind m_kind; + mxs_module_param_type m_legacy_type; +}; + +/** + * ParamBool + */ +class ParamBool : public Param +{ +public: + using value_type = bool; + + ParamBool(Specification* pSpecification, + const char* zName, + const char* zDescription) + : ParamBool(pSpecification, zName, zDescription, Param::MANDATORY, value_type()) + { + } + + ParamBool(Specification* pSpecification, + const char* zName, + const char* zDescription, + value_type default_value) + : ParamBool(pSpecification, zName, zDescription, Param::OPTIONAL, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(value_type value) const; + +private: + ParamBool(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_BOOL) + , m_default_value(default_value) + { + } + +private: + value_type m_default_value; +}; + +/** + * ParamCount + */ +class ParamCount : public Param +{ +public: + using value_type = uint64_t; + + ParamCount(Specification* pSpecification, + const char* zName, + const char* zDescription) + : ParamCount(pSpecification, zName, zDescription, Param::MANDATORY, value_type()) + { + } + + ParamCount(Specification* pSpecification, + const char* zName, + const char* zDescription, + value_type default_value) + : ParamCount(pSpecification, zName, zDescription, Param::OPTIONAL, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(value_type value) const; + +private: + ParamCount(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_COUNT) + , m_default_value(default_value) + { + } + +private: + value_type m_default_value; +}; + +/** + * ParamDuration + */ +template +class ParamDuration : public Param +{ +public: + using value_type = T; + + ParamDuration(Specification* pSpecification, + const char* zName, + const char* zDescription, + mxs::config::DurationInterpretation interpretation) + : ParamDuration(pSpecification, zName, zDescription, Param::MANDATORY, interpretation, value_type()) + { + } + + ParamDuration(Specification* pSpecification, + const char* zName, + const char* zDescription, + mxs::config::DurationInterpretation interpretation, + value_type default_value) + : ParamDuration(pSpecification, zName, zDescription, Param::OPTIONAL, interpretation, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(const value_type& value) const; + +private: + ParamDuration(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + mxs::config::DurationInterpretation interpretation, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_DURATION) + , m_interpretation(interpretation) + , m_default_value(default_value) + { + } + +private: + mxs::config::DurationInterpretation m_interpretation; + value_type m_default_value; +}; + +/** + * ParamEnum + */ +template +class ParamEnum : public Param +{ +public: + using value_type = T; + + ParamEnum(Specification* pSpecification, + const char* zName, + const char* zDescription, + const std::vector>& enumeration) + : ParamEnum(pSpecification, zName, zDescription, Param::MANDATORY, enumeration, value_type()) + { + } + + ParamEnum(Specification* pSpecification, + const char* zName, + const char* zDescription, + const std::vector>& enumeration, + value_type default_value) + : ParamEnum(pSpecification, zName, zDescription, Param::OPTIONAL, enumeration, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(value_type value) const; + + void populate(MXS_MODULE_PARAM& param) const; + +private: + ParamEnum(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + const std::vector>& enumeration, + value_type default_value); + +private: + std::vector> m_enumeration; + value_type m_default_value; + std::vector m_enum_values; +}; + +/** + * ParamPath + */ +class ParamPath : public Param +{ +public: + using value_type = std::string; + + enum Options + { + X = MXS_MODULE_OPT_PATH_X_OK, // Execute permission required. + R = MXS_MODULE_OPT_PATH_R_OK, // Read permission required. + W = MXS_MODULE_OPT_PATH_W_OK, // Write permission required. + F = MXS_MODULE_OPT_PATH_F_OK, // File existence required. + C = MXS_MODULE_OPT_PATH_CREAT // Create path if does not exist. + }; + + const uint32_t MASK = X | R | W | F | C; + + + ParamPath(Specification* pSpecification, + const char* zName, + const char* zDescription, + uint32_t options) + : ParamPath(pSpecification, zName, zDescription, Param::MANDATORY, options, value_type()) + { + } + + ParamPath(Specification* pSpecification, + const char* zName, + const char* zDescription, + uint32_t options, + value_type default_value) + : ParamPath(pSpecification, zName, zDescription, Param::OPTIONAL, options, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(const value_type& value) const; + + void populate(MXS_MODULE_PARAM& param) const; + +private: + ParamPath(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + uint32_t options, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_PATH) + , m_options(options) + , m_default_value(default_value) + { + } + +private: + uint32_t m_options; + value_type m_default_value; +}; + +/** + * ParamSize + */ +class ParamSize : public Param +{ +public: + using value_type = uint64_t; + + ParamSize(Specification* pSpecification, + const char* zName, + const char* zDescription) + : ParamSize(pSpecification, zName, zDescription, Param::MANDATORY, value_type()) + { + } + + ParamSize(Specification* pSpecification, + const char* zName, + const char* zDescription, + value_type default_value) + : ParamSize(pSpecification, zName, zDescription, Param::OPTIONAL, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(value_type value) const; + +private: + ParamSize(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_SIZE) + , m_default_value(default_value) + { + } + +private: + value_type m_default_value; +}; + +/** + * ParamString + */ +class ParamString : public Param +{ +public: + using value_type = std::string; + + ParamString(Specification* pSpecification, + const char* zName, + const char* zDescription) + : ParamString(pSpecification, zName, zDescription, Param::MANDATORY, value_type()) + { + } + + ParamString(Specification* pSpecification, + const char* zName, + const char* zDescription, + value_type default_value) + : ParamString(pSpecification, zName, zDescription, Param::OPTIONAL, default_value) + { + } + + std::string type() const override; + + std::string default_to_string() const override; + + bool validate(const std::string& value_as_string, std::string* pMessage) const override; + + bool set(Type& value, const std::string& value_as_string) const override; + + bool from_string(const std::string& value, value_type* pValue, std::string* pMessage = nullptr) const; + std::string to_string(value_type value) const; + +private: + ParamString(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_STRING) + , m_default_value(default_value) + { + } + +private: + value_type m_default_value; +}; + +/** + * ParamBitMask + */ +using ParamBitMask = ParamCount; + +/** + * An instance of the class Configuration specifies the configuration of a particular + * instance of a module. + * + * Walks hand in hand with Specification. + */ +class Configuration +{ +public: + using ValuesByName = std::map; + using const_iterator = ValuesByName::const_iterator; + using value_type = ValuesByName::value_type; + + /** + * Constructor + * + * @param pSpecification The specification this instance is a configuration of. + */ + Configuration(const Specification* pSpecification); + + /** + * @return The specification of this configuration. + */ + const Specification& specification() const; + + /** + * @param name The name of the parameter to look up. + * + * @return The corresponding @c Value or NULL if @c name is unknown. + */ + Type* find_value(const std::string& name); + const Type* find_value(const std::string& name) const; + + /** + * Persist the configuration to a stream. + * + * @param out The stream to persist to. + */ + std::ostream& persist(std::ostream& out) const; + + /** + * Called when configuration has initially been configured, to allow a + * Configuration to check any interdependencies between values or to calculate + * derived ones. + * + * @return True, if everything is ok. + * + * @note The default implementation returns true. + */ + virtual bool configure(); + + /** + * @return The number of values in the configuration. + */ + size_t size() const; + + /** + * @return Const iterator to first parameter. + */ + const_iterator cbegin() const + { + return m_values.cbegin(); + } + + /** + * @return Const iterator to one past last parameter. + */ + const_iterator cend() const + { + return m_values.cend(); + } + +private: + friend Type; + + void insert(Type* pValue); + void remove(Type* pValue); + +private: + const Specification& m_specification; + ValuesByName m_values; +}; + + +/** + * Base-class of all configuration value types. + * + * In the description of this class, "value" should be read as + * "an instance of this type". + */ +class Type +{ +public: + Type(const Type& rhs) = delete; + Type& operator = (const Type&) = delete; + + ~Type(); + + /** + * Get parameter describing this value. + * + * @return Param of the value. + */ + const Param& parameter() const; + + /** + * Persist this value to a stream. It will be written as + * + * name=value + * + * where @c value will be formatted in the correct way. + * + * @param out The stream to write to. + * + * @return @c out. + */ + std::ostream& persist(std::ostream& out) const; + + /** + * Convert this value into its string representation. + * + * @return The value as it should appear in a configuration file. + */ + virtual std::string to_string() const = 0; + + /** + * Set value. + * + * @param value_as_string The new value expressed as a string. + * + * @return True, if the value could be set, false otherwise. + */ + bool set(const std::string& value_as_string); + +protected: + Type(Configuration* pConfiguration, const Param* pParam); + +private: + Configuration& m_configuration; + const Param& m_param; +}; + +/** + * A concrete Value. Instantiated with a derived class and the + * corresponding param type. + */ +template +class ConcreteType : public Type +{ +public: + using value_type = typename ParamType::value_type; + + ConcreteType(const ConcreteType&) = delete; + + ConcreteType(Configuration* pConfiguration, const ParamType* pParam) + : Type(pConfiguration, pParam) + { + } + + This& operator = (const ConcreteType& rhs) + { + // Only the value is copied, the parameter and the configuration + // remains the same. + m_value = rhs.m_value; + return static_cast(*this); + } + + value_type get() const + { + return m_value; + } + + void set(const value_type& value) + { + m_value = value; + } + + std::string to_string() const override + { + return static_cast(parameter()).to_string(m_value); + } + +protected: + value_type m_value; +}; + +/** + * Comparison operators: + * + * ConcreteType <-> ConcreteType + */ +template +inline bool operator == (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return lhs.get() == rhs.get(); +} + +template +inline bool operator != (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return lhs.get() != rhs.get(); +} + +template +inline bool operator < (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return lhs.get() < rhs.get(); +} + +template +inline bool operator > (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return lhs.get() > rhs.get(); +} + +template +inline bool operator <= (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return (lhs.get() < rhs.get()) || (lhs == rhs); +} + +template +inline bool operator >= (const ConcreteType& lhs, + const ConcreteType& rhs) +{ + return (lhs.get() > rhs.get()) || (lhs == rhs); +} + +/** + * Comparison operators: + * + * ConcreteType <-> ParamType::value_type + */ +template +inline bool operator == (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return lhs.get() == rhs; +} + +template +inline bool operator != (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return lhs.get() != rhs; +} + +template +inline bool operator < (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return lhs.get() < rhs; +} + +template +inline bool operator > (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return lhs.get() > rhs; +} + +template +inline bool operator <= (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return (lhs.get() < rhs) || (lhs.get() == rhs); +} + +template +inline bool operator >= (const ConcreteType& lhs, + const typename ParamType::value_type& rhs) +{ + return (lhs.get() > rhs) || (lhs.get() == rhs); +} + +/** + * Comparison operators: + * + * ParamType::value_type <-> ConcreteType + */ +template +inline bool operator == (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return lhs == rhs.get(); +} + +template +inline bool operator != (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return lhs != rhs.get(); +} + +template +inline bool operator < (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return lhs < rhs.get(); +} + +template +inline bool operator > (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return lhs > rhs.get(); +} + +template +inline bool operator <= (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return (lhs < rhs.get()) || (lhs == rhs.get()); +} + +template +inline bool operator >= (const typename ParamType::value_type& lhs, + const ConcreteType& rhs) +{ + return (lhs > rhs.get()) || (lhs == rhs.get()); +} + + +/** + * Count + */ +class Count : public ConcreteType +{ +public: + Count(Configuration* pConfiguration, const ParamCount* pParam) + : ConcreteType(pConfiguration, pParam) + { + } +}; + +/** + * BitMask + */ +class BitMask : public Count +{ +public: + BitMask(Configuration* pConfiguration, const ParamCount* pParam) + : Count(pConfiguration, pParam) + { + } + + bool is_set(value_type bit) const + { + return (m_value & bit) == bit; + } +}; + +/** + * Bool + */ +class Bool : public ConcreteType +{ +public: + Bool(Configuration* pConfiguration, const ParamBool* pParam) + : ConcreteType(pConfiguration, pParam) + { + } + + explicit operator bool () const + { + return m_value; + } +}; + +/** + * Duration + */ +template +class Duration : public ConcreteType, ParamDuration> +{ +public: + Duration(Configuration* pConfiguration, const ParamDuration* pParam) + : ConcreteType, ParamDuration>(pConfiguration, pParam) + { + } + + typename T::rep count() const + { + return ConcreteType, ParamDuration>::m_value.count(); + } +}; + +/* +template +inline bool operator < (const Duration& lhs, const Duration& rhs) +{ + return lhs.get() < rhs.get(); +} + +template +inline bool operator > (const Duration& lhs, const Duration& rhs) +{ + return lhs.get() > rhs.get(); +} +*/ + +/** + * Enum + */ +template +class Enum : public ConcreteType, ParamEnum> +{ +public: + Enum(Configuration* pConfiguration, const ParamEnum* pParam) + : ConcreteType, ParamEnum>(pConfiguration, pParam) + { + } +}; + +/** + * Path + */ +class Path : public ConcreteType +{ +public: + Path(Configuration* pConfiguration, const ParamPath* pParam) + : ConcreteType(pConfiguration, pParam) + { + } + + const char* c_str() const + { + return m_value.c_str(); + } + + bool empty() const + { + return m_value.empty(); + } +}; + +/** + * Size + */ +class Size : public ConcreteType +{ +public: + Size(Configuration* pConfiguration, const ParamSize* pParam) + : ConcreteType(pConfiguration, pParam) + { + } +}; + +inline Size::value_type operator / (const Size& lhs, Size::value_type rhs) +{ + return lhs.get() / rhs; +} + +/** + * String + */ +class String : public ConcreteType +{ +public: + String(Configuration* pConfiguration, const ParamString* pParam) + : ConcreteType(pConfiguration, pParam) + { + } + + const char* c_str() const + { + return m_value.c_str(); + } + + bool empty() const + { + return m_value.empty(); + } +}; + +/** + * IMPLEMENTATION DETAILS + */ +struct DurationSuffix +{ + static const char* of(const std::chrono::seconds&) + { + return "s"; + } + + static const char* of(const std::chrono::milliseconds&) + { + return "ms"; + } +}; + +template +std::string ParamDuration::type() const +{ + return "duration"; +} + +template +std::string ParamDuration::default_to_string() const +{ + return to_string(m_default_value); +} + +template +bool ParamDuration::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +template +bool ParamDuration::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Duration& duration_value = static_cast&>(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + duration_value.set(x); + } + + return valid; +} + +template +bool ParamDuration::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + mxs::config::DurationUnit unit; + + std::chrono::milliseconds duration; + bool valid = get_suffixed_duration(value_as_string.c_str(), m_interpretation, &duration, &unit); + + if (valid) + { + if (unit == mxs::config::DURATION_IN_DEFAULT) + { + if (pMessage) + { + *pMessage = "Specifying durations without a suffix denoting the unit has been deprecated: "; + *pMessage += value_as_string; + *pMessage += ". Use the suffixes 'h' (hour), 'm' (minute) 's' (second) or "; + *pMessage += "'ms' (milliseconds)."; + } + } + + *pValue = std::chrono::duration_cast(duration); + } + else if (pMessage) + { + *pMessage = "Invalid duration: "; + *pMessage += value_as_string; + } + + return valid; +} + +template +std::string ParamDuration::to_string(const value_type& value) const +{ + std::stringstream ss; + ss << value.count() << DurationSuffix::of(value); + return ss.str(); +} + +template +ParamEnum::ParamEnum(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + const std::vector>& enumeration, + value_type default_value) + : Param(pSpecification, zName, zDescription, kind, MXS_MODULE_PARAM_ENUM) + , m_enumeration(enumeration) + , m_default_value(default_value) +{ + m_enum_values.reserve(m_enumeration.size() + 1); + + for (const auto& entry : enumeration) + { + MXS_ENUM_VALUE x {}; + x.name = entry.second; + x.enum_value = entry.first; + + m_enum_values.emplace_back(x); + } + + MXS_ENUM_VALUE end { NULL }; + m_enum_values.emplace_back(end); +} + +template +std::string ParamEnum::type() const +{ + std::string s("enumeration:["); + + bool first = true; + for (const auto& p : m_enumeration) + { + if (first) + { + first = false; + } + else + { + s += ", "; + } + + s += p.second; + } + + s += "]"; + + return s; +} + +template +std::string ParamEnum::default_to_string() const +{ + return to_string(m_default_value); +} + +template +bool ParamEnum::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +template +bool ParamEnum::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Enum& enum_value = static_cast&>(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + enum_value.set(x); + } + + return valid; +} + +template +bool ParamEnum::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + auto it = std::find_if(m_enumeration.begin(), m_enumeration.end(), + [value_as_string](const std::pair& elem) { + return value_as_string == elem.second; + }); + + if (it != m_enumeration.end()) + { + *pValue = it->first; + } + else if (pMessage) + { + std::string s; + for (size_t i = 0; i < m_enumeration.size(); ++i) + { + s += "'"; + s += m_enumeration[i].second; + s += "'"; + + if (i == m_enumeration.size() - 2) + { + s += " and "; + } + else if (i != m_enumeration.size() - 1) + { + s += ", "; + } + } + + *pMessage = "Invalid enumeration value: "; + *pMessage += value_as_string; + *pMessage += ", valid values are: "; + *pMessage += s; + *pMessage += "."; + } + + return it != m_enumeration.end(); +} + +template +std::string ParamEnum::to_string(value_type value) const +{ + auto it = std::find_if(m_enumeration.begin(), m_enumeration.end(), + [value] (const std::pair& entry) { + return entry.first == value; + }); + + return it != m_enumeration.end() ? it->second : "unknown"; +} + +template +void ParamEnum::populate(MXS_MODULE_PARAM& param) const +{ + Param::populate(param); + + param.accepted_values = &m_enum_values[0]; +} + +} + diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index a46f97eb8..a625999a5 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(maxscale-common SHARED backend.cc buffer.cc config.cc + config2.cc config_runtime.cc dcb.cc encryption.cc diff --git a/server/core/config.cc b/server/core/config.cc index edd67cb23..02c4fb2c4 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -4218,7 +4218,8 @@ bool config_is_ssl_parameter(const char* key) return false; } -static bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value) +// TEMPORARILY EXPOSED +/*static*/ bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value) { bool valid = false; diff --git a/server/core/config2.cc b/server/core/config2.cc new file mode 100644 index 000000000..445427ae2 --- /dev/null +++ b/server/core/config2.cc @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2018 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include "internal/config.hh" + +using namespace std; + +namespace config +{ + +/** + * class Specification + */ +Specification::Specification(const char* zModule) + : m_module(zModule) +{ +} + +Specification::~Specification() +{ +} + +const string& Specification::module() const +{ + return m_module; +} + +const Param* Specification::find_param(const string& name) const +{ + auto it = m_params.find(name); + + return it != m_params.end() ? it->second : nullptr; +} + +ostream& Specification::document(ostream& out) const +{ + for (const auto& entry : m_params) + { + out << entry.second->documentation() << endl; + } + + return out; +} + +bool Specification::validate(const MXS_CONFIG_PARAMETER& params) const +{ + bool valid = true; + + set provided; + + for (const auto& param : params) + { + const auto& name = param.first; + const auto& value = param.second; + + const Param* pParam = find_param(name.c_str()); + + if (pParam) + { + string message; + + if (!pParam->validate(value.c_str(), &message)) + { + valid = false; + } + + if (!message.empty()) + { + if (valid) + { + MXS_WARNING("%s: %s", name.c_str(), message.c_str()); + } + else + { + MXS_ERROR("%s: %s", name.c_str(), message.c_str()); + } + } + + provided.insert(name); + } + else + { + MXS_WARNING("%s: The parameter '%s' is unrecognized.", m_module.c_str(), name.c_str()); + valid = false; + } + } + + for (const auto& entry : m_params) + { + const Param* pParam = entry.second; + + if (pParam->is_mandatory() && (provided.find(pParam->name()) == provided.end())) + { + MXS_ERROR("%s: The mandatory parameter '%s' is not provided.", + m_module.c_str(), pParam->name().c_str()); + valid = false; + } + } + + return valid; +} + +bool Specification::configure(Configuration& configuration, const MXS_CONFIG_PARAMETER& params) const +{ + mxb_assert(validate(params)); + mxb_assert(size() == configuration.size()); + + bool configured = true; + + for (const auto& param : params) + { + const auto& name = param.first; + const auto& value = param.second; + + const Param* pParam = find_param(name.c_str()); + config::Type* pValue = configuration.find_value(name.c_str()); + + mxb_assert(pValue && pParam); // Should have been validated. + mxb_assert(&pValue->parameter() == pParam); + + if (pParam && pValue) + { + if (!pParam->set(*pValue, value.c_str())) + { + mxb_assert(!true); + configured = false; + } + } + else + { + MXS_ERROR("%s: The parameter '%s' is unrecognized.", m_module.c_str(), name.c_str()); + configured = false; + } + } + + if (configured) + { + configured = configuration.configure(); + } + + return configured; +} + +void Specification::populate(MXS_MODULE& module) const +{ + MXS_MODULE_PARAM* pModule_param = &module.parameters[0]; + + for (const auto& entry : m_params) + { + const Param* pParam = entry.second; + + pParam->populate(*pModule_param); + ++pModule_param; + } +} + +size_t Specification::size() const +{ + return m_params.size(); +} + +void Specification::insert(Param* pParam) +{ + mxb_assert(m_params.find(pParam->name()) == m_params.end()); + + m_params.insert(make_pair(pParam->name(), pParam)); +} + +void Specification::remove(Param* pParam) +{ + auto it = m_params.find(pParam->name()); + mxb_assert(it != m_params.end()); + + m_params.erase(it); +} + + +/** + * class Param + */ +Param::Param(Specification* pSpecification, + const char* zName, + const char* zDescription, + Kind kind, + mxs_module_param_type legacy_type) + : m_specification(*pSpecification) + , m_name(zName) + , m_description(zDescription) + , m_kind(kind) + , m_legacy_type(legacy_type) +{ + m_specification.insert(this); +} + +Param::~Param() +{ + m_specification.remove(this); +} + +const string& Param::name() const +{ + return m_name; +} + +const string& Param::description() const +{ + return m_description; +} + +std::string Param::documentation() const +{ + std::stringstream ss; + + ss << m_name << " (" << type() << ", "; + + if (is_mandatory()) + { + ss << "mandatory"; + } + else + { + ss << "optional, default: " << default_to_string(); + } + + ss << "): " << m_description; + + return ss.str(); +} + +Param::Kind Param::kind() const +{ + return m_kind; +} + +bool Param::is_mandatory() const +{ + return m_kind == MANDATORY; +} + +bool Param::is_optional() const +{ + return m_kind == OPTIONAL; +} + +bool Param::has_default_value() const +{ + return is_optional(); +} + +void Param::populate(MXS_MODULE_PARAM& param) const +{ + param.type = m_legacy_type; + param.name = MXS_STRDUP_A(name().c_str()); + + if (has_default_value()) + { + string s = default_to_string().c_str(); + + if ((s.length() >= 2) && (s.at(0) == '"') && (s.at(s.length() - 1) == '"')) + { + s = s.substr(1, s.length() - 2); + } + + param.default_value = MXS_STRDUP_A(s.c_str()); + } + + if (is_mandatory()) + { + param.options |= MXS_MODULE_OPT_REQUIRED; + } +} + + +/** + * class Configuration + */ +Configuration::Configuration(const config::Specification* pSpecification) + : m_specification(*pSpecification) +{ +} + +const config::Specification& Configuration::specification() const +{ + return m_specification; +} + +Type* Configuration::find_value(const string& name) +{ + auto it = m_values.find(name); + + return it != m_values.end() ? it->second : nullptr; +} + +const Type* Configuration::find_value(const string& name) const +{ + return const_cast(this)->find_value(name); +} + +ostream& Configuration::persist(ostream& out) const +{ + for (const auto& entry : m_values) + { + Type* pValue = entry.second; + pValue->persist(out) << "\n"; + } + + return out; +} + +void Configuration::insert(Type* pValue) +{ + mxb_assert(m_values.find(pValue->parameter().name()) == m_values.end()); + + m_values.insert(make_pair(pValue->parameter().name(), pValue)); +} + +void Configuration::remove(Type* pValue) +{ + auto it = m_values.find(pValue->parameter().name()); + + mxb_assert(it != m_values.end()); + m_values.erase(it); +} + +bool Configuration::configure() +{ + return true; +} + +size_t Configuration::size() const +{ + return m_values.size(); +} + +/** + * class Type + */ +Type::Type(Configuration* pConfiguration, const config::Param* pParam) + : m_configuration(*pConfiguration) + , m_param(*pParam) +{ + m_configuration.insert(this); +} + +Type::~Type() +{ + m_configuration.remove(this); +} + +const config::Param& Type::parameter() const +{ + return m_param; +} + +ostream& Type::persist(ostream& out) const +{ + out << m_param.name() << "=" << to_string(); + return out; +} + +bool Type::set(const string& value_as_string) +{ + return m_param.set(*this, value_as_string); +} + +/** + * ParamBool + */ +std::string ParamBool::type() const +{ + return "boolean"; +} + +std::string ParamBool::default_to_string() const +{ + return to_string(m_default_value); +} + +bool ParamBool::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +bool ParamBool::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Bool& bool_value = static_cast(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + bool_value.set(x); + } + + return valid; +} + +bool ParamBool::from_string(const string& value_as_string, value_type* pValue, string* pMessage) const +{ + int rv = config_truth_value(value_as_string.c_str()); + + if (rv == 1) + { + *pValue = true; + } + else if (rv == 0) + { + *pValue = false; + } + else if (pMessage) + { + mxb_assert(rv == -1); + + *pMessage = "Invalid boolean: "; + *pMessage += value_as_string; + } + + return rv != -1; +} + +string ParamBool::to_string(value_type value) const +{ + return value ? "true" : "false"; +} + +/** + * ParamCount + */ +std::string ParamCount::type() const +{ + return "count"; +} + +std::string ParamCount::default_to_string() const +{ + return to_string(m_default_value); +} + +bool ParamCount::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +bool ParamCount::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Count& count_value = static_cast(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + count_value.set(x); + } + + return valid; +} + +bool ParamCount::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + const char* zValue = value_as_string.c_str(); + char* zEnd; + long l = strtol(zValue, &zEnd, 10); + bool valid = (l >= 0 && zEnd != zValue && *zEnd == 0); + + if (valid) + { + *pValue = l; + } + else if (pMessage) + { + *pMessage = "Invalid count: "; + *pMessage += value_as_string; + } + + return valid; +} + +std::string ParamCount::to_string(value_type value) const +{ + return std::to_string(value); +} + +/** + * ParamPath + */ +std::string ParamPath::type() const +{ + return "path"; +} + +std::string ParamPath::default_to_string() const +{ + return to_string(m_default_value); +} + +bool ParamPath::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +bool ParamPath::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Path& path_value = static_cast(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + path_value.set(x); + } + + return valid; +} + +bool ParamPath::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + MXS_MODULE_PARAM param {}; + param.options = m_options; + + bool valid = check_path_parameter(¶m, value_as_string.c_str()); + + if (valid) + { + *pValue = value_as_string; + } + else if (pMessage) + { + *pMessage = "Invalid path (does not exist, required permissions are not granted, "; + *pMessage += "or cannot be created): "; + *pMessage += value_as_string; + } + + return valid; +} + +std::string ParamPath::to_string(const value_type& value) const +{ + return value; +} + +void ParamPath::populate(MXS_MODULE_PARAM& param) const +{ + Param::populate(param); + + param.options |= m_options; +} + +/** + * ParamSize + */ +std::string ParamSize::type() const +{ + return "size"; +} + +std::string ParamSize::default_to_string() const +{ + return to_string(m_default_value); +} + +bool ParamSize::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +bool ParamSize::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + Size& size_value = static_cast(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + size_value.set(x); + } + + return valid; +} + +bool ParamSize::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + bool valid = get_suffixed_size(value_as_string.c_str(), pValue); + + if (!valid && pMessage) + { + *pMessage = "Invalid size: "; + *pMessage += value_as_string; + } + + return valid; +} + +std::string ParamSize::to_string(value_type value) const +{ + // TODO: Use largest possible unit. + return std::to_string(value); +} + +/** + * ParamString + */ +std::string ParamString::type() const +{ + return "string"; +} + +std::string ParamString::default_to_string() const +{ + return to_string(m_default_value); +} + +bool ParamString::validate(const std::string& value_as_string, std::string* pMessage) const +{ + value_type value; + return from_string(value_as_string, &value, pMessage); +} + +bool ParamString::set(Type& value, const std::string& value_as_string) const +{ + mxb_assert(&value.parameter() == this); + + String& string_value = static_cast(value); + + value_type x; + bool valid = from_string(value_as_string, &x); + + if (valid) + { + string_value.set(x); + } + + return valid; +} + +bool ParamString::from_string(const std::string& value_as_string, + value_type* pValue, + std::string* pMessage) const +{ + bool valid = true; + + char b = value_as_string.empty() ? 0 : value_as_string.front(); + char e = value_as_string.empty() ? 0 : value_as_string.back(); + + if (b != '"' && b != '\'') + { + if (pMessage) + { + *pMessage = "A string value should be enclosed in quotes: "; + *pMessage += value_as_string; + } + } + + string s = value_as_string; + + if (b == '"' || b == '\'') + { + valid = (b == e); + + if (valid) + { + s = s.substr(1, s.length() - 2); + } + else if (pMessage) + { + *pMessage = "A quoted string must end with the same quote: "; + *pMessage += value_as_string; + } + } + + if (valid) + { + *pValue = s; + } + + return valid; +} + +std::string ParamString::to_string(value_type value) const +{ + stringstream ss; + ss << "\"" << value << "\""; + return ss.str(); +} + +} diff --git a/server/core/internal/config.hh b/server/core/internal/config.hh index 06e8062f1..1f6b2f790 100644 --- a/server/core/internal/config.hh +++ b/server/core/internal/config.hh @@ -214,25 +214,6 @@ bool is_normal_server_parameter(const char* param); */ bool get_suffixed_size(const char* value, uint64_t* dest); -/** - * Converts a string into a duration, intepreting in a case-insensitive manner - * an 'h'-suffix to indicate hours, an 'm'-suffix to indicate minutes, an - * 's'-suffix to indicate seconds and an 'ms'-suffix to indicate milliseconds. - * - * @param zValue A numerical string, possibly suffixed by 'h', 'm', - * 's' or 'ms'. - * @param interpretation How a value lacking a specific suffix should be interpreted. - * @param pDuration Pointer, if non-NULL, where the result is stored. - * @param pUnit Pointer, if non-NULL, where the detected unit is stored. - * - * @return True on success, false on invalid input in which case @c pUnit and - * @c pDuration will not be modified. - */ -bool get_suffixed_duration(const char* zValue, - mxs::config::DurationInterpretation interpretation, - std::chrono::milliseconds* pDuration, - mxs::config::DurationUnit* pUnit = nullptr); - /** * Generate configuration file contents out of module configuration parameters. Only parameters defined * in the parameter definition arrays are printed. Printing is in the order the parameters are given in diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index c647a6f0b..7c56a359f 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(test_adminusers test_adminusers.cc) add_executable(test_atomic test_atomic.cc) add_executable(test_buffer test_buffer.cc) add_executable(test_config test_config.cc) +add_executable(test_config2 test_config2.cc) add_executable(test_dcb test_dcb.cc) add_executable(test_event test_event.cc) add_executable(test_filter test_filter.cc) @@ -31,6 +32,7 @@ target_link_libraries(test_adminusers maxscale-common) target_link_libraries(test_atomic maxscale-common) target_link_libraries(test_buffer maxscale-common) target_link_libraries(test_config maxscale-common) +target_link_libraries(test_config2 maxscale-common) target_link_libraries(test_dcb maxscale-common) target_link_libraries(test_event maxscale-common) target_link_libraries(test_filter maxscale-common) @@ -58,6 +60,7 @@ add_test(test_adminusers test_adminusers) add_test(test_atomic test_atomic) add_test(test_buffer test_buffer) add_test(test_config test_config) +add_test(test_config2 test_config2) add_test(test_dcb test_dcb) add_test(test_event test_event) add_test(test_filter test_filter) diff --git a/server/core/test/test_config2.cc b/server/core/test/test_config2.cc new file mode 100644 index 000000000..12b0a7efe --- /dev/null +++ b/server/core/test/test_config2.cc @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +// To ensure that ss_info_assert asserts also when builing in non-debug mode. +#ifndef SS_DEBUG +#define SS_DEBUG +#endif + +#include +#include +#include + +using namespace std; + +inline ostream& operator << (ostream& out, const std::chrono::seconds& x) +{ + out << x.count(); + return out; +} + +inline ostream& operator << (ostream& out, const std::chrono::milliseconds& x) +{ + out << x.count(); + return out; +} + +config::Specification specification("test_module"); + +config::ParamBool +param_bool(&specification, + "boolean_parameter", + "Specifies whether something is enabled."); + +config::ParamCount +param_count(&specification, + "count_parameter", + "Specifies the cardinality of something."); + +config::ParamDuration +param_duration_1(&specification, + "duration_parameter_1", + "Specifies the duration of something.", + mxs::config::INTERPRET_AS_SECONDS); + +config::ParamDuration +param_duration_2(&specification, + "duration_parameter_2", + "Specifies the duration of something.", + mxs::config::INTERPRET_AS_MILLISECONDS); + +enum Enum +{ + ENUM_ONE, + ENUM_TWO +}; + +config::ParamEnum +param_enum(&specification, + "enum_parameter", + "Specifies a range of values.", + { + { ENUM_ONE, "one" }, + { ENUM_TWO, "two" } + }); + +config::ParamPath +param_path(&specification, + "path_parameter", + "Specifies the path of something.", + config::ParamPath::F); + +config::ParamSize +param_size(&specification, + "size_parameter", + "Specifies the size of something."); + +config::ParamString +param_string(&specification, + "string_parameter", + "Specifies the name of something."); + +template +struct TestEntry +{ + const char* zText; + bool valid; + T value; +}; + +#define elements_in_array(x) (sizeof(x)/sizeof(x[0])) + +template +int test(T& value, const TestEntry* pEntries, int nEntries) +{ + const config::Param& param = value.parameter(); + + cout << "Testing " << param.type() << " parameter " << param.name() << "." << endl; + + int nErrors = 0; + + for (int i = 0; i < nEntries; ++i) + { + const auto& entry = pEntries[i]; + + std::string message; + bool validated = param.validate(entry.zText, &message); + + if (entry.valid && validated) + { + param.set(value, entry.zText); + + if (value != entry.value) + { + cout << value.to_string() << " != " << entry.value << endl; + ++nErrors; + } + } + else if (entry.valid && !validated) + { + cout << "Expected \"" << entry.zText << "\" to BE valid for " << param.type() + << " parameter " << param.name() << ", but it was NOT validated: " << message << endl; + ++nErrors; + } + else if (!entry.valid && validated) + { + cout << "Expected \"" << entry.zText << "\" NOT to be valid for " << param.type() + << " parameter " << param.name() << ", but it WAS validated." << endl; + ++nErrors; + } + } + + return nErrors; +} + +int test_bool(config::Bool& value) +{ + static const TestEntry entries[] = + { + { "1", true, true }, + { "0", true, false }, + { "true", true, true }, + { "false", true, false }, + { "on", true, true }, + { "off", true, false }, + + { "2", false }, + { "truth", false }, + { "%&", false }, + { "-1", false }, + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_count(config::Count& value) +{ + static const TestEntry entries[] = + { + { "1", true, 1 }, + { "9999", true, 9999 }, + { "0", true, 0 }, + + { "0x45", false }, + { "blah", false }, + { "-1", false }, + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_duration(config::Duration& value) +{ + static const TestEntry::value_type> entries[] = + { + { "1", true, std::chrono::seconds { 1 } }, + { "1ms", true, std::chrono::seconds { 0 } }, + { "1s", true, std::chrono::seconds { 1 } }, + { "1m", true, std::chrono::seconds { 60 } }, + { "1h", true, std::chrono::seconds { 3600 } }, + + { "1x", false }, + { "a", false }, + { "-", false }, + { "second", false } + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_duration(config::Duration& value) +{ + static const TestEntry::value_type> entries[] = + { + { "1", true, std::chrono::milliseconds { 1 } }, + { "1ms", true, std::chrono::milliseconds { 1 } }, + { "1s", true, std::chrono::milliseconds { 1000 } }, + { "1m", true, std::chrono::milliseconds { 60000 } }, + { "1h", true, std::chrono::milliseconds { 3600000 } }, + + { "1x", false }, + { "a", false }, + { "-", false }, + { "second", false } + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_enum(config::Enum& value) +{ + static const TestEntry entries[] = + { + { "one", true, ENUM_ONE }, + { "two", true, ENUM_TWO }, + + { "blah", false }, + { "1", false }, + { "ones", false } + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_path(config::Path& value) +{ + static const TestEntry entries[] = + { + { ".", true, "." }, + { "/tmp", true, "/tmp" }, + + { "non-existent", false } + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_size(config::Size& value) +{ + static const TestEntry entries[] = + { + { "0", true, 0 }, + { "100", true, 100 }, + + { "-100", false }, + { "0x100", false }, + }; + + return test(value, entries, elements_in_array(entries)); +} + +int test_string(config::String& value) +{ + static const TestEntry entries[] = + { + { "blah", true, "blah" }, + { "\"blah\"", true, "blah" }, + { "'blah'", true, "blah" }, + { "123", true, "123" }, + { "`blah`", true, "`blah`" }, + + { "'blah\"", false } + }; + + return test(value, entries, elements_in_array(entries)); +} + +int main() +{ + mxb::Log log; + + for_each(specification.cbegin(), specification.cend(),[](const config::Specification::value_type& p) { + cout << p.second->documentation() << endl; + }); + + cout << endl; + + specification.document(cout); + + config::Configuration configuration(&specification); + + int nErrors = 0; + + config::Bool value_bool(&configuration, ¶m_bool); + nErrors += test_bool(value_bool); + + config::Count value_count(&configuration, ¶m_count); + nErrors += test_count(value_count); + + config::Duration value_duration_1(&configuration, ¶m_duration_1); + nErrors += test_duration(value_duration_1); + + config::Duration value_duration_2(&configuration, ¶m_duration_2); + nErrors += test_duration(value_duration_2); + + config::Enum value_enum(&configuration, ¶m_enum); + nErrors += test_enum(value_enum); + + config::Path value_path(&configuration, ¶m_path); + nErrors += test_path(value_path); + + config::Size value_size(&configuration, ¶m_size); + nErrors += test_size(value_size); + + config::String value_string(&configuration, ¶m_string); + nErrors += test_string(value_string); + + return nErrors ? EXIT_FAILURE : EXIT_SUCCESS; +}