mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	@@ -21,6 +21,7 @@
 | 
			
		||||
 | 
			
		||||
#include "migration/MigrationApplication.hpp"
 | 
			
		||||
#include "util/build/Build.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigDescription.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/program_options/options_description.hpp>
 | 
			
		||||
#include <boost/program_options/parsers.hpp>
 | 
			
		||||
@@ -29,6 +30,7 @@
 | 
			
		||||
#include <boost/program_options/variables_map.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
@@ -42,12 +44,13 @@ CliArgs::parse(int argc, char const* argv[])
 | 
			
		||||
    // clang-format off
 | 
			
		||||
    po::options_description description("Options");
 | 
			
		||||
    description.add_options()
 | 
			
		||||
        ("help,h", "print help message and exit")
 | 
			
		||||
        ("version,v", "print version and exit")
 | 
			
		||||
        ("conf,c", po::value<std::string>()->default_value(kDEFAULT_CONFIG_PATH), "configuration file")
 | 
			
		||||
        ("help,h", "Print help message and exit")
 | 
			
		||||
        ("version,v", "Print version and exit")
 | 
			
		||||
        ("conf,c", po::value<std::string>()->default_value(kDEFAULT_CONFIG_PATH), "Configuration file")
 | 
			
		||||
        ("ng-web-server,w", "Use ng-web-server")
 | 
			
		||||
        ("migrate", po::value<std::string>(), "start migration helper")
 | 
			
		||||
        ("migrate", po::value<std::string>(), "Start migration helper")
 | 
			
		||||
        ("verify", "Checks the validity of config values")
 | 
			
		||||
        ("config-description,d", po::value<std::string>(), "Generate config description markdown file")
 | 
			
		||||
    ;
 | 
			
		||||
    // clang-format on
 | 
			
		||||
    po::positional_options_description positional;
 | 
			
		||||
@@ -67,6 +70,17 @@ CliArgs::parse(int argc, char const* argv[])
 | 
			
		||||
        return Action{Action::Exit{EXIT_SUCCESS}};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parsed.count("config-description") != 0u) {
 | 
			
		||||
        std::filesystem::path filePath = parsed["config-description"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
        auto const res = util::config::ClioConfigDescription::generateConfigDescriptionToFile(filePath);
 | 
			
		||||
        if (res.has_value())
 | 
			
		||||
            return Action{Action::Exit{EXIT_SUCCESS}};
 | 
			
		||||
 | 
			
		||||
        std::cerr << res.error().error << std::endl;
 | 
			
		||||
        return Action{Action::Exit{EXIT_FAILURE}};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto configPath = parsed["conf"].as<std::string>();
 | 
			
		||||
 | 
			
		||||
    if (parsed.count("migrate") != 0u) {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ target_sources(
 | 
			
		||||
          newconfig/ConfigDefinition.cpp
 | 
			
		||||
          newconfig/ConfigFileJson.cpp
 | 
			
		||||
          newconfig/ObjectView.cpp
 | 
			
		||||
          newconfig/Types.cpp
 | 
			
		||||
          newconfig/ValueView.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
@@ -103,4 +104,18 @@ private:
 | 
			
		||||
    std::vector<ConfigValue> elements_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Custom output stream for Array
 | 
			
		||||
 *
 | 
			
		||||
 * @param stream The output stream
 | 
			
		||||
 * @param arr The Array
 | 
			
		||||
 * @return The same ostream we were given
 | 
			
		||||
 */
 | 
			
		||||
inline std::ostream&
 | 
			
		||||
operator<<(std::ostream& stream, Array arr)
 | 
			
		||||
{
 | 
			
		||||
    stream << arr.getArrayPattern();
 | 
			
		||||
    return stream;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <variant>
 | 
			
		||||
@@ -149,6 +150,28 @@ protected:
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::optional<Error>
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -177,6 +200,17 @@ private:
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    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;
 | 
			
		||||
};
 | 
			
		||||
@@ -206,6 +240,17 @@ private:
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    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";
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -260,6 +305,17 @@ private:
 | 
			
		||||
        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
 | 
			
		||||
    {
 | 
			
		||||
        stream << fmt::format("The value must be one of the following: `{}`", fmt::join(arr_, ", "));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string_view key_;
 | 
			
		||||
    std::array<char const*, ArrSize> arr_;
 | 
			
		||||
};
 | 
			
		||||
@@ -312,6 +368,17 @@ private:
 | 
			
		||||
        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_;
 | 
			
		||||
};
 | 
			
		||||
@@ -341,6 +408,17 @@ private:
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    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 << fmt::format("The value must be a positive double number");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static constinit PortConstraint gValidatePort{};
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ ClioConfigDefinition::getObject(std::string_view prefix, std::optional<std::size
 | 
			
		||||
        auto const hasPrefix = mapKey.starts_with(prefixWithDot);
 | 
			
		||||
        if (idx.has_value() && hasPrefix && std::holds_alternative<Array>(mapVal)) {
 | 
			
		||||
            ASSERT(std::get<Array>(mapVal).size() > idx.value(), "Index provided is out of scope");
 | 
			
		||||
 | 
			
		||||
            // we want to support getObject("array") and getObject("array.[]"), so we check if "[]" exists
 | 
			
		||||
            if (!prefix.contains("[]"))
 | 
			
		||||
                return ObjectView{prefixWithDot + "[]", idx.value(), *this};
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/Array.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigConstraints.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigDescription.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigFileInterface.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
@@ -31,16 +30,10 @@
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
#include "util/newconfig/ValueView.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <initializer_list>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -84,26 +77,6 @@ public:
 | 
			
		||||
    [[nodiscard]] std::optional<std::vector<Error>>
 | 
			
		||||
    parse(ConfigFileInterface const& config);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Validates the configuration file
 | 
			
		||||
     *
 | 
			
		||||
     * Should only check for valid values, without populating
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The configuration file interface
 | 
			
		||||
     * @return An optional vector of Error objects stating all the failures if validation fails
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<std::vector<Error>>
 | 
			
		||||
    validate(ConfigFileInterface const& config) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Generate markdown file of all the clio config descriptions
 | 
			
		||||
     *
 | 
			
		||||
     * @param configDescription The configuration description object
 | 
			
		||||
     * @return An optional Error if generating markdown fails
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::expected<std::string, Error>
 | 
			
		||||
    getMarkdown(ClioConfigDescription const& configDescription) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns the ObjectView specified with the prefix
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,19 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigDefinition.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
@@ -61,26 +71,90 @@ public:
 | 
			
		||||
        return itr->value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Generate markdown file of all the clio config descriptions
 | 
			
		||||
     *
 | 
			
		||||
     * @param path The path location to generate the Config-description file
 | 
			
		||||
     * @return An Error if generating markdown fails, otherwise nothing
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] static std::expected<void, Error>
 | 
			
		||||
    generateConfigDescriptionToFile(std::filesystem::path path)
 | 
			
		||||
    {
 | 
			
		||||
        namespace fs = std::filesystem;
 | 
			
		||||
 | 
			
		||||
        // Validate the directory exists
 | 
			
		||||
        auto const dir = path.parent_path();
 | 
			
		||||
        if (!dir.empty() && !fs::exists(dir)) {
 | 
			
		||||
            return std::unexpected<Error>{
 | 
			
		||||
                fmt::format("Error: Directory '{}' does not exist or provided path is invalid", dir.string())
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::ofstream file(path.string());
 | 
			
		||||
        if (!file.is_open()) {
 | 
			
		||||
            return std::unexpected{fmt::format("Failed to create file '{}': {}", path.string(), std::strerror(errno))};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        writeConfigDescriptionToFile(file);
 | 
			
		||||
        file.close();
 | 
			
		||||
 | 
			
		||||
        std::cout << "Markdown file generated successfully: " << path << "\n";
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Writes to Config description to file
 | 
			
		||||
     *
 | 
			
		||||
     * @param file The config file to write to
 | 
			
		||||
     */
 | 
			
		||||
    static void
 | 
			
		||||
    writeConfigDescriptionToFile(std::ostream& file)
 | 
			
		||||
    {
 | 
			
		||||
        file << "# Clio Config Description\n";
 | 
			
		||||
        file << "This file lists all Clio Configuration definitions in detail.\n\n";
 | 
			
		||||
        file << "## Configuration Details\n\n";
 | 
			
		||||
 | 
			
		||||
        for (auto const& [key, val] : kCONFIG_DESCRIPTION) {
 | 
			
		||||
            file << "### Key: " << key << "\n";
 | 
			
		||||
 | 
			
		||||
            // Every type of value is directed to operator<< in ConfigValue.hpp
 | 
			
		||||
            // as ConfigValue is the one that holds all the info regarding the config values
 | 
			
		||||
            if (key.contains("[]")) {
 | 
			
		||||
                file << gClioConfig.asArray(key);
 | 
			
		||||
            } else {
 | 
			
		||||
                file << gClioConfig.getValueView(key);
 | 
			
		||||
            }
 | 
			
		||||
            file << " -   **Description**: " << val << "\n";
 | 
			
		||||
        }
 | 
			
		||||
        file << "\n";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    static constexpr auto kCONFIG_DESCRIPTION = std::array{
 | 
			
		||||
        KV{.key = "database.type", .value = "Type of database to use. Default is Scylladb."},
 | 
			
		||||
        KV{.key = "database.type",
 | 
			
		||||
           .value = "Type of database to use. We currently support Cassandra and Scylladb. We default to Scylladb."},
 | 
			
		||||
        KV{.key = "database.cassandra.contact_points",
 | 
			
		||||
           .value =
 | 
			
		||||
               "A list of IP addresses or hostnames of the initial nodes (Cassandra/Scylladb cluster nodes) that the "
 | 
			
		||||
               "client will connect to when establishing a connection with the database."},
 | 
			
		||||
           .value = "A list of IP addresses or hostnames of the initial nodes (Cassandra/Scylladb cluster nodes) that "
 | 
			
		||||
                    "the client will connect to when establishing a connection with the database. If you're running "
 | 
			
		||||
                    "locally, it should be 'localhost' or 127.0.0.1"},
 | 
			
		||||
        KV{.key = "database.cassandra.secure_connect_bundle",
 | 
			
		||||
           .value = "Configuration file that contains the necessary security credentials and connection details for "
 | 
			
		||||
                    "securely "
 | 
			
		||||
                    "connecting to a Cassandra database cluster."},
 | 
			
		||||
        KV{.key = "database.cassandra.port", .value = "Port number to connect to Cassandra."},
 | 
			
		||||
        KV{.key = "database.cassandra.keyspace", .value = "Keyspace to use in Cassandra."},
 | 
			
		||||
        KV{.key = "database.cassandra.replication_factor", .value = "Number of replicated nodes for Scylladb."},
 | 
			
		||||
        KV{.key = "database.cassandra.table_prefix", .value = "Prefix for Cassandra table names."},
 | 
			
		||||
        KV{.key = "database.cassandra.port", .value = "Port number to connect to the database."},
 | 
			
		||||
        KV{.key = "database.cassandra.keyspace", .value = "Keyspace to use for the database."},
 | 
			
		||||
        KV{.key = "database.cassandra.replication_factor",
 | 
			
		||||
           .value = "Number of replicated nodes for Scylladb. Visit this link for more details : "
 | 
			
		||||
                    "https://university.scylladb.com/courses/scylla-essentials-overview/lessons/high-availability/"
 | 
			
		||||
                    "topic/fault-tolerance-replication-factor/ "},
 | 
			
		||||
        KV{.key = "database.cassandra.table_prefix", .value = "Prefix for Database table names."},
 | 
			
		||||
        KV{.key = "database.cassandra.max_write_requests_outstanding",
 | 
			
		||||
           .value = "Maximum number of outstanding write requests."},
 | 
			
		||||
           .value = "Maximum number of outstanding write requests. Write requests are api calls that write to database "
 | 
			
		||||
        },
 | 
			
		||||
        KV{.key = "database.cassandra.max_read_requests_outstanding",
 | 
			
		||||
           .value = "Maximum number of outstanding read requests."},
 | 
			
		||||
        KV{.key = "database.cassandra.threads", .value = "Number of threads for Cassandra operations."},
 | 
			
		||||
           .value = "Maximum number of outstanding read requests, which reads from database"},
 | 
			
		||||
        KV{.key = "database.cassandra.threads", .value = "Number of threads that will be used for database operations."
 | 
			
		||||
        },
 | 
			
		||||
        KV{.key = "database.cassandra.core_connections_per_host",
 | 
			
		||||
           .value = "Number of core connections per host for Cassandra."},
 | 
			
		||||
        KV{.key = "database.cassandra.queue_size_io", .value = "Queue size for I/O operations in Cassandra."},
 | 
			
		||||
@@ -106,10 +180,10 @@ private:
 | 
			
		||||
           .value = "Timeout duration for the forwarding cache used in Rippled communication."},
 | 
			
		||||
        KV{.key = "forwarding.request_timeout",
 | 
			
		||||
           .value = "Timeout duration for the forwarding request used in Rippled communication."},
 | 
			
		||||
        KV{.key = "rpc.cache_timeout", .value = "Timeout duration for the rpc request."},
 | 
			
		||||
        KV{.key = "rpc.cache_timeout", .value = "Timeout duration for RPC requests."},
 | 
			
		||||
        KV{.key = "num_markers",
 | 
			
		||||
           .value = "The number of markers is the number of coroutines to load the cache concurrently."},
 | 
			
		||||
        KV{.key = "dos_guard.[].whitelist", .value = "List of IP addresses to whitelist for DOS protection."},
 | 
			
		||||
           .value = "The number of markers is the number of coroutines to download the initial ledger"},
 | 
			
		||||
        KV{.key = "dos_guard.whitelist.[]", .value = "List of IP addresses to whitelist for DOS protection."},
 | 
			
		||||
        KV{.key = "dos_guard.max_fetches", .value = "Maximum number of fetch operations allowed by DOS guard."},
 | 
			
		||||
        KV{.key = "dos_guard.max_connections", .value = "Maximum number of concurrent connections allowed by DOS guard."
 | 
			
		||||
        },
 | 
			
		||||
@@ -120,39 +194,53 @@ private:
 | 
			
		||||
        KV{.key = "server.port", .value = "Port number of the Clio HTTP server."},
 | 
			
		||||
        KV{.key = "server.max_queue_size",
 | 
			
		||||
           .value = "Maximum size of the server's request queue. Value of 0 is no limit."},
 | 
			
		||||
        KV{.key = "server.local_admin", .value = "Indicates if the server should run with admin privileges."},
 | 
			
		||||
        KV{.key = "server.admin_password", .value = "Password for Clio admin-only APIs."},
 | 
			
		||||
        KV{.key = "server.local_admin",
 | 
			
		||||
           .value = "Indicates if the server should run with admin privileges. Only one of local_admin or "
 | 
			
		||||
                    "admin_password can be set."},
 | 
			
		||||
        KV{.key = "server.admin_password",
 | 
			
		||||
           .value = "Password for Clio admin-only APIs. Only one of local_admin or admin_password can be set."},
 | 
			
		||||
        KV{.key = "server.processing_policy",
 | 
			
		||||
           .value = R"(Could be "sequent" or "parallel". For the sequent policy, requests from a single client 
 | 
			
		||||
        connection are processed one by one, with the next request read only after the previous one is processed. For the parallel policy, Clio will accept
 | 
			
		||||
         all requests and process them in parallel, sending a reply for each request as soon as it is ready.)"},
 | 
			
		||||
        KV{.key = "server.parallel_requests_limit",
 | 
			
		||||
           .value = R"(Optional parameter, used only if "processing_strategy" is
 | 
			
		||||
         "parallel". It limits the number of requests for a single client connection that are processed in parallel. If not specified, the limit is infinite.)"
 | 
			
		||||
           .value =
 | 
			
		||||
               R"(Optional parameter, used only if processing_strategy `parallel`. It limits the number of requests for a single client connection that are processed in parallel. If not specified, the limit is infinite.)"
 | 
			
		||||
        },
 | 
			
		||||
        KV{.key = "server.ws_max_sending_queue_size", .value = "Maximum size of the websocket sending queue."},
 | 
			
		||||
        KV{.key = "prometheus.enabled", .value = "Enable or disable Prometheus metrics."},
 | 
			
		||||
        KV{.key = "prometheus.compress_reply", .value = "Enable or disable compression of Prometheus responses."},
 | 
			
		||||
        KV{.key = "io_threads", .value = "Number of I/O threads. Value must be greater than 1"},
 | 
			
		||||
        KV{.key = "io_threads", .value = "Number of I/O threads. Value cannot be less than 1"},
 | 
			
		||||
        KV{.key = "subscription_workers",
 | 
			
		||||
           .value = "The number of worker threads or processes that are responsible for managing and processing "
 | 
			
		||||
                    "subscription-based tasks."},
 | 
			
		||||
                    "subscription-based tasks from rippled"},
 | 
			
		||||
        KV{.key = "graceful_period", .value = "Number of milliseconds server will wait to shutdown gracefully."},
 | 
			
		||||
        KV{.key = "cache.num_diffs", .value = "Number of diffs to cache."},
 | 
			
		||||
        KV{.key = "cache.num_diffs", .value = "Number of diffs to cache. For more info, consult readme.md in etc"},
 | 
			
		||||
        KV{.key = "cache.num_markers", .value = "Number of markers to cache."},
 | 
			
		||||
        KV{.key = "cache.num_cursors_from_diff", .value = "Num of cursors that are different."},
 | 
			
		||||
        KV{.key = "cache.num_cursors_from_account", .value = "Number of cursors from an account."},
 | 
			
		||||
        KV{.key = "cache.page_fetch_size", .value = "Page fetch size for cache operations."},
 | 
			
		||||
        KV{.key = "cache.load", .value = "Cache loading strategy ('sync' or 'async')."},
 | 
			
		||||
        KV{.key = "log_channels.[].channel", .value = "Name of the log channel."},
 | 
			
		||||
        KV{.key = "log_channels.[].log_level", .value = "Log level for the log channel."},
 | 
			
		||||
        KV{.key = "log_level", .value = "General logging level of Clio."},
 | 
			
		||||
        KV{.key = "log_channels.[].channel",
 | 
			
		||||
           .value = "Name of the log channel."
 | 
			
		||||
                    "'RPC', 'ETL', and 'Performance'"},
 | 
			
		||||
        KV{.key = "log_channels.[].log_level",
 | 
			
		||||
           .value = "Log level for the specific log channel."
 | 
			
		||||
                    "`warning`, `error`, `fatal`"},
 | 
			
		||||
        KV{.key = "log_level",
 | 
			
		||||
           .value = "General logging level of Clio. This level will be applied to all log channels that do not have an "
 | 
			
		||||
                    "explicitly defined logging level."},
 | 
			
		||||
        KV{.key = "log_format", .value = "Format string for log messages."},
 | 
			
		||||
        KV{.key = "log_to_console", .value = "Enable or disable logging to console."},
 | 
			
		||||
        KV{.key = "log_directory", .value = "Directory path for log files."},
 | 
			
		||||
        KV{.key = "log_rotation_size", .value = "Log rotation size in megabytes."},
 | 
			
		||||
        KV{.key = "log_rotation_size",
 | 
			
		||||
           .value =
 | 
			
		||||
               "Log rotation size in megabytes. When the log file reaches this particular size, a new log file starts."
 | 
			
		||||
        },
 | 
			
		||||
        KV{.key = "log_directory_max_size", .value = "Maximum size of the log directory in megabytes."},
 | 
			
		||||
        KV{.key = "log_rotation_hour_interval", .value = "Interval in hours for log rotation."},
 | 
			
		||||
        KV{.key = "log_rotation_hour_interval",
 | 
			
		||||
           .value = "Interval in hours for log rotation. If the current log file reaches this value in logging, a new "
 | 
			
		||||
                    "log file starts."},
 | 
			
		||||
        KV{.key = "log_tag_style", .value = "Style for log tags."},
 | 
			
		||||
        KV{.key = "extractor_threads", .value = "Number of extractor threads."},
 | 
			
		||||
        KV{.key = "read_only", .value = "Indicates if the server should have read-only privileges."},
 | 
			
		||||
@@ -164,8 +252,8 @@ private:
 | 
			
		||||
        KV{.key = "api_version.default", .value = "Default API version Clio will run on."},
 | 
			
		||||
        KV{.key = "api_version.min", .value = "Minimum API version."},
 | 
			
		||||
        KV{.key = "api_version.max", .value = "Maximum API version."},
 | 
			
		||||
        KV{.key = "migration.full_scan_threads", .value = "The number of threads used to scan table."},
 | 
			
		||||
        KV{.key = "migration.full_scan_jobs", .value = "The number of coroutines used to scan table."},
 | 
			
		||||
        KV{.key = "migration.full_scan_threads", .value = "The number of threads used to scan the table."},
 | 
			
		||||
        KV{.key = "migration.full_scan_jobs", .value = "The number of coroutines used to scan the table."},
 | 
			
		||||
        KV{.key = "migration.cursors_per_job", .value = "The number of cursors each coroutine will scan."}
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <variant>
 | 
			
		||||
@@ -141,7 +142,7 @@ public:
 | 
			
		||||
     *
 | 
			
		||||
     * @return An optional reference to the associated Constraint.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<std::reference_wrapper<Constraint const>>
 | 
			
		||||
    [[nodiscard]] constexpr std::optional<std::reference_wrapper<Constraint const>>
 | 
			
		||||
    getConstraint() const
 | 
			
		||||
    {
 | 
			
		||||
        return cons_;
 | 
			
		||||
@@ -201,6 +202,29 @@ public:
 | 
			
		||||
        return value_.value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Prints all the info of this config value to the output stream.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stream The output stream
 | 
			
		||||
     * @param val The config value to output to osstream
 | 
			
		||||
     * @return The same ostream we were given
 | 
			
		||||
     */
 | 
			
		||||
    friend std::ostream&
 | 
			
		||||
    operator<<(std::ostream& stream, ConfigValue val)
 | 
			
		||||
    {
 | 
			
		||||
        stream << "- **Required**: " << (val.isOptional() ? "False" : "True") << "\n";
 | 
			
		||||
        stream << "- **Type**: " << val.type() << "\n";
 | 
			
		||||
        stream << "- **Default value**: " << (val.hasValue() ? *val.value_ : "None") << "\n";
 | 
			
		||||
        stream << "- **Constraints**: ";
 | 
			
		||||
 | 
			
		||||
        if (val.getConstraint().has_value()) {
 | 
			
		||||
            stream << val.getConstraint()->get() << "\n";
 | 
			
		||||
        } else {
 | 
			
		||||
            stream << "None" << "\n";
 | 
			
		||||
        }
 | 
			
		||||
        return stream;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Checks if the value type is consistent with the specified ConfigType
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								src/util/newconfig/Types.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/util/newconfig/Types.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
   This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
   Copyright (c) 2025, 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.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
std::ostream&
 | 
			
		||||
operator<<(std::ostream& stream, ConfigType type)
 | 
			
		||||
{
 | 
			
		||||
    switch (type) {
 | 
			
		||||
        case ConfigType::Integer:
 | 
			
		||||
            stream << "int";
 | 
			
		||||
            break;
 | 
			
		||||
        case ConfigType::String:
 | 
			
		||||
            stream << "string";
 | 
			
		||||
            break;
 | 
			
		||||
        case ConfigType::Double:
 | 
			
		||||
            stream << "double";
 | 
			
		||||
            break;
 | 
			
		||||
        case ConfigType::Boolean:
 | 
			
		||||
            stream << "boolean";
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            stream << "unsupported type";
 | 
			
		||||
    }
 | 
			
		||||
    return stream;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::ostream&
 | 
			
		||||
operator<<(std::ostream& stream, Value value)
 | 
			
		||||
{
 | 
			
		||||
    if (std::holds_alternative<std::string>(value)) {
 | 
			
		||||
        stream << std::get<std::string>(value);
 | 
			
		||||
    } else if (std::holds_alternative<bool>(value)) {
 | 
			
		||||
        stream << (std::get<bool>(value) ? "False" : "True");
 | 
			
		||||
    } else if (std::holds_alternative<double>(value)) {
 | 
			
		||||
        stream << std::get<double>(value);
 | 
			
		||||
    } else if (std::holds_alternative<int64_t>(value)) {
 | 
			
		||||
        stream << std::get<int64_t>(value);
 | 
			
		||||
    }
 | 
			
		||||
    return stream;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
@@ -22,8 +22,9 @@
 | 
			
		||||
#include "util/UnsupportedType.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
@@ -31,9 +32,29 @@ namespace util::config {
 | 
			
		||||
/** @brief Custom clio config types */
 | 
			
		||||
enum class ConfigType { Integer, String, Double, Boolean };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Prints the specified config type to output stream
 | 
			
		||||
 *
 | 
			
		||||
 * @param stream The output stream
 | 
			
		||||
 * @param type The config type
 | 
			
		||||
 * @return The same ostream we were given
 | 
			
		||||
 */
 | 
			
		||||
std::ostream&
 | 
			
		||||
operator<<(std::ostream& stream, ConfigType type);
 | 
			
		||||
 | 
			
		||||
/** @brief Represents the supported Config Values */
 | 
			
		||||
using Value = std::variant<int64_t, std::string, bool, double>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Prints the specified value to output stream
 | 
			
		||||
 *
 | 
			
		||||
 * @param stream The output stream
 | 
			
		||||
 * @param value The value type
 | 
			
		||||
 * @return The same ostream we were given
 | 
			
		||||
 */
 | 
			
		||||
std::ostream&
 | 
			
		||||
operator<<(std::ostream& stream, Value value);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the corresponding clio config type
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigConstraints.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -30,6 +31,7 @@
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
@@ -141,6 +143,17 @@ public:
 | 
			
		||||
        return configVal_.get().isOptional();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves the constraint associated with the ConfigValue in this ValueView, if any.
 | 
			
		||||
     *
 | 
			
		||||
     * @return An optional reference to the associated Constraint
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr std::optional<std::reference_wrapper<Constraint const>>
 | 
			
		||||
    getConstraint() const
 | 
			
		||||
    {
 | 
			
		||||
        return configVal_.get().getConstraint();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves the stored value as the specified type T
 | 
			
		||||
     *
 | 
			
		||||
@@ -186,6 +199,20 @@ public:
 | 
			
		||||
        return std::make_optional(getValueImpl<T>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Custom output stream for ValueView
 | 
			
		||||
     *
 | 
			
		||||
     * @param stream The output stream
 | 
			
		||||
     * @param value The ValueView
 | 
			
		||||
     * @return The same ostream we were given
 | 
			
		||||
     */
 | 
			
		||||
    friend std::ostream&
 | 
			
		||||
    operator<<(std::ostream& stream, ValueView value)
 | 
			
		||||
    {
 | 
			
		||||
        stream << value.configVal_;
 | 
			
		||||
        return stream;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::reference_wrapper<ConfigValue const> configVal_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,12 @@ struct TmpFile {
 | 
			
		||||
        ofs << content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static TmpFile
 | 
			
		||||
    empty()
 | 
			
		||||
    {
 | 
			
		||||
        return TmpFile{""};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TmpFile(TmpFile const&) = delete;
 | 
			
		||||
    TmpFile(TmpFile&& other) : path{std::move(other.path)}
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,19 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "app/CliArgs.hpp"
 | 
			
		||||
#include "util/TmpFile.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigDefinition.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigDescription.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
 | 
			
		||||
using namespace app;
 | 
			
		||||
@@ -145,3 +152,71 @@ TEST_F(CliArgsTests, Parse_VerifyConfig)
 | 
			
		||||
        returnCode
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(CliArgsTests, Parse_ConfigDescriptionInvalidPath)
 | 
			
		||||
{
 | 
			
		||||
    using namespace util::config;
 | 
			
		||||
    std::array argv{"clio_server", "--config-description", ""};
 | 
			
		||||
    auto const action = CliArgs::parse(argv.size(), argv.data());
 | 
			
		||||
    EXPECT_CALL(onExitMock, Call).WillOnce([](CliArgs::Action::Exit const& exit) { return exit.exitCode; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(
 | 
			
		||||
        action.apply(
 | 
			
		||||
            onRunMock.AsStdFunction(),
 | 
			
		||||
            onExitMock.AsStdFunction(),
 | 
			
		||||
            onMigrateMock.AsStdFunction(),
 | 
			
		||||
            onVerifyMock.AsStdFunction()
 | 
			
		||||
        ),
 | 
			
		||||
        EXIT_FAILURE
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct CliArgsTestsWithTmpFile : CliArgsTests {
 | 
			
		||||
    TmpFile tmpFile = TmpFile::empty();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TEST_F(CliArgsTestsWithTmpFile, Parse_ConfigDescription)
 | 
			
		||||
{
 | 
			
		||||
    std::array argv{"clio_server", "--config-description", tmpFile.path.c_str()};
 | 
			
		||||
    auto const action = CliArgs::parse(argv.size(), argv.data());
 | 
			
		||||
    EXPECT_CALL(onExitMock, Call).WillOnce([](CliArgs::Action::Exit const& exit) { return exit.exitCode; });
 | 
			
		||||
 | 
			
		||||
    // user provide config markdown file name as well
 | 
			
		||||
    ASSERT_TRUE(std::filesystem::exists(tmpFile.path));
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(
 | 
			
		||||
        action.apply(
 | 
			
		||||
            onRunMock.AsStdFunction(),
 | 
			
		||||
            onExitMock.AsStdFunction(),
 | 
			
		||||
            onMigrateMock.AsStdFunction(),
 | 
			
		||||
            onVerifyMock.AsStdFunction()
 | 
			
		||||
        ),
 | 
			
		||||
        EXIT_SUCCESS
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(CliArgsTestsWithTmpFile, Parse_ConfigDescriptionFileContent)
 | 
			
		||||
{
 | 
			
		||||
    using namespace util::config;
 | 
			
		||||
 | 
			
		||||
    std::ofstream file(tmpFile.path);
 | 
			
		||||
    ASSERT_TRUE(file.is_open());
 | 
			
		||||
    ClioConfigDescription::writeConfigDescriptionToFile(file);
 | 
			
		||||
    file.close();
 | 
			
		||||
 | 
			
		||||
    std::ifstream inFile(tmpFile.path);
 | 
			
		||||
    ASSERT_TRUE(inFile.is_open());
 | 
			
		||||
 | 
			
		||||
    std::stringstream buffer;
 | 
			
		||||
    buffer << inFile.rdbuf();
 | 
			
		||||
    inFile.close();
 | 
			
		||||
 | 
			
		||||
    auto const fileContent = buffer.str();
 | 
			
		||||
    EXPECT_TRUE(fileContent.find("# Clio Config Description") != std::string::npos);
 | 
			
		||||
    EXPECT_TRUE(fileContent.find("This file lists all Clio Configuration definitions in detail.") != std::string::npos);
 | 
			
		||||
    EXPECT_TRUE(fileContent.find("## Configuration Details") != std::string::npos);
 | 
			
		||||
 | 
			
		||||
    // all keys that exist in clio config should be listed in config description file
 | 
			
		||||
    for (auto const& key : gClioConfig)
 | 
			
		||||
        EXPECT_TRUE(fileContent.find(key.first));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -165,7 +165,10 @@ TEST(ConfigDescription, GetValues)
 | 
			
		||||
{
 | 
			
		||||
    ClioConfigDescription const definition{};
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(definition.get("database.type"), "Type of database to use. Default is Scylladb.");
 | 
			
		||||
    EXPECT_EQ(
 | 
			
		||||
        definition.get("database.type"),
 | 
			
		||||
        "Type of database to use. We currently support Cassandra and Scylladb. We default to Scylladb."
 | 
			
		||||
    );
 | 
			
		||||
    EXPECT_EQ(definition.get("etl_sources.[].ip"), "IP address of the ETL source.");
 | 
			
		||||
    EXPECT_EQ(definition.get("prometheus.enabled"), "Enable or disable Prometheus metrics.");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user