mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-16 09:45:53 +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