refactor: Clio Config (#1544)

Implementation of new config definition + methods + UT

Steps that still need to be implemented: 
- Make ClioConfigDefinition and it's method to be as constexpr as
possible
- Getting User Config file and populating the values in ConfigDefinition
while checking for constraints on user values
- Replacing all the places where we fetch config values (by using
config.valueOr/MaybeValue) to instead get it from Config Definition
- Generate markdown file using Clio Config Description
This commit is contained in:
Peter Chen
2024-08-06 11:07:25 -04:00
committed by GitHub
parent 5abf912b5a
commit 2bd7ac346c
27 changed files with 2421 additions and 20 deletions

View File

@@ -21,6 +21,7 @@
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/Tuple.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <xrpl/basics/base_uint.h>
@@ -41,9 +42,6 @@
namespace data::cassandra::impl {
template <typename>
static constexpr bool unsupported_v = false;
template <typename Type>
inline Type
extractColumn(CassRow const* row, std::size_t idx)
@@ -103,7 +101,7 @@ extractColumn(CassRow const* row, std::size_t idx)
output = static_cast<DecayedType>(out);
} else {
// type not supported for extraction
static_assert(unsupported_v<DecayedType>);
static_assert(util::Unsupported<DecayedType>);
}
return output;

View File

@@ -23,6 +23,7 @@
#include "data/cassandra/impl/Collection.hpp"
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/Tuple.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <fmt/core.h>
@@ -44,9 +45,6 @@ namespace data::cassandra::impl {
class Statement : public ManagedObject<CassStatement> {
static constexpr auto deleter = [](CassStatement* ptr) { cass_statement_free(ptr); };
template <typename>
static constexpr bool unsupported_v = false;
public:
/**
* @brief Construct a new statement with optionally provided arguments.
@@ -141,7 +139,7 @@ public:
throwErrorIfNeeded(rc, "Bind int64");
} else {
// type not supported for binding
static_assert(unsupported_v<DecayedType>);
static_assert(util::Unsupported<DecayedType>);
}
}
};

View File

@@ -20,6 +20,7 @@
#pragma once
#include "data/cassandra/impl/ManagedObject.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <xrpl/basics/base_uint.h>
@@ -38,9 +39,6 @@ namespace data::cassandra::impl {
class Tuple : public ManagedObject<CassTuple> {
static constexpr auto deleter = [](CassTuple* ptr) { cass_tuple_free(ptr); };
template <typename>
static constexpr bool unsupported_v = false;
public:
/* implicit */ Tuple(CassTuple* ptr);
@@ -91,15 +89,12 @@ public:
throwErrorIfNeeded(rc, "Bind ripple::uint256");
} else {
// type not supported for binding
static_assert(unsupported_v<DecayedType>);
static_assert(util::Unsupported<DecayedType>);
}
}
};
class TupleIterator : public ManagedObject<CassIterator> {
template <typename>
static constexpr bool unsupported_v = false;
public:
/* implicit */ TupleIterator(CassIterator* ptr);
@@ -141,7 +136,7 @@ private:
output = static_cast<DecayedType>(out);
} else {
// type not supported for extraction
static_assert(unsupported_v<DecayedType>);
static_assert(util::Unsupported<DecayedType>);
}
return output;

View File

@@ -23,6 +23,7 @@
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Concepts.hpp"
#include "rpc/common/Types.hpp"
#include "util/UnsupportedType.hpp"
#include <boost/json/array.hpp>
#include <boost/json/value.hpp>
@@ -36,9 +37,6 @@
namespace rpc::impl {
template <typename>
static constexpr bool unsupported_v = false;
using FieldSpecProcessor = std::function<MaybeError(boost::json::value&)>;
static FieldSpecProcessor const EMPTY_FIELD_PROCESSOR = [](boost::json::value&) -> MaybeError { return {}; };
@@ -64,7 +62,7 @@ makeFieldProcessor(std::string const& key, Processors&&... procs)
if (auto const res = req->modify(j, key); not res)
firstFailure = res.error();
} else {
static_assert(unsupported_v<decltype(*req)>);
static_assert(util::Unsupported<decltype(*req)>);
}
}(),
...

View File

@@ -24,6 +24,10 @@ target_sources(
TimeUtils.cpp
TxUtils.cpp
LedgerUtils.cpp
newconfig/ConfigDefinition.cpp
newconfig/ObjectView.cpp
newconfig/ArrayView.cpp
newconfig/ValueView.cpp
)
target_link_libraries(

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
/*
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
namespace util {
/** @brief used for compile time checking of unsupported types */
template <typename>
static constexpr bool Unsupported = false;
} // namespace util

View File

@@ -0,0 +1,92 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
namespace util::config {
/**
* @brief Array definition for Json/Yaml config
*
* Used in ClioConfigDefinition to represent multiple potential values (like whitelist)
*/
class Array {
public:
/**
* @brief Constructs an Array with the provided arguments
*
* @tparam Args Types of the arguments
* @param args Arguments to initialize the elements of the Array
*/
template <typename... Args>
constexpr Array(Args&&... args) : elements_{std::forward<Args>(args)...}
{
}
/**
* @brief Add ConfigValues to Array class
*
* @param value The ConfigValue to add
*/
void
emplaceBack(ConfigValue value)
{
elements_.push_back(std::move(value));
}
/**
* @brief Returns the number of values stored in the Array
*
* @return Number of values stored in the Array
*/
[[nodiscard]] size_t
size() const
{
return elements_.size();
}
/**
* @brief Returns the ConfigValue at the specified index
*
* @param idx Index of the ConfigValue to retrieve
* @return ConfigValue at the specified index
*/
[[nodiscard]] ConfigValue const&
at(std::size_t idx) const
{
ASSERT(idx < elements_.size(), "index is out of scope");
return elements_[idx];
}
private:
std::vector<ConfigValue> elements_;
};
} // namespace util::config

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ArrayView.hpp"
#include "util/Assert.hpp"
#include "util/newconfig/Array.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <cstddef>
#include <string_view>
namespace util::config {
ArrayView::ArrayView(std::string_view prefix, ClioConfigDefinition const& configDef)
: prefix_{prefix}, clioConfig_{configDef}
{
}
ValueView
ArrayView::valueAt(std::size_t idx) const
{
ASSERT(clioConfig_.get().contains(prefix_), "Current string {} is a prefix, not a key of config", prefix_);
ConfigValue const& val = clioConfig_.get().asArray(prefix_).at(idx);
return ValueView{val};
}
size_t
ArrayView::size() const
{
return clioConfig_.get().arraySize(prefix_);
}
ObjectView
ArrayView::objectAt(std::size_t idx) const
{
ASSERT(idx < this->size(), "Object index is out of scope");
return ObjectView{prefix_, idx, clioConfig_};
}
} // namespace util::config

View File

@@ -0,0 +1,203 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <cstddef>
#include <functional>
#include <iterator>
#include <string>
#include <string_view>
namespace util::config {
/**
* @brief View for array structure for config.
*
* This class provides a view into an array structure within ClioConfigDefinition.
* It allows accessing individual elements of the array as either values or objects, and
* is used within the ClioConfigDefinition to represent multiple potential values.
*/
class ArrayView {
public:
/**
* @brief Custom iterator class which contains config object or value underneath ArrayView
*/
template <typename T>
struct ArrayIterator {
using iterator_category = std::forward_iterator_tag;
using pointer = T const*;
using reference = T const&;
using value_type = T;
/**
* @brief Constructs an ArrayIterator with underlying ArrayView and index value
*
* @param arr ArrayView to iterate
* @param index Current index of the ArrayView
*/
ArrayIterator(ArrayView const& arr, std::size_t index) : arr_{arr}, index_{index}
{
if (arr_.clioConfig_.get().contains(arr_.prefix_)) {
ASSERT((std::is_same_v<T, ValueView>), "Array iterator must be ValueView");
} else {
ASSERT((std::is_same_v<T, ObjectView>), "Array iterator must be ObjectView");
}
}
/**
* @brief Prefix increment operator
*
* @return Reference to the incremented ArrayIterator
*/
ArrayIterator&
operator++()
{
if (index_ < arr_.size())
++index_;
return *this;
}
/**
* @brief Postfix increment operator
*
* @return Copy of the ArrayIterator before increment
*/
ArrayIterator
operator++(int)
{
ArrayIterator temp = *this;
if (index_ < arr_.size())
++index_;
return temp;
}
/**
* @brief Dereference operator to get a ValueView or ObjectView
*
* @return ValueView of the ConfigValue
*/
T
operator*()
{
if constexpr (std::is_same_v<T, ObjectView>) {
return ObjectView{arr_.prefix_, index_, arr_.clioConfig_.get()};
} else {
return arr_.clioConfig_.get().getValueInArray(arr_.prefix_, index_);
}
}
/**
* @brief Equality operator
*
* @param other Another ArrayIterator to compare
* @return true if iterators are pointing to the same element in the same ArrayView object, otherwise false
*/
bool
operator==(ArrayIterator const& other) const
{
return &arr_ == &(other.arr_) && index_ == other.index_;
}
/**
* @brief Inequality operator
*
* @param other Another ArrayIterator to compare
* @return true if iterators are not equal, otherwise false
*/
bool
operator!=(ArrayIterator const& other) const
{
return &arr_ != &(other.arr_) || index_ != other.index_;
}
private:
ArrayView const& arr_;
std::size_t index_ = 0;
};
/**
* @brief Returns an iterator to the beginning of the Array
*
* @return Iterator to the beginning of the Array
*/
template <typename T>
[[nodiscard]] auto
begin() const
{
return ArrayIterator<T>(*this, 0);
}
/**
* @brief Returns an iterator to the end of the Array
*
* @return Iterator to the end of the Array
*/
template <typename T>
[[nodiscard]] auto
end() const
{
return ArrayIterator<T>(*this, this->size());
}
/**
* @brief Constructs an ArrayView with the given prefix and config definition.
*
* @param prefix The prefix for the array view.
* @param configDef The ClioConfigDefinition instance.
*/
ArrayView(std::string_view prefix, ClioConfigDefinition const& configDef);
/**
* @brief Returns an ObjectView at the specified index.
*
* @param idx Index of the object to retrieve.
* @return ObjectView at the specified index.
*/
[[nodiscard]] ObjectView
objectAt(std::size_t idx) const;
/**
* @brief Returns a ValueView at the specified index.
*
* @param idx Index of the value to retrieve.
* @return ValueView at the specified index.
*/
[[nodiscard]] ValueView
valueAt(std::size_t idx) const;
/**
* @brief Returns the number of elements in the array.
*
* @return Number of elements in the array.
*/
[[nodiscard]] size_t
size() const;
private:
std::string prefix_;
std::reference_wrapper<ClioConfigDefinition const> clioConfig_;
};
} // namespace util::config

View File

@@ -0,0 +1,209 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/Assert.hpp"
#include "util/newconfig/Array.hpp"
#include "util/newconfig/ArrayView.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <fmt/core.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <variant>
namespace util::config {
/**
* @brief Full Clio Configuration definition.
*
* Specifies which keys are valid in Clio Config and provides default values if user's do not specify one. Those
* without default values must be present in the user's config file.
*/
static ClioConfigDefinition ClioConfig = ClioConfigDefinition{
{{"database.type", ConfigValue{ConfigType::String}.defaultValue("cassandra")},
{"database.cassandra.contact_points", ConfigValue{ConfigType::String}.defaultValue("localhost")},
{"database.cassandra.port", ConfigValue{ConfigType::Integer}},
{"database.cassandra.keyspace", ConfigValue{ConfigType::String}.defaultValue("clio")},
{"database.cassandra.replication_factor", ConfigValue{ConfigType::Integer}.defaultValue(3u)},
{"database.cassandra.table_prefix", ConfigValue{ConfigType::String}.defaultValue("table_prefix")},
{"database.cassandra.max_write_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(10'000)},
{"database.cassandra.max_read_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(100'000)},
{"database.cassandra.threads",
ConfigValue{ConfigType::Integer}.defaultValue(static_cast<uint32_t>(std::thread::hardware_concurrency()))},
{"database.cassandra.core_connections_per_host", ConfigValue{ConfigType::Integer}.defaultValue(1)},
{"database.cassandra.queue_size_io", ConfigValue{ConfigType::Integer}.optional()},
{"database.cassandra.write_batch_size", ConfigValue{ConfigType::Integer}.defaultValue(20)},
{"etl_source.[].ip", Array{ConfigValue{ConfigType::String}.optional()}},
{"etl_source.[].ws_port", Array{ConfigValue{ConfigType::String}.optional().min(1).max(65535)}},
{"etl_source.[].grpc_port", Array{ConfigValue{ConfigType::String}.optional().min(1).max(65535)}},
{"forwarding.cache_timeout", ConfigValue{ConfigType::Double}.defaultValue(0.0)},
{"forwarding.request_timeout", ConfigValue{ConfigType::Double}.defaultValue(10.0)},
{"dos_guard.whitelist.[]", Array{ConfigValue{ConfigType::String}}},
{"dos_guard.max_fetches", ConfigValue{ConfigType::Integer}.defaultValue(1000'000)},
{"dos_guard.max_connections", ConfigValue{ConfigType::Integer}.defaultValue(20)},
{"dos_guard.max_requests", ConfigValue{ConfigType::Integer}.defaultValue(20)},
{"dos_guard.sweep_interval", ConfigValue{ConfigType::Double}.defaultValue(1.0)},
{"cache.peers.[].ip", Array{ConfigValue{ConfigType::String}}},
{"cache.peers.[].port", Array{ConfigValue{ConfigType::String}}},
{"server.ip", ConfigValue{ConfigType::String}},
{"server.port", ConfigValue{ConfigType::Integer}},
{"server.max_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(0)},
{"server.local_admin", ConfigValue{ConfigType::Boolean}.optional()},
{"prometheus.enabled", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
{"prometheus.compress_reply", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
{"io_threads", ConfigValue{ConfigType::Integer}.defaultValue(2)},
{"cache.num_diffs", ConfigValue{ConfigType::Integer}.defaultValue(32)},
{"cache.num_markers", ConfigValue{ConfigType::Integer}.defaultValue(48)},
{"cache.num_cursors_from_diff", ConfigValue{ConfigType::Integer}.defaultValue(0)},
{"cache.num_cursors_from_account", ConfigValue{ConfigType::Integer}.defaultValue(0)},
{"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512)},
{"cache.load", ConfigValue{ConfigType::String}.defaultValue("async")},
{"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.optional()}},
{"log_channels.[].log_level", Array{ConfigValue{ConfigType::String}.optional()}},
{"log_level", ConfigValue{ConfigType::String}.defaultValue("info")},
{"log_format",
ConfigValue{ConfigType::String}.defaultValue(
R"(%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%)"
)},
{"log_to_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
{"log_directory", ConfigValue{ConfigType::String}.optional()},
{"log_rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048)},
{"log_directory_max_size", ConfigValue{ConfigType::Integer}.defaultValue(50 * 1024)},
{"log_rotation_hour_interval", ConfigValue{ConfigType::Integer}.defaultValue(12)},
{"log_tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")},
{"extractor_threads", ConfigValue{ConfigType::Integer}.defaultValue(2u)},
{"read_only", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
{"txn_threshold", ConfigValue{ConfigType::Integer}.defaultValue(0)},
{"start_sequence", ConfigValue{ConfigType::String}.optional()},
{"finish_sequence", ConfigValue{ConfigType::String}.optional()},
{"ssl_cert_file", ConfigValue{ConfigType::String}.optional()},
{"ssl_key_file", ConfigValue{ConfigType::String}.optional()},
{"api_version.min", ConfigValue{ConfigType::Integer}},
{"api_version.max", ConfigValue{ConfigType::Integer}}}
};
ClioConfigDefinition::ClioConfigDefinition(std::initializer_list<KeyValuePair> pair)
{
for (auto const& [key, value] : pair) {
if (key.contains("[]"))
ASSERT(std::holds_alternative<Array>(value), "Value must be array if key has \"[]\"");
map_.insert({key, value});
}
}
ObjectView
ClioConfigDefinition::getObject(std::string_view prefix, std::optional<std::size_t> idx) const
{
auto const prefixWithDot = std::string(prefix) + ".";
for (auto const& [mapKey, mapVal] : map_) {
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};
return ObjectView{prefix, idx.value(), *this};
}
if (hasPrefix && !idx.has_value() && !mapKey.contains(prefixWithDot + "[]"))
return ObjectView{prefix, *this};
}
ASSERT(false, "Key {} is not found in config", prefixWithDot);
std::unreachable();
}
ArrayView
ClioConfigDefinition::getArray(std::string_view prefix) const
{
auto const key = addBracketsForArrayKey(prefix);
for (auto const& [mapKey, mapVal] : map_) {
if (mapKey.starts_with(key)) {
ASSERT(
std::holds_alternative<Array>(mapVal), "Trying to retrieve Object or ConfigValue, instead of an Array "
);
return ArrayView{key, *this};
}
}
ASSERT(false, "Key {} is not found in config", key);
std::unreachable();
}
bool
ClioConfigDefinition::contains(std::string_view key) const
{
return map_.contains(key);
}
bool
ClioConfigDefinition::hasItemsWithPrefix(std::string_view key) const
{
return std::ranges::any_of(map_, [&key](auto const& pair) { return pair.first.starts_with(key); });
}
ValueView
ClioConfigDefinition::getValue(std::string_view fullKey) const
{
ASSERT(map_.contains(fullKey), "key {} does not exist in config", fullKey);
if (std::holds_alternative<ConfigValue>(map_.at(fullKey))) {
return ValueView{std::get<ConfigValue>(map_.at(fullKey))};
}
ASSERT(false, "Value of key {} is an Array, not an object", fullKey);
std::unreachable();
}
ValueView
ClioConfigDefinition::getValueInArray(std::string_view fullKey, std::size_t index) const
{
auto const it = getArrayIterator(fullKey);
return ValueView{std::get<Array>(it->second).at(index)};
}
Array const&
ClioConfigDefinition::asArray(std::string_view fullKey) const
{
auto const it = getArrayIterator(fullKey);
return std::get<Array>(it->second);
}
std::size_t
ClioConfigDefinition::arraySize(std::string_view prefix) const
{
auto const key = addBracketsForArrayKey(prefix);
for (auto const& pair : map_) {
if (pair.first.starts_with(key)) {
return std::get<Array>(pair.second).size();
}
}
ASSERT(false, "Prefix {} not found in any of the config keys", key);
std::unreachable();
}
} // namespace util::config

View File

@@ -0,0 +1,230 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include "util/newconfig/Array.hpp"
#include "util/newconfig/ConfigDescription.hpp"
#include "util/newconfig/ConfigFileInterface.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/Errors.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <fmt/core.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <expected>
#include <initializer_list>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
namespace util::config {
/**
* @brief All the config data will be stored and extracted from this class
*
* Represents all the possible config data
*/
class ClioConfigDefinition {
public:
using KeyValuePair = std::pair<std::string_view, std::variant<ConfigValue, Array>>;
/**
* @brief Constructs a new ClioConfigDefinition
*
* Initializes the configuration with a predefined set of key-value pairs
* If a key contains "[]", the corresponding value must be an Array
*
* @param pair A list of key-value pairs for the predefined set of clio configurations
*/
ClioConfigDefinition(std::initializer_list<KeyValuePair> pair);
/**
* @brief Parses the configuration file
*
* Should also check that no extra configuration key/value pairs are present
*
* @param config The configuration file interface
* @return An optional Error object if parsing fails
*/
[[nodiscard]] std::optional<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 Error object if validation fails
*/
[[nodiscard]] std::optional<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
*
* @param prefix The key prefix for the ObjectView
* @param idx Used if getting Object in an Array
* @return ObjectView with the given prefix
*/
[[nodiscard]] ObjectView
getObject(std::string_view prefix, std::optional<std::size_t> idx = std::nullopt) const;
/**
* @brief Returns the specified ValueView object associated with the key
*
* @param fullKey The config key to search for
* @return ValueView associated with the given key
*/
[[nodiscard]] ValueView
getValue(std::string_view fullKey) const;
/**
* @brief Returns the specified ValueView object in an array with a given index
*
* @param fullKey The config key to search for
* @param index The index of the config value inside the Array to get
* @return ValueView associated with the given key
*/
[[nodiscard]] ValueView
getValueInArray(std::string_view fullKey, std::size_t index) const;
/**
* @brief Returns the specified Array object from ClioConfigDefinition
*
* @param prefix The prefix to search config keys from
* @return ArrayView with all key-value pairs where key starts with "prefix"
*/
[[nodiscard]] ArrayView
getArray(std::string_view prefix) const;
/**
* @brief Checks if a key is present in the configuration map.
*
* @param key The key to search for in the configuration map.
* @return True if the key is present, false otherwise.
*/
[[nodiscard]] bool
contains(std::string_view key) const;
/**
* @brief Checks if any key in config starts with "key"
*
* @param key The key to search for in the configuration map.
* @return True if the any key in config starts with "key", false otherwise.
*/
[[nodiscard]] bool
hasItemsWithPrefix(std::string_view key) const;
/**
* @brief Returns the Array object associated with the specified key.
*
* @param key The key whose associated Array object is to be returned.
* @return The Array object associated with the specified key.
*/
[[nodiscard]] Array const&
asArray(std::string_view key) const;
/**
* @brief Returns the size of an Array
*
* @param prefix The prefix whose associated Array object is to be returned.
* @return The size of the array associated with the specified prefix.
*/
[[nodiscard]] std::size_t
arraySize(std::string_view prefix) const;
/**
* @brief Returns an iterator to the beginning of the configuration map.
*
* @return A constant iterator to the beginning of the map.
*/
[[nodiscard]] auto
begin() const
{
return map_.begin();
}
/**
* @brief Returns an iterator to the end of the configuration map.
*
* @return A constant iterator to the end of the map.
*/
[[nodiscard]] auto
end() const
{
return map_.end();
}
private:
/**
* @brief returns the iterator of key-value pair with key fullKey
*
* @param fullKey Key to search for
* @return iterator of map
*/
[[nodiscard]] auto
getArrayIterator(std::string_view key) const
{
auto const fullKey = addBracketsForArrayKey(key);
auto const it = std::ranges::find_if(map_, [&fullKey](auto pair) { return pair.first == fullKey; });
ASSERT(it != map_.end(), "key {} does not exist in config", fullKey);
ASSERT(std::holds_alternative<Array>(it->second), "Value of {} is not an array", fullKey);
return it;
}
/**
* @brief Used for all Array API's; check to make sure "[]" exists, if not, append to end
*
* @param key key to check for
* @return the key with "[]" appended to the end
*/
[[nodiscard]] static std::string
addBracketsForArrayKey(std::string_view key)
{
std::string fullKey = std::string(key);
if (!key.contains(".[]"))
fullKey += ".[]";
return fullKey;
}
std::unordered_map<std::string_view, std::variant<ConfigValue, Array>> map_;
};
} // namespace util::config

View File

@@ -0,0 +1,125 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include <algorithm>
#include <array>
#include <string_view>
namespace util::config {
/**
* @brief All the config description are stored and extracted from this class
*
* Represents all the possible config description
*/
struct ClioConfigDescription {
public:
/** @brief Struct to represent a key-value pair*/
struct KV {
std::string_view key;
std::string_view value;
};
/**
* @brief Constructs a new Clio Config Description based on pre-existing descriptions
*
* Config Keys and it's corresponding descriptions are all predefined. Used to generate markdown file
*/
constexpr ClioConfigDescription() = default;
/**
* @brief Retrieves the description for a given key
*
* @param key The key to look up the description for
* @return The description associated with the key, or "Not Found" if the key does not exist
*/
[[nodiscard]] static constexpr std::string_view
get(std::string_view key)
{
auto const itr = std::ranges::find_if(configDescription, [&](auto const& v) { return v.key == key; });
ASSERT(itr != configDescription.end(), "Key {} doesn't exist in config", key);
return itr->value;
}
private:
static constexpr auto configDescription = std::array{
KV{"database.type", "Type of database to use."},
KV{"database.cassandra.contact_points", "Comma-separated list of contact points for Cassandra nodes."},
KV{"database.cassandra.port", "Port number to connect to Cassandra."},
KV{"database.cassandra.keyspace", "Keyspace to use in Cassandra."},
KV{"database.cassandra.replication_factor", "Number of replicated nodes for Scylladb."},
KV{"database.cassandra.table_prefix", "Prefix for Cassandra table names."},
KV{"database.cassandra.max_write_requests_outstanding", "Maximum number of outstanding write requests."},
KV{"database.cassandra.max_read_requests_outstanding", "Maximum number of outstanding read requests."},
KV{"database.cassandra.threads", "Number of threads for Cassandra operations."},
KV{"database.cassandra.core_connections_per_host", "Number of core connections per host for Cassandra."},
KV{"database.cassandra.queue_size_io", "Queue size for I/O operations in Cassandra."},
KV{"database.cassandra.write_batch_size", "Batch size for write operations in Cassandra."},
KV{"etl_source.[].ip", "IP address of the ETL source."},
KV{"etl_source.[].ws_port", "WebSocket port of the ETL source."},
KV{"etl_source.[].grpc_port", "gRPC port of the ETL source."},
KV{"forwarding.cache_timeout", "Timeout duration for the forwarding cache used in Rippled communication."},
KV{"forwarding.request_timeout", "Timeout duration for the forwarding request used in Rippled communication."},
KV{"dos_guard.[].whitelist", "List of IP addresses to whitelist for DOS protection."},
KV{"dos_guard.max_fetches", "Maximum number of fetch operations allowed by DOS guard."},
KV{"dos_guard.max_connections", "Maximum number of concurrent connections allowed by DOS guard."},
KV{"dos_guard.max_requests", "Maximum number of requests allowed by DOS guard."},
KV{"dos_guard.sweep_interval", "Interval in seconds for DOS guard to sweep/clear its state."},
KV{"cache.peers.[].ip", "IP address of peer nodes to cache."},
KV{"cache.peers.[].port", "Port number of peer nodes to cache."},
KV{"server.ip", "IP address of the Clio HTTP server."},
KV{"server.port", "Port number of the Clio HTTP server."},
KV{"server.max_queue_size", "Maximum size of the server's request queue."},
KV{"server.local_admin", "Indicates if the server should run with admin privileges."},
KV{"prometheus.enabled", "Enable or disable Prometheus metrics."},
KV{"prometheus.compress_reply", "Enable or disable compression of Prometheus responses."},
KV{"io_threads", "Number of I/O threads."},
KV{"cache.num_diffs", "Number of diffs to cache."},
KV{"cache.num_markers", "Number of markers to cache."},
KV{"cache.num_cursors_from_diff", "Num of cursors that are different."},
KV{"cache.num_cursors_from_account", "Number of cursors from an account."},
KV{"cache.page_fetch_size", "Page fetch size for cache operations."},
KV{"cache.load", "Cache loading strategy ('sync' or 'async')."},
KV{"log_channels.[].channel", "Name of the log channel."},
KV{"log_channels.[].log_level", "Log level for the log channel."},
KV{"log_level", "General logging level of Clio."},
KV{"log_format", "Format string for log messages."},
KV{"log_to_console", "Enable or disable logging to console."},
KV{"log_directory", "Directory path for log files."},
KV{"log_rotation_size", "Log rotation size in megabytes."},
KV{"log_directory_max_size", "Maximum size of the log directory in megabytes."},
KV{"log_rotation_hour_interval", "Interval in hours for log rotation."},
KV{"log_tag_style", "Style for log tags."},
KV{"extractor_threads", "Number of extractor threads."},
KV{"read_only", "Indicates if the server should have read-only privileges."},
KV{"txn_threshold", "Transaction threshold value."},
KV{"start_sequence", "Starting ledger index."},
KV{"finish_sequence", "Ending ledger index."},
KV{"ssl_cert_file", "Path to the SSL certificate file."},
KV{"ssl_key_file", "Path to the SSL key file."},
KV{"api_version.min", "Minimum API version."},
KV{"api_version.max", "Maximum API version."}
};
};
} // namespace util::config

View File

@@ -0,0 +1,66 @@
//------------------------------------------------------------------------------
/*
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 "util/newconfig/ConfigValue.hpp"
#include <optional>
#include <string_view>
#include <vector>
namespace util::config {
/**
* @brief The interface for configuration files.
*
* This class defines the interface for handling configuration files,
* which can be implemented for different formats such as JSON or YAML.
*/
class ConfigFileInterface {
public:
virtual ~ConfigFileInterface() = default;
/**
* @brief Parses the provided path of user clio configuration data
*
* @param filePath The path to the Clio Config data
*/
virtual void
parse(std::string_view filePath) = 0;
/**
* @brief Retrieves a configuration value.
*
* @param key The key of the configuration value.
* @return An optional containing the configuration value if found, otherwise std::nullopt.
*/
virtual std::optional<ConfigValue>
getValue(std::string_view key) const = 0;
/**
* @brief Retrieves an array of configuration values.
*
* @param key The key of the configuration array.
* @return An optional containing a vector of configuration values if found, otherwise std::nullopt.
*/
virtual std::optional<std::vector<ConfigValue>>
getArray(std::string_view key) const = 0;
};
} // namespace util::config

View File

@@ -0,0 +1,216 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include "util/UnsupportedType.hpp"
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <type_traits>
#include <variant>
namespace util::config {
/** @brief Custom clio config types */
enum class ConfigType { Integer, String, Double, Boolean };
/**
* @brief Get the corresponding clio config type
*
* @tparam Type The type to get the corresponding ConfigType for
* @return The corresponding ConfigType
*/
template <typename Type>
constexpr ConfigType
getType()
{
if constexpr (std::is_same_v<Type, int64_t>) {
return ConfigType::Integer;
} else if constexpr (std::is_same_v<Type, std::string>) {
return ConfigType::String;
} else if constexpr (std::is_same_v<Type, double>) {
return ConfigType::Double;
} else if constexpr (std::is_same_v<Type, bool>) {
return ConfigType::Boolean;
} else {
static_assert(util::Unsupported<Type>, "Wrong config type");
}
}
/**
* @brief Represents the config values for Json/Yaml config
*
* Used in ClioConfigDefinition to indicate the required type of value and
* whether it is mandatory to specify in the configuration
*/
class ConfigValue {
public:
using Type = std::variant<int64_t, std::string, bool, double>;
/**
* @brief Constructor initializing with the config type
*
* @param type The type of the config value
*/
constexpr ConfigValue(ConfigType type) : type_(type)
{
}
/**
* @brief Sets the default value for the config
*
* @param value The default value
* @return Reference to this ConfigValue
*/
[[nodiscard]] ConfigValue&
defaultValue(Type value)
{
setValue(value);
return *this;
}
/**
* @brief Gets the config type
*
* @return The config type
*/
[[nodiscard]] constexpr ConfigType
type() const
{
return type_;
}
/**
* @brief Sets the minimum value for the config
*
* @param min The minimum value
* @return Reference to this ConfigValue
*/
[[nodiscard]] constexpr ConfigValue&
min(std::uint32_t min)
{
min_ = min;
return *this;
}
/**
* @brief Sets the maximum value for the config
*
* @param max The maximum value
* @return Reference to this ConfigValue
*/
[[nodiscard]] constexpr ConfigValue&
max(std::uint32_t max)
{
max_ = max;
return *this;
}
/**
* @brief Sets the config value as optional, meaning the user doesn't have to provide the value in their config
*
* @return Reference to this ConfigValue
*/
[[nodiscard]] constexpr ConfigValue&
optional()
{
optional_ = true;
return *this;
}
/**
* @brief Checks if configValue is optional
*
* @return true if optional, false otherwise
*/
[[nodiscard]] constexpr bool
isOptional() const
{
return optional_;
}
/**
* @brief Check if value is optional
*
* @return if value is optiona, false otherwise
*/
[[nodiscard]] constexpr bool
hasValue() const
{
return value_.has_value();
}
/**
* @brief Get the value of config
*
* @return Config Value
*/
[[nodiscard]] Type const&
getValue() const
{
return value_.value();
}
private:
/**
* @brief Checks if the value type is consistent with the specified ConfigType
*
* @param type The config type
* @param value The config value
*/
static void
checkTypeConsistency(ConfigType type, Type value)
{
if (std::holds_alternative<std::string>(value)) {
ASSERT(type == ConfigType::String, "Value does not match type string");
} else if (std::holds_alternative<bool>(value)) {
ASSERT(type == ConfigType::Boolean, "Value does not match type boolean");
} else if (std::holds_alternative<double>(value)) {
ASSERT(type == ConfigType::Double, "Value does not match type double");
} else if (std::holds_alternative<int64_t>(value)) {
ASSERT(type == ConfigType::Integer, "Value does not match type integer");
}
}
/**
* @brief Sets the value for the config
*
* @param value The value to set
* @return The value that was set
*/
Type
setValue(Type value)
{
checkTypeConsistency(type_, value);
value_ = value;
return value;
}
ConfigType type_{};
bool optional_{false};
std::optional<Type> value_;
std::optional<std::uint32_t> min_;
std::optional<std::uint32_t> max_;
};
} // namespace util::config

View File

@@ -0,0 +1,33 @@
//------------------------------------------------------------------------------
/*
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 <string>
#include <string_view>
namespace util::config {
/** @brief todo: Will display the different errors when parsing config */
struct Error {
std::string_view key;
std::string_view error;
};
} // namespace util::config

View File

@@ -0,0 +1,101 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ObjectView.hpp"
#include "util/Assert.hpp"
#include "util/newconfig/ArrayView.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ValueView.hpp"
#include <fmt/core.h>
#include <unistd.h>
#include <algorithm>
#include <cstddef>
#include <string>
#include <string_view>
#include <utility>
namespace util::config {
ObjectView::ObjectView(std::string_view prefix, ClioConfigDefinition const& clioConfig)
: prefix_{prefix}, clioConfig_{clioConfig}
{
}
ObjectView::ObjectView(std::string_view prefix, std::size_t arrayIndex, ClioConfigDefinition const& clioConfig)
: prefix_{prefix}, arrayIndex_{arrayIndex}, clioConfig_{clioConfig}
{
}
bool
ObjectView::containsKey(std::string_view key) const
{
return clioConfig_.get().contains(getFullKey(key));
}
ValueView
ObjectView::getValue(std::string_view key) const
{
auto const fullKey = getFullKey(key);
if (arrayIndex_.has_value()) {
return clioConfig_.get().getArray(fullKey).valueAt(arrayIndex_.value());
}
return clioConfig_.get().getValue(fullKey);
}
ObjectView
ObjectView::getObject(std::string_view key) const
{
auto const fullKey = getFullKey(key);
if (startsWithKey(fullKey) && !arrayIndex_.has_value()) {
return clioConfig_.get().getObject(fullKey);
}
if (startsWithKey(fullKey) && arrayIndex_.has_value()) {
return ObjectView(fullKey, arrayIndex_.value(), clioConfig_);
}
ASSERT(false, "Key {} does not exist in object", fullKey);
std::unreachable();
}
ArrayView
ObjectView::getArray(std::string_view key) const
{
auto fullKey = getFullKey(key);
if (!fullKey.contains(".[]"))
fullKey += ".[]";
ASSERT(clioConfig_.get().hasItemsWithPrefix(fullKey), "Key {} does not exist in object", fullKey);
return clioConfig_.get().getArray(fullKey);
}
std::string
ObjectView::getFullKey(std::string_view key) const
{
return fmt::format("{}.{}", prefix_, key);
}
bool
ObjectView::startsWithKey(std::string_view key) const
{
return std::ranges::any_of(clioConfig_.get(), [&key](auto const& pair) { return pair.first.starts_with(key); });
}
} // namespace util::config

View File

@@ -0,0 +1,122 @@
//------------------------------------------------------------------------------
/*
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 "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/ValueView.hpp"
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
namespace util::config {
class ClioConfigDefinition;
class ArrayView;
/**
* @brief Provides a view into a subset of configuration data defined by a prefix
*
* Allows querying and accessing configuration values based on the provided prefix
*/
class ObjectView {
public:
/**
* @brief Constructs an ObjectView for the specified prefix. The view must be of type object
*
* @param prefix The prefix indicating the subset of configuration data to view
* @param clioConfig Reference to the ClioConfigDefinition containing all the configuration data
*/
ObjectView(std::string_view prefix, ClioConfigDefinition const& clioConfig);
/**
* @brief Constructs an ObjectView for an indexed array within the specified prefix
*
* @param prefix The prefix indicating the subset of configuration data to view
* @param arrayIndex The index of the array object element to view
* @param clioConfig Reference to the ClioConfigDefinition containing all the configuration data
*/
ObjectView(std::string_view prefix, std::size_t arrayIndex, ClioConfigDefinition const& clioConfig);
/**
* @brief Checks if prefix_.key (fullkey) exists in ClioConfigDefinition
*
* @param key The suffix of the key
* @return true if the full key exists, otherwise false
*/
[[nodiscard]] bool
containsKey(std::string_view key) const;
/**
* @brief Retrieves the value associated with the specified prefix._key in ClioConfigDefinition
*
* @param key The suffix of the key
* @return A ValueView object representing the value associated with the key
*/
[[nodiscard]] ValueView
getValue(std::string_view key) const;
/**
* @brief Retrieves an ObjectView in ClioConfigDefinition with key that starts with prefix_.key. The view must be of
* type object
*
* @param key The suffix of the key
* @return An ObjectView representing the subset of configuration data
*/
[[nodiscard]] ObjectView
getObject(std::string_view key) const;
/**
* @brief Retrieves an ArrayView in ClioConfigDefinition with key that starts with prefix_.key. The view must be of
* type object
*
* @param key The suffix of the key
* @return An ObjectView representing the subset of configuration data
*/
[[nodiscard]] ArrayView
getArray(std::string_view key) const;
private:
/**
* @brief returns the full key (prefix.key)
*
* @param key The suffix of the key
* @return the full string of the key
*/
[[nodiscard]] std::string
getFullKey(std::string_view key) const;
/**
* @brief Checks if any key in ClioConfigDefinition starts with prefix_.key
*
* @param key The suffix of the key
* @return true if at least one key starts with the specified prefix_.key, otherwise false
*/
[[nodiscard]] bool
startsWithKey(std::string_view key) const;
std::string prefix_;
std::optional<size_t> arrayIndex_;
std::reference_wrapper<ClioConfigDefinition const> clioConfig_;
};
} // namespace util::config

View File

@@ -0,0 +1,82 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ValueView.hpp"
#include "util/Assert.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
namespace util::config {
ValueView::ValueView(ConfigValue const& configVal) : configVal_{configVal}
{
}
std::string_view
ValueView::asString() const
{
if (this->type() == ConfigType::String && configVal_.get().hasValue())
return std::get<std::string>(configVal_.get().getValue());
ASSERT(false, "Value view is not of String type");
std::unreachable();
}
bool
ValueView::asBool() const
{
if (type() == ConfigType::Boolean && configVal_.get().hasValue())
return std::get<bool>(configVal_.get().getValue());
ASSERT(false, "Value view is not of Bool type");
std::unreachable();
}
double
ValueView::asDouble() const
{
if (configVal_.get().hasValue()) {
if (type() == ConfigType::Double) {
return std::get<double>(configVal_.get().getValue());
}
if (type() == ConfigType::Integer)
return static_cast<double>(std::get<int64_t>(configVal_.get().getValue()));
}
ASSERT(false, "Value view is not of Double type");
std::unreachable();
}
float
ValueView::asFloat() const
{
if (configVal_.get().hasValue()) {
if (type() == ConfigType::Double) {
return static_cast<float>(std::get<double>(configVal_.get().getValue()));
}
if (type() == ConfigType::Integer)
return static_cast<float>(std::get<int64_t>(configVal_.get().getValue()));
}
ASSERT(false, "Value view is not of Float type");
std::unreachable();
}
} // namespace util::config

View File

@@ -0,0 +1,146 @@
//------------------------------------------------------------------------------
/*
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 "util/Assert.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include <fmt/core.h>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string_view>
namespace util::config {
class ClioConfigDefinition;
/**
* @brief Provides view into ConfigValues that represents values in Clio Config
*/
class ValueView {
public:
/**
* @brief Constructs a ValueView object
*
* @param configVal the config Value to view
*/
ValueView(ConfigValue const& configVal);
/**
* @brief Retrieves the value as a string
*
* @return The value as a string
* @throws std::bad_variant_access if the value is not a string
*/
[[nodiscard]] std::string_view
asString() const;
/**
* @brief Retrieves the value as a boolean
*
* @return The value as a boolean
* @throws std::bad_variant_access if the value is not a boolean
*/
[[nodiscard]] bool
asBool() const;
/**
* @brief Retrieves any type of "int" value (uint_32, int64_t etc)
*
* @return The value with user requested type (if convertible)
* @throws std::logic_error if the int < 0 and user requested unsigned int
* @throws std::bad_variant_access if the value is not of type int
*/
template <typename T>
[[nodiscard]] T
asIntType() const
{
if ((type() == ConfigType::Integer) && configVal_.get().hasValue()) {
auto const val = std::get<int64_t>(configVal_.get().getValue());
if (std::is_unsigned_v<T> && val < 0)
ASSERT(false, "Int {} cannot be converted to the specified unsigned type", val);
if (std::is_convertible_v<decltype(val), T>) {
return static_cast<T>(val);
}
}
ASSERT(false, "Value view is not of any Int type");
return 0;
}
/**
* @brief Retrieves the value as a double
*
* @return The value as a double
* @throws std::bad_variant_access if the value cannot be retrieved as a Double
*/
[[nodiscard]] double
asDouble() const;
/**
* @brief Retrieves the value as a float
*
* @return The value as a float
* @throws std::bad_variant_access if the value cannot be retrieved as a float
*/
[[nodiscard]] float
asFloat() const;
/**
* @brief Gets the config type
*
* @return The config type
*/
[[nodiscard]] constexpr ConfigType
type() const
{
return configVal_.get().type();
}
/**
* @brief Check if Config Value exists
*
* @return true if exists, false otherwise
*/
[[nodiscard]] constexpr bool
hasValue() const
{
return configVal_.get().hasValue();
}
/**
* @brief Check if config value is optional
*
* @return true if optional, false otherwise
*/
[[nodiscard]] constexpr bool
isOptional() const
{
return configVal_.get().isOptional();
}
private:
std::reference_wrapper<ConfigValue const> configVal_;
};
} // namespace util::config

View File

@@ -0,0 +1,95 @@
//------------------------------------------------------------------------------
/*
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 "util/newconfig/Array.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include <gtest/gtest.h>
using namespace util::config;
inline ClioConfigDefinition
generateConfig()
{
return ClioConfigDefinition{
{"header.text1", ConfigValue{ConfigType::String}.defaultValue("value")},
{"header.port", ConfigValue{ConfigType::Integer}.defaultValue(123)},
{"header.admin", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
{"header.sub.sub2Value", ConfigValue{ConfigType::String}.defaultValue("TSM")},
{"ip", ConfigValue{ConfigType::Double}.defaultValue(444.22)},
{"array.[].sub",
Array{
ConfigValue{ConfigType::Double}.defaultValue(111.11), ConfigValue{ConfigType::Double}.defaultValue(4321.55)
}},
{"array.[].sub2",
Array{
ConfigValue{ConfigType::String}.defaultValue("subCategory"),
ConfigValue{ConfigType::String}.defaultValue("temporary")
}},
{"higher.[].low.section", Array{ConfigValue{ConfigType::String}.defaultValue("true")}},
{"higher.[].low.admin", Array{ConfigValue{ConfigType::Boolean}.defaultValue(false)}},
{"dosguard.whitelist.[]",
Array{
ConfigValue{ConfigType::String}.defaultValue("125.5.5.2"),
ConfigValue{ConfigType::String}.defaultValue("204.2.2.2")
}},
{"dosguard.port", ConfigValue{ConfigType::Integer}.defaultValue(55555)}
};
}
/* The config definition above would look like this structure in config.json:
"header": {
"text1": "value",
"port": 123,
"admin": true,
"sub": {
"sub2Value": "TSM"
}
},
"ip": 444.22,
"array": [
{
"sub": 111.11,
"sub2": "subCategory"
},
{
"sub": 4321.55,
"sub2": "temporary"
}
],
"higher": [
{
"low": {
"section": "true",
"admin": false
}
}
],
"dosguard": {
"whitelist": [
"125.5.5.2", "204.2.2.2"
],
"port" : 55555
},
*/

View File

@@ -131,6 +131,13 @@ target_sources(
web/ServerTests.cpp
web/SweepHandlerTests.cpp
web/WhitelistHandlerTests.cpp
# New Config
util/newconfig/ArrayViewTests.cpp
util/newconfig/ObjectViewTests.cpp
util/newconfig/ValueViewTests.cpp
util/newconfig/ArrayTests.cpp
util/newconfig/ConfigValueTests.cpp
util/newconfig/ClioConfigDefinitionTests.cpp
)
configure_file(test_data/cert.pem ${CMAKE_BINARY_DIR}/tests/unit/test_data/cert.pem COPYONLY)

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/Array.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/ValueView.hpp"
#include <gtest/gtest.h>
using namespace util::config;
TEST(ArrayTest, testConfigArray)
{
auto arr = Array{
ConfigValue{ConfigType::Boolean}.defaultValue(false),
ConfigValue{ConfigType::Integer}.defaultValue(1234),
ConfigValue{ConfigType::Double}.defaultValue(22.22),
};
auto cv = arr.at(0);
ValueView vv{cv};
EXPECT_EQ(vv.asBool(), false);
auto cv2 = arr.at(1);
ValueView vv2{cv2};
EXPECT_EQ(vv2.asIntType<int>(), 1234);
EXPECT_EQ(arr.size(), 3);
arr.emplaceBack(ConfigValue{ConfigType::String}.defaultValue("false"));
EXPECT_EQ(arr.size(), 4);
auto cv4 = arr.at(3);
ValueView vv4{cv4};
EXPECT_EQ(vv4.asString(), "false");
}

View File

@@ -0,0 +1,139 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ArrayView.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/FakeConfigData.hpp"
#include "util/newconfig/ObjectView.hpp"
#include "util/newconfig/ValueView.hpp"
#include <gtest/gtest.h>
#include <cstddef>
using namespace util::config;
struct ArrayViewTest : testing::Test {
ClioConfigDefinition const configData = generateConfig();
};
TEST_F(ArrayViewTest, ArrayValueTest)
{
ArrayView const arrVals = configData.getArray("array.[].sub");
auto valIt = arrVals.begin<ValueView>();
auto const precision = 1e-9;
EXPECT_NEAR((*valIt++).asDouble(), 111.11, precision);
EXPECT_NEAR((*valIt++).asDouble(), 4321.55, precision);
EXPECT_EQ(valIt, arrVals.end<ValueView>());
EXPECT_NEAR(111.11, arrVals.valueAt(0).asDouble(), precision);
EXPECT_NEAR(4321.55, arrVals.valueAt(1).asDouble(), precision);
ArrayView const arrVals2 = configData.getArray("array.[].sub2");
auto val2It = arrVals2.begin<ValueView>();
EXPECT_EQ((*val2It++).asString(), "subCategory");
EXPECT_EQ((*val2It++).asString(), "temporary");
EXPECT_EQ(val2It, arrVals2.end<ValueView>());
ValueView const tempVal = arrVals2.valueAt(0);
EXPECT_EQ(tempVal.type(), ConfigType::String);
EXPECT_EQ("subCategory", tempVal.asString());
}
TEST_F(ArrayViewTest, ArrayWithObjTest)
{
ArrayView const arrVals = configData.getArray("array.[]");
ArrayView const arrValAlt = configData.getArray("array");
auto const precision = 1e-9;
auto const obj1 = arrVals.objectAt(0);
auto const obj2 = arrValAlt.objectAt(0);
EXPECT_NEAR(obj1.getValue("sub").asDouble(), obj2.getValue("sub").asDouble(), precision);
EXPECT_NEAR(obj1.getValue("sub").asDouble(), 111.11, precision);
}
TEST_F(ArrayViewTest, IterateArray)
{
auto arr = configData.getArray("dosguard.whitelist");
EXPECT_EQ(2, arr.size());
EXPECT_EQ(arr.valueAt(0).asString(), "125.5.5.2");
EXPECT_EQ(arr.valueAt(1).asString(), "204.2.2.2");
auto it = arr.begin<ValueView>();
EXPECT_EQ((*it++).asString(), "125.5.5.2");
EXPECT_EQ((*it++).asString(), "204.2.2.2");
EXPECT_EQ((it), arr.end<ValueView>());
}
TEST_F(ArrayViewTest, DifferentArrayIterators)
{
auto const subArray = configData.getArray("array.[].sub");
auto const dosguardArray = configData.getArray("dosguard.whitelist.[]");
ASSERT_EQ(subArray.size(), dosguardArray.size());
auto itArray = subArray.begin<ValueView>();
auto itDosguard = dosguardArray.begin<ValueView>();
for (std::size_t i = 0; i < subArray.size(); i++)
EXPECT_NE(itArray++, itDosguard++);
}
TEST_F(ArrayViewTest, IterateObject)
{
auto arr = configData.getArray("array");
EXPECT_EQ(2, arr.size());
auto it = arr.begin<ObjectView>();
EXPECT_EQ(111.11, (*it).getValue("sub").asDouble());
EXPECT_EQ("subCategory", (*it++).getValue("sub2").asString());
EXPECT_EQ(4321.55, (*it).getValue("sub").asDouble());
EXPECT_EQ("temporary", (*it++).getValue("sub2").asString());
EXPECT_EQ(it, arr.end<ObjectView>());
}
struct ArrayViewDeathTest : ArrayViewTest {};
TEST_F(ArrayViewDeathTest, IncorrectAccess)
{
ArrayView const arr = configData.getArray("higher");
// dies because higher only has 1 object
EXPECT_DEATH({ [[maybe_unused]] auto _ = arr.objectAt(1); }, ".*");
ArrayView const arrVals2 = configData.getArray("array.[].sub2");
ValueView const tempVal = arrVals2.valueAt(0);
// dies because array.[].sub2 only has 2 config values
EXPECT_DEATH([[maybe_unused]] auto _ = arrVals2.valueAt(2), ".*");
// dies as value is not of type int
EXPECT_DEATH({ [[maybe_unused]] auto _ = tempVal.asIntType<int>(); }, ".*");
}
TEST_F(ArrayViewDeathTest, IncorrectIterateAccess)
{
ArrayView const arr = configData.getArray("higher");
EXPECT_DEATH({ [[maybe_unused]] auto _ = arr.begin<ValueView>(); }, ".*");
ArrayView const dosguardWhitelist = configData.getArray("dosguard.whitelist");
EXPECT_DEATH({ [[maybe_unused]] auto _ = dosguardWhitelist.begin<ObjectView>(); }, ".*");
}

View File

@@ -0,0 +1,167 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ArrayView.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ConfigDescription.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/FakeConfigData.hpp"
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <gtest/gtest.h>
#include <string_view>
#include <unordered_set>
using namespace util::config;
// TODO: parsing config file and populating into config will be here once implemented
struct NewConfigTest : testing::Test {
ClioConfigDefinition const configData = generateConfig();
};
TEST_F(NewConfigTest, fetchValues)
{
auto const v = configData.getValue("header.port");
EXPECT_EQ(v.type(), ConfigType::Integer);
EXPECT_EQ("value", configData.getValue("header.text1").asString());
EXPECT_EQ(123, configData.getValue("header.port").asIntType<int>());
EXPECT_EQ(true, configData.getValue("header.admin").asBool());
EXPECT_EQ("TSM", configData.getValue("header.sub.sub2Value").asString());
EXPECT_EQ(444.22, configData.getValue("ip").asDouble());
auto const v2 = configData.getValueInArray("dosguard.whitelist", 0);
EXPECT_EQ(v2.asString(), "125.5.5.2");
}
TEST_F(NewConfigTest, fetchObject)
{
auto const obj = configData.getObject("header");
EXPECT_TRUE(obj.containsKey("sub.sub2Value"));
auto const obj2 = obj.getObject("sub");
EXPECT_TRUE(obj2.containsKey("sub2Value"));
EXPECT_EQ(obj2.getValue("sub2Value").asString(), "TSM");
auto const objInArr = configData.getObject("array", 0);
auto const obj2InArr = configData.getObject("array", 1);
EXPECT_EQ(objInArr.getValue("sub").asDouble(), 111.11);
EXPECT_EQ(objInArr.getValue("sub2").asString(), "subCategory");
EXPECT_EQ(obj2InArr.getValue("sub").asDouble(), 4321.55);
EXPECT_EQ(obj2InArr.getValue("sub2").asString(), "temporary");
}
TEST_F(NewConfigTest, fetchArray)
{
auto const obj = configData.getObject("dosguard");
EXPECT_TRUE(obj.containsKey("whitelist.[]"));
auto const arr = obj.getArray("whitelist");
EXPECT_EQ(2, arr.size());
auto const sameArr = configData.getArray("dosguard.whitelist");
EXPECT_EQ(2, sameArr.size());
EXPECT_EQ(sameArr.valueAt(0).asString(), arr.valueAt(0).asString());
EXPECT_EQ(sameArr.valueAt(1).asString(), arr.valueAt(1).asString());
}
TEST_F(NewConfigTest, CheckKeys)
{
EXPECT_TRUE(configData.contains("header.port"));
EXPECT_TRUE(configData.contains("array.[].sub"));
EXPECT_TRUE(configData.contains("dosguard.whitelist.[]"));
EXPECT_FALSE(configData.contains("dosguard.whitelist"));
EXPECT_TRUE(configData.hasItemsWithPrefix("dosguard"));
EXPECT_TRUE(configData.hasItemsWithPrefix("ip"));
EXPECT_EQ(configData.arraySize("array"), 2);
EXPECT_EQ(configData.arraySize("higher"), 1);
EXPECT_EQ(configData.arraySize("dosguard.whitelist"), 2);
}
TEST_F(NewConfigTest, CheckAllKeys)
{
auto expected = std::unordered_set<std::string_view>{};
auto const actual = std::unordered_set<std::string_view>{
"header.text1",
"header.port",
"header.admin",
"header.sub.sub2Value",
"ip",
"array.[].sub",
"array.[].sub2",
"higher.[].low.section",
"higher.[].low.admin",
"dosguard.whitelist.[]",
"dosguard.port"
};
for (auto i = configData.begin(); i != configData.end(); ++i) {
expected.emplace((i->first));
}
EXPECT_EQ(expected, actual);
}
struct NewConfigDeathTest : NewConfigTest {};
TEST_F(NewConfigDeathTest, IncorrectGetValues)
{
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("head"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("head."); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("asdf"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("dosguard.whitelist"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getValue("dosguard.whitelist.[]"); }, ".*");
}
TEST_F(NewConfigDeathTest, IncorrectGetObject)
{
ASSERT_FALSE(configData.contains("head"));
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("head"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("array", 2); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getObject("doesNotExist"); }, ".*");
}
TEST_F(NewConfigDeathTest, IncorrectGetArray)
{
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getArray("header.text1"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = configData.getArray("asdf"); }, ".*");
}
TEST(ConfigDescription, getValues)
{
ClioConfigDescription definition{};
EXPECT_EQ(definition.get("database.type"), "Type of database to use.");
EXPECT_EQ(definition.get("etl_source.[].ip"), "IP address of the ETL source.");
EXPECT_EQ(definition.get("prometheus.enabled"), "Enable or disable Prometheus metrics.");
}
TEST(ConfigDescriptionAssertDeathTest, nonExistingKeyTest)
{
ClioConfigDescription definition{};
EXPECT_DEATH({ [[maybe_unused]] auto a = definition.get("data"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a = definition.get("etl_source.[]"); }, ".*");
}

View File

@@ -0,0 +1,40 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ConfigValue.hpp"
#include <gtest/gtest.h>
using namespace util::config;
TEST(ConfigValue, testConfigValue)
{
auto cvStr = ConfigValue{ConfigType::String}.defaultValue("12345");
EXPECT_EQ(cvStr.type(), ConfigType::String);
EXPECT_TRUE(cvStr.hasValue());
EXPECT_FALSE(cvStr.isOptional());
auto cvInt = ConfigValue{ConfigType::Integer}.defaultValue(543);
EXPECT_EQ(cvInt.type(), ConfigType::Integer);
EXPECT_TRUE(cvStr.hasValue());
EXPECT_FALSE(cvStr.isOptional());
auto cvOpt = ConfigValue{ConfigType::Integer}.optional();
EXPECT_TRUE(cvOpt.isOptional());
}

View File

@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ArrayView.hpp"
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/FakeConfigData.hpp"
#include "util/newconfig/ObjectView.hpp"
#include <gtest/gtest.h>
using namespace util::config;
struct ObjectViewTest : testing::Test {
ClioConfigDefinition const configData = generateConfig();
};
TEST_F(ObjectViewTest, ObjectValueTest)
{
auto const headerObj = configData.getObject("header");
EXPECT_FALSE(headerObj.containsKey("header"));
EXPECT_TRUE(headerObj.containsKey("text1"));
EXPECT_TRUE(headerObj.containsKey("port"));
EXPECT_TRUE(headerObj.containsKey("admin"));
EXPECT_EQ("value", headerObj.getValue("text1").asString());
EXPECT_EQ(123, headerObj.getValue("port").asIntType<int>());
EXPECT_EQ(true, headerObj.getValue("admin").asBool());
}
TEST_F(ObjectViewTest, ObjectInArray)
{
ArrayView const arr = configData.getArray("array");
EXPECT_EQ(arr.size(), 2);
ObjectView const firstObj = arr.objectAt(0);
ObjectView const secondObj = arr.objectAt(1);
EXPECT_TRUE(firstObj.containsKey("sub"));
EXPECT_TRUE(firstObj.containsKey("sub2"));
// object's key is only "sub" and "sub2"
EXPECT_FALSE(firstObj.containsKey("array.[].sub"));
EXPECT_EQ(firstObj.getValue("sub").asDouble(), 111.11);
EXPECT_EQ(firstObj.getValue("sub2").asString(), "subCategory");
EXPECT_EQ(secondObj.getValue("sub").asDouble(), 4321.55);
EXPECT_EQ(secondObj.getValue("sub2").asString(), "temporary");
}
TEST_F(ObjectViewTest, ObjectInArrayMoreComplex)
{
ArrayView const arr = configData.getArray("higher");
ASSERT_EQ(1, arr.size());
ObjectView const firstObj = arr.objectAt(0);
// this returns the 1st object inside "low"
ObjectView const sameObjFromConfigData = configData.getObject("higher.[].low", 0);
EXPECT_EQ(sameObjFromConfigData.getValue("admin").asBool(), firstObj.getValue("low.admin").asBool());
EXPECT_FALSE(firstObj.containsKey("low"));
EXPECT_TRUE(firstObj.containsKey("low.admin"));
ObjectView const objLow = firstObj.getObject("low");
EXPECT_TRUE(objLow.containsKey("section"));
EXPECT_TRUE(objLow.containsKey("admin"));
EXPECT_EQ(objLow.getValue("section").asString(), "true");
EXPECT_EQ(objLow.getValue("admin").asBool(), false);
}
TEST_F(ObjectViewTest, getArrayInObject)
{
auto const obj = configData.getObject("dosguard");
EXPECT_TRUE(obj.containsKey("whitelist.[]"));
auto const arr = obj.getArray("whitelist");
EXPECT_EQ(2, arr.size());
EXPECT_EQ("125.5.5.2", arr.valueAt(0).asString());
EXPECT_EQ("204.2.2.2", arr.valueAt(1).asString());
}
struct ObjectViewDeathTest : ObjectViewTest {};
TEST_F(ObjectViewDeathTest, incorrectKeys)
{
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("header.text1"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("head"); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getArray("header"); }, ".*");
// dies because only 1 object in higher.[].low
EXPECT_DEATH({ [[maybe_unused]] auto _ = configData.getObject("higher.[].low", 1); }, ".*");
}

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include "util/newconfig/ConfigDefinition.hpp"
#include "util/newconfig/ConfigValue.hpp"
#include "util/newconfig/FakeConfigData.hpp"
#include "util/newconfig/ValueView.hpp"
#include <gtest/gtest.h>
#include <cstdint>
using namespace util::config;
struct ValueViewTest : testing::Test {
ClioConfigDefinition const configData = generateConfig();
};
TEST_F(ValueViewTest, ValueView)
{
ConfigValue const cv = ConfigValue{ConfigType::String}.defaultValue("value");
ValueView const vv = ValueView(cv);
EXPECT_EQ("value", vv.asString());
EXPECT_EQ(ConfigType::String, vv.type());
EXPECT_EQ(true, vv.hasValue());
EXPECT_EQ(false, vv.isOptional());
}
TEST_F(ValueViewTest, DifferentIntegerTest)
{
auto const vv = configData.getValue("header.port");
auto const uint32 = vv.asIntType<uint32_t>();
auto const uint64 = vv.asIntType<uint64_t>();
auto const int32 = vv.asIntType<int32_t>();
auto const int64 = vv.asIntType<int64_t>();
EXPECT_EQ(vv.asIntType<int>(), uint32);
EXPECT_EQ(vv.asIntType<int>(), uint64);
EXPECT_EQ(vv.asIntType<int>(), int32);
EXPECT_EQ(vv.asIntType<int>(), int64);
auto const doubleVal = vv.asIntType<double>();
auto const floatVal = vv.asIntType<float>();
auto const sameDouble = vv.asDouble();
auto const sameFloat = vv.asFloat();
auto const precision = 1e-9;
EXPECT_NEAR(doubleVal, sameDouble, precision);
EXPECT_NEAR(floatVal, sameFloat, precision);
auto const ipVal = configData.getValue("ip");
auto const ipDouble = ipVal.asDouble();
auto const ipFloat = ipVal.asFloat();
EXPECT_NEAR(ipDouble, 444.22, precision);
EXPECT_NEAR(ipFloat, 444.22f, precision);
}
struct ValueDeathTest : ValueViewTest {};
TEST_F(ValueDeathTest, WrongTypes)
{
auto const vv = configData.getValue("header.port");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = vv.asBool(); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = vv.asString(); }, ".*");
auto const cv = ConfigValue{ConfigType::Integer}.defaultValue(-5);
auto const vv2 = ValueView(cv);
EXPECT_DEATH({ [[maybe_unused]] auto a_ = vv2.asIntType<uint32_t>(); }, ".*");
auto const cv2 = ConfigValue{ConfigType::String}.defaultValue("asdf");
auto const vv3 = ValueView(cv2);
EXPECT_DEATH({ [[maybe_unused]] auto a_ = vv3.asDouble(); }, ".*");
EXPECT_DEATH({ [[maybe_unused]] auto a_ = vv3.asFloat(); }, ".*");
}