//------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio Copyright (c) 2024, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #pragma once #include "rpc/common/APIVersion.hpp" #include "util/config/Error.hpp" #include "util/config/Types.hpp" #include "util/log/Logger.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace util::config { class ValueView; class ConfigValue; /** * @brief specific values that are accepted for logger levels in config. */ static constexpr std::array kLOG_LEVELS = { "trace", "debug", "info", "warning", "error", "fatal", }; /** * @brief specific values that are accepted for logger tag style in config. */ static constexpr std::array kLOG_TAGS = { "int", "uint", "null", "none", "uuid", }; /** * @brief specific values that are accepted for cache loading in config. */ static constexpr std::array kLOAD_CACHE_MODE = { "sync", "async", "none", }; /** * @brief specific values that are accepted for database type in config. */ static constexpr std::array kDATABASE_TYPE = {"cassandra"}; /** * @brief specific values that are accepted for server's processing_policy in config. */ static constexpr std::array kPROCESSING_POLICY = {"parallel", "sequent"}; /** * @brief specific values that are accepted for database provider in config. */ static constexpr std::array kPROVIDER = {"cassandra", "aws_keyspace"}; /** * @brief An interface to enforce constraints on certain values within ClioConfigDefinition. */ class Constraint { public: constexpr virtual ~Constraint() noexcept = default; /** * @brief Check if the value meets the specific constraint. * * @param val The value to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkConstraint(Value const& val) const { if (auto const maybeError = checkTypeImpl(val); maybeError.has_value()) return maybeError; return checkValueImpl(val); } protected: /** * @brief Creates an error message for all constraints that must satisfy certain hard-coded values. * * @tparam arrSize, the size of the array of hardcoded values * @param key The key to the value * @param value The value the user provided * @param arr The array with hard-coded values to add to error message * @return The error message specifying what the value of key must be */ template constexpr std::string makeErrorMsg(std::string_view key, Value const& value, std::array arr) const { // Extract the value from the variant auto const valueStr = std::visit([](auto const& v) { return fmt::format("{}", v); }, value); // Create the error message return fmt::format( R"(You provided value "{}". Key "{}"'s value must be one of the following: {})", valueStr, key, fmt::join(arr, ", ") ); } /** * @brief Check if the value is of a correct type for the constraint. * * @param val The value type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ virtual std::optional checkTypeImpl(Value const& val) const = 0; /** * @brief Check if the value is within the constraint. * * @param val The value type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ virtual std::optional checkValueImpl(Value const& val) const = 0; /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ virtual void print(std::ostream& stream) const = 0; /** * @brief Custom output stream for constraint * * @param stream The output stream * @param cons The constraint * @return The same ostream we were given */ friend std::ostream& operator<<(std::ostream& stream, Constraint const& cons) { cons.print(stream); return stream; } }; /** * @brief A constraint to ensure the port number is within a valid range. */ class PortConstraint final : public Constraint { public: constexpr ~PortConstraint() noexcept override = default; private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param port The type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& port) const override; /** * @brief Check if the value is within the constraint. * * @param port The value to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& port) const override; /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { stream << fmt::format("The minimum value is `{}`. The maximum value is `{}`.", kPORT_MIN, kPORT_MAX); } static constexpr uint32_t kPORT_MIN = 1; static constexpr uint32_t kPORT_MAX = 65535; }; /** * @brief A constraint to ensure the IP address is valid. */ class ValidIPConstraint final : public Constraint { public: constexpr ~ValidIPConstraint() noexcept override = default; private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param ip The type to be checked. * @return An optional Error if the constraint is not met, std::nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& ip) const override; /** * @brief Check if the value is within the constraint. * * @param ip The value to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& ip) const override; /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { stream << "The value must be a valid IP address."; } }; /** * @brief A constraint class to ensure the provided value is one of the specified values in an array. * * @tparam arrSize The size of the array containing the valid values for the constraint */ template class OneOf final : public Constraint { public: /** * @brief Constructs a constraint where the value must be one of the values in the provided array. * * @param key The key of the ConfigValue that has this constraint * @param arr The value that has this constraint must be of the values in arr */ constexpr OneOf(std::string_view key, std::array arr) : key_{key}, arr_{arr} { } constexpr ~OneOf() noexcept override = default; private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param val The type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& val) const override { if (!std::holds_alternative(val)) return Error{fmt::format(R"(Key "{}"'s value must be a string)", key_)}; return std::nullopt; } /** * @brief Check if the value matches one of the value in the provided array * * @param val The value to check * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& val) const override { namespace rg = std::ranges; auto const check = [&val](std::string_view name) { return std::get(val) == name; }; if (rg::any_of(arr_, check)) return std::nullopt; return Error{makeErrorMsg(key_, val, arr_)}; } /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { std::string valuesStream; std::ranges::for_each(arr_, [&valuesStream](std::string_view elem) { valuesStream += fmt::format(" `{}`,", elem); }); // replace the last "," with "." valuesStream.back() = '.'; stream << fmt::format("The value must be one of the following:{}", valuesStream); } std::string_view key_; std::array arr_; }; /** * @brief A constraint class to ensure an integer value is between two numbers (inclusive) */ template class NumberValueConstraint final : public Constraint { public: /** * @brief Constructs a constraint where the number must be between min_ and max_. * * @param min the minimum number it can be to satisfy this constraint * @param max the maximum number it can be to satisfy this constraint */ constexpr NumberValueConstraint(NumType min, NumType max) : min_{min}, max_{max} { } constexpr ~NumberValueConstraint() noexcept override = default; private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param num The type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& num) const override { if (!std::holds_alternative(num)) return Error{"Number must be of type integer"}; return std::nullopt; } /** * @brief Check if the number is positive. * * @param num The number to check * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& num) const override { auto const numValue = std::get(num); if (numValue >= static_cast(min_) && numValue <= static_cast(max_)) return std::nullopt; return Error{fmt::format("Number must be between {} and {}", min_, max_)}; } /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { stream << fmt::format("The minimum value is `{}`. The maximum value is `{}`.", min_, max_); } NumType min_; NumType max_; }; /** * @brief A constraint to ensure a double number is positive */ class PositiveDouble final : public Constraint { public: constexpr ~PositiveDouble() noexcept override = default; private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param num The type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& num) const override; /** * @brief Check if the number is positive. * * @param num The number to check * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& num) const override; /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { stream << "The value must be a positive double number."; } }; /** * @brief A constraint to ensure the value is a valid RPC command name. */ class RpcNameConstraint final : public Constraint { private: /** * @brief Check if the type of the value is correct for this specific constraint. * * @param value The type to be checked * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkTypeImpl(Value const& value) const override; /** * @brief Check if the value is a valid RPC command name. * * @param value The value to check * @return An Error object if the constraint is not met, nullopt otherwise */ [[nodiscard]] std::optional checkValueImpl(Value const& value) const override; /** * @brief Prints to the output stream for this specific constraint. * * @param stream The output stream */ void print(std::ostream& stream) const override { stream << "Checks whether provided RPC name is valid"; } }; static constinit PortConstraint gValidatePort{}; static constinit ValidIPConstraint gValidateIp{}; static constinit OneOf gValidateChannelName{"channel", Logger::kCHANNELS}; static constinit OneOf gValidateLogLevelName{"log.level", kLOG_LEVELS}; static constinit OneOf gValidateCassandraName{"database.type", kDATABASE_TYPE}; static constinit OneOf gValidateLoadMode{"cache.load", kLOAD_CACHE_MODE}; static constinit OneOf gValidateLogTag{"log.tag_style", kLOG_TAGS}; static constinit OneOf gValidateProcessingPolicy{"server.processing_policy", kPROCESSING_POLICY}; static constinit OneOf gValidateProvider{"database.cassandra.provider", kPROVIDER}; static constinit PositiveDouble gValidatePositiveDouble{}; static constinit NumberValueConstraint gValidateNumMarkers{1, 256}; static constinit NumberValueConstraint gValidateNumCursors{0, std::numeric_limits::max()}; // replication factor can be 0 static constinit NumberValueConstraint gValidateReplicationFactor{0, std::numeric_limits::max()}; static constinit NumberValueConstraint gValidateUint16{1, std::numeric_limits::max()}; static constinit NumberValueConstraint gValidateUint32{1, std::numeric_limits::max()}; static constinit NumberValueConstraint gValidateNonNegativeUint32{0, std::numeric_limits::max()}; static constinit NumberValueConstraint gValidateApiVersion{rpc::kAPI_VERSION_MIN, rpc::kAPI_VERSION_MAX}; static constinit RpcNameConstraint gRpcNameConstraint{}; } // namespace util::config