mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -36,14 +36,22 @@ Array::Array(ConfigValue arg) : itemPattern_{std::move(arg)}
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view
|
||||||
|
Array::prefix(std::string_view key)
|
||||||
|
{
|
||||||
|
static constexpr std::string_view kARRAY_SUFFIX = ".[]";
|
||||||
|
ASSERT(key.contains(kARRAY_SUFFIX), "Provided key is not an array key: {}", key);
|
||||||
|
|
||||||
|
return key.substr(0, key.rfind(kARRAY_SUFFIX) + kARRAY_SUFFIX.size());
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<Error>
|
std::optional<Error>
|
||||||
Array::addValue(Value value, std::optional<std::string_view> key)
|
Array::addValue(Value value, std::optional<std::string_view> key)
|
||||||
{
|
{
|
||||||
auto const& configValPattern = itemPattern_;
|
auto const constraint = itemPattern_.getConstraint();
|
||||||
auto const constraint = configValPattern.getConstraint();
|
|
||||||
|
|
||||||
auto newElem = constraint.has_value() ? ConfigValue{configValPattern.type()}.withConstraint(constraint->get())
|
auto newElem = constraint.has_value() ? ConfigValue{itemPattern_.type()}.withConstraint(constraint->get())
|
||||||
: ConfigValue{configValPattern.type()};
|
: ConfigValue{itemPattern_.type()};
|
||||||
if (auto const maybeError = newElem.setValue(value, key); maybeError.has_value())
|
if (auto const maybeError = newElem.setValue(value, key); maybeError.has_value())
|
||||||
return maybeError;
|
return maybeError;
|
||||||
elements_.emplace_back(std::move(newElem));
|
elements_.emplace_back(std::move(newElem));
|
||||||
|
|||||||
@@ -47,6 +47,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
Array(ConfigValue arg);
|
Array(ConfigValue arg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract array prefix from a key, For example for a key foo.[].bar the method will return foo.[]
|
||||||
|
* @note Provided key must contain '.[]'
|
||||||
|
* @warning Be careful with string_view! Returned value is valid only while the key is valid
|
||||||
|
*
|
||||||
|
* @param key The key to extract the array prefix from
|
||||||
|
* @return Prefix of array extracted from the key
|
||||||
|
*/
|
||||||
|
static std::string_view
|
||||||
|
prefix(std::string_view key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add ConfigValues to Array class
|
* @brief Add ConfigValues to Array class
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "util/newconfig/ConfigFileJson.hpp"
|
#include "util/newconfig/ConfigFileJson.hpp"
|
||||||
|
|
||||||
#include "util/Assert.hpp"
|
#include "util/Assert.hpp"
|
||||||
|
#include "util/newconfig/Array.hpp"
|
||||||
#include "util/newconfig/Error.hpp"
|
#include "util/newconfig/Error.hpp"
|
||||||
#include "util/newconfig/Types.hpp"
|
#include "util/newconfig/Types.hpp"
|
||||||
|
|
||||||
@@ -30,15 +31,19 @@
|
|||||||
#include <boost/json/value.hpp>
|
#include <boost/json/value.hpp>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -69,14 +74,17 @@ extractJsonValue(boost::json::value const& jsonValue)
|
|||||||
if (jsonValue.is_double()) {
|
if (jsonValue.is_double()) {
|
||||||
return jsonValue.as_double();
|
return jsonValue.as_double();
|
||||||
}
|
}
|
||||||
ASSERT(false, "Json is not of type int, uint, string, bool or double");
|
if (jsonValue.is_null()) {
|
||||||
|
return NullType{};
|
||||||
|
}
|
||||||
|
ASSERT(false, "Json is not of type null, int, uint, string, bool or double");
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ConfigFileJson::ConfigFileJson(boost::json::object jsonObj)
|
ConfigFileJson::ConfigFileJson(boost::json::object jsonObj)
|
||||||
{
|
{
|
||||||
flattenJson(jsonObj, "");
|
flattenJson(jsonObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<ConfigFileJson, Error>
|
std::expected<ConfigFileJson, Error>
|
||||||
@@ -86,8 +94,7 @@ ConfigFileJson::makeConfigFileJson(std::filesystem::path const& configFilePath)
|
|||||||
if (auto const in = std::ifstream(configFilePath.string(), std::ios::in | std::ios::binary); in) {
|
if (auto const in = std::ifstream(configFilePath.string(), std::ios::in | std::ios::binary); in) {
|
||||||
std::stringstream contents;
|
std::stringstream contents;
|
||||||
contents << in.rdbuf();
|
contents << in.rdbuf();
|
||||||
auto opts = boost::json::parse_options{};
|
auto const opts = boost::json::parse_options{.allow_comments = true};
|
||||||
opts.allow_comments = true;
|
|
||||||
auto const tempObj = boost::json::parse(contents.str(), {}, opts).as_object();
|
auto const tempObj = boost::json::parse(contents.str(), {}, opts).as_object();
|
||||||
return ConfigFileJson{tempObj};
|
return ConfigFileJson{tempObj};
|
||||||
}
|
}
|
||||||
@@ -105,7 +112,9 @@ ConfigFileJson::makeConfigFileJson(std::filesystem::path const& configFilePath)
|
|||||||
Value
|
Value
|
||||||
ConfigFileJson::getValue(std::string_view key) const
|
ConfigFileJson::getValue(std::string_view key) const
|
||||||
{
|
{
|
||||||
|
ASSERT(containsKey(key), "Key {} not found in ConfigFileJson", key);
|
||||||
auto const jsonValue = jsonObject_.at(key);
|
auto const jsonValue = jsonObject_.at(key);
|
||||||
|
ASSERT(jsonValue.is_primitive(), "Key {} has value that is not a primitive", key);
|
||||||
auto const value = extractJsonValue(jsonValue);
|
auto const value = extractJsonValue(jsonValue);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -113,14 +122,15 @@ ConfigFileJson::getValue(std::string_view key) const
|
|||||||
std::vector<Value>
|
std::vector<Value>
|
||||||
ConfigFileJson::getArray(std::string_view key) const
|
ConfigFileJson::getArray(std::string_view key) const
|
||||||
{
|
{
|
||||||
|
ASSERT(containsKey(key), "Key {} not found in ConfigFileJson", key);
|
||||||
ASSERT(jsonObject_.at(key).is_array(), "Key {} has value that is not an array", key);
|
ASSERT(jsonObject_.at(key).is_array(), "Key {} has value that is not an array", key);
|
||||||
|
|
||||||
std::vector<Value> configValues;
|
std::vector<Value> configValues;
|
||||||
auto const arr = jsonObject_.at(key).as_array();
|
auto const arr = jsonObject_.at(key).as_array();
|
||||||
|
|
||||||
for (auto const& item : arr) {
|
for (auto const& item : arr) {
|
||||||
auto const value = extractJsonValue(item);
|
auto value = extractJsonValue(item);
|
||||||
configValues.emplace_back(value);
|
configValues.emplace_back(std::move(value));
|
||||||
}
|
}
|
||||||
return configValues;
|
return configValues;
|
||||||
}
|
}
|
||||||
@@ -131,38 +141,90 @@ ConfigFileJson::containsKey(std::string_view key) const
|
|||||||
return jsonObject_.contains(key);
|
return jsonObject_.contains(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
boost::json::object const&
|
||||||
ConfigFileJson::flattenJson(boost::json::object const& obj, std::string const& prefix)
|
ConfigFileJson::inner() const
|
||||||
{
|
{
|
||||||
for (auto const& [key, value] : obj) {
|
return jsonObject_;
|
||||||
std::string const fullKey = prefix.empty() ? std::string(key) : fmt::format("{}.{}", prefix, std::string(key));
|
}
|
||||||
|
|
||||||
// In ClioConfigDefinition, value must be a primitive or array
|
void
|
||||||
if (value.is_object()) {
|
ConfigFileJson::flattenJson(boost::json::object const& jsonRootObject)
|
||||||
flattenJson(value.as_object(), fullKey);
|
{
|
||||||
} else if (value.is_array()) {
|
struct Task {
|
||||||
auto const& arr = value.as_array();
|
boost::json::object const& object;
|
||||||
for (std::size_t i = 0; i < arr.size(); ++i) {
|
std::string prefix;
|
||||||
std::string const arrayPrefix = fullKey + ".[]";
|
std::optional<size_t> arrayIndex = std::nullopt;
|
||||||
if (arr[i].is_object()) {
|
};
|
||||||
flattenJson(arr[i].as_object(), arrayPrefix);
|
|
||||||
|
std::queue<Task> tasks;
|
||||||
|
tasks.push(Task{.object = jsonRootObject, .prefix = ""});
|
||||||
|
|
||||||
|
std::unordered_map<std::string, size_t> arraysSizes;
|
||||||
|
|
||||||
|
while (not tasks.empty()) {
|
||||||
|
auto const task = std::move(tasks.front());
|
||||||
|
tasks.pop();
|
||||||
|
|
||||||
|
for (auto const& [key, value] : task.object) {
|
||||||
|
auto fullKey =
|
||||||
|
task.prefix.empty() ? std::string(key) : fmt::format("{}.{}", task.prefix, std::string_view{key});
|
||||||
|
|
||||||
|
if (value.is_object()) {
|
||||||
|
tasks.push(
|
||||||
|
Task{.object = value.as_object(), .prefix = std::move(fullKey), .arrayIndex = task.arrayIndex}
|
||||||
|
);
|
||||||
|
} else if (value.is_array()) {
|
||||||
|
fullKey += ".[]";
|
||||||
|
auto const& array = value.as_array();
|
||||||
|
|
||||||
|
if (std::ranges::all_of(array, [](auto const& v) { return v.is_primitive(); })) {
|
||||||
|
jsonObject_[fullKey] = array;
|
||||||
|
} else if (std::ranges::all_of(array, [](auto const& v) { return v.is_object(); })) {
|
||||||
|
for (size_t i = 0; i < array.size(); ++i) {
|
||||||
|
tasks.push(Task{.object = array.at(i).as_object(), .prefix = fullKey, .arrayIndex = i});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
jsonObject_[arrayPrefix] = arr;
|
ASSERT(
|
||||||
|
false,
|
||||||
|
"Arrays containing both values and objects are not supported. Please check the array {}",
|
||||||
|
fullKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if "[]" is present in key, then value must be an array instead of primitive
|
|
||||||
if (fullKey.contains(".[]") && !jsonObject_.contains(fullKey)) {
|
|
||||||
boost::json::array newArray;
|
|
||||||
newArray.emplace_back(value);
|
|
||||||
jsonObject_[fullKey] = newArray;
|
|
||||||
} else if (fullKey.contains(".[]") && jsonObject_.contains(fullKey)) {
|
|
||||||
jsonObject_[fullKey].as_array().emplace_back(value);
|
|
||||||
} else {
|
} else {
|
||||||
jsonObject_[fullKey] = value;
|
if (task.arrayIndex.has_value()) {
|
||||||
|
if (not jsonObject_.contains(fullKey)) {
|
||||||
|
jsonObject_[fullKey] = boost::json::array{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& targetArray = jsonObject_.at(fullKey).as_array();
|
||||||
|
while (targetArray.size() < (*task.arrayIndex + 1)) {
|
||||||
|
targetArray.push_back(boost::json::value());
|
||||||
|
}
|
||||||
|
targetArray.at(*task.arrayIndex) = value;
|
||||||
|
auto const prefix = std::string{Array::prefix(fullKey)};
|
||||||
|
arraysSizes[prefix] = std::max(arraysSizes[prefix], targetArray.size());
|
||||||
|
} else {
|
||||||
|
jsonObject_[fullKey] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjust length of each array containing objects
|
||||||
|
std::ranges::for_each(jsonObject_, [&arraysSizes](auto& item) {
|
||||||
|
auto const key = item.key();
|
||||||
|
if (not key.contains("[]"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& value = item.value();
|
||||||
|
auto const prefix = std::string{Array::prefix(key)};
|
||||||
|
if (auto const it = arraysSizes.find(prefix); it != arraysSizes.end()) {
|
||||||
|
auto const size = it->second;
|
||||||
|
while (value.as_array().size() < size) {
|
||||||
|
value.as_array().push_back(boost::json::value{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace util::config
|
} // namespace util::config
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ namespace util::config {
|
|||||||
|
|
||||||
/** @brief Json representation of config */
|
/** @brief Json representation of config */
|
||||||
class ConfigFileJson final : public ConfigFileInterface {
|
class ConfigFileJson final : public ConfigFileInterface {
|
||||||
|
boost::json::object jsonObject_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new ConfigJson object and stores the values from
|
* @brief Construct a new ConfigJson object and stores the values from
|
||||||
@@ -81,20 +83,26 @@ public:
|
|||||||
[[nodiscard]] static std::expected<ConfigFileJson, Error>
|
[[nodiscard]] static std::expected<ConfigFileJson, Error>
|
||||||
makeConfigFileJson(std::filesystem::path const& configFilePath);
|
makeConfigFileJson(std::filesystem::path const& configFilePath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the inner representation of json file.
|
||||||
|
* @note This method is mostly used for testing purposes.
|
||||||
|
*
|
||||||
|
* @return The inner representation of json file.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] boost::json::object const&
|
||||||
|
inner() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Recursive function to flatten a JSON object into the same structure as the Clio Config.
|
* @brief Method to flatten a JSON object into the same structure as the Clio Config.
|
||||||
*
|
*
|
||||||
* The keys will end up having the same naming convensions in Clio Config.
|
* The keys will end up having the same naming conventions in Clio Config.
|
||||||
* Other than the keys specified in user Config file, no new keys are created.
|
* Other than the keys specified in user Config file, no new keys are created.
|
||||||
*
|
*
|
||||||
* @param obj The JSON object to flatten.
|
* @param obj The JSON object to flatten.
|
||||||
* @param prefix The prefix to use for the keys in the flattened object.
|
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
flattenJson(boost::json::object const& obj, std::string const& prefix);
|
flattenJson(boost::json::object const& jsonRootObject);
|
||||||
|
|
||||||
boost::json::object jsonObject_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace util::config
|
} // namespace util::config
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
namespace util::config {
|
namespace util::config {
|
||||||
@@ -80,18 +81,31 @@ public:
|
|||||||
[[nodiscard]] std::optional<Error>
|
[[nodiscard]] std::optional<Error>
|
||||||
setValue(Value value, std::optional<std::string_view> key = std::nullopt)
|
setValue(Value value, std::optional<std::string_view> key = std::nullopt)
|
||||||
{
|
{
|
||||||
|
if (std::holds_alternative<NullType>(value)) {
|
||||||
|
if (hasValue()) {
|
||||||
|
// Using default value
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (not isOptional()) {
|
||||||
|
return Error{
|
||||||
|
key.value_or("Unknown_key"),
|
||||||
|
"Provided value is null but ConfigValue is not optional and doesn't have a default value."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
value_ = std::move(value);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
auto err = checkTypeConsistency(type_, value);
|
auto err = checkTypeConsistency(type_, value);
|
||||||
if (err.has_value()) {
|
if (err.has_value()) {
|
||||||
if (key.has_value())
|
err->error = fmt::format("{} {}", key.value_or("Unknown_key"), err->error);
|
||||||
err->error = fmt::format("{} {}", key.value(), err->error);
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cons_.has_value()) {
|
if (cons_.has_value()) {
|
||||||
auto constraintCheck = cons_->get().checkConstraint(value);
|
auto constraintCheck = cons_->get().checkConstraint(value);
|
||||||
if (constraintCheck.has_value()) {
|
if (constraintCheck.has_value()) {
|
||||||
if (key.has_value())
|
constraintCheck->error = fmt::format("{} {}", key.value_or("Unknown_key"), constraintCheck->error);
|
||||||
constraintCheck->error = fmt::format("{} {}", key.value(), constraintCheck->error);
|
|
||||||
return constraintCheck;
|
return constraintCheck;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +141,8 @@ public:
|
|||||||
[&type](bool tmp) { type = fmt::format("bool {}", tmp); },
|
[&type](bool tmp) { type = fmt::format("bool {}", tmp); },
|
||||||
[&type](std::string const& tmp) { type = fmt::format("string {}", tmp); },
|
[&type](std::string const& tmp) { type = fmt::format("string {}", tmp); },
|
||||||
[&type](double tmp) { type = fmt::format("double {}", tmp); },
|
[&type](double tmp) { type = fmt::format("double {}", tmp); },
|
||||||
[&type](int64_t tmp) { type = fmt::format("int {}", tmp); }
|
[&type](int64_t tmp) { type = fmt::format("int {}", tmp); },
|
||||||
|
[&type](NullType) { type = "null"; },
|
||||||
},
|
},
|
||||||
value_.value()
|
value_.value()
|
||||||
);
|
);
|
||||||
@@ -199,6 +214,7 @@ public:
|
|||||||
[[nodiscard]] Value const&
|
[[nodiscard]] Value const&
|
||||||
getValue() const
|
getValue() const
|
||||||
{
|
{
|
||||||
|
ASSERT(value_.has_value(), "getValue() is called when there is no value set");
|
||||||
return value_.value();
|
return value_.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#include "util/UnsupportedType.hpp"
|
#include "util/UnsupportedType.hpp"
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <expected>
|
#include <expected>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
@@ -30,7 +32,23 @@
|
|||||||
namespace util::config {
|
namespace util::config {
|
||||||
|
|
||||||
/** @brief Custom clio config types */
|
/** @brief Custom clio config types */
|
||||||
enum class ConfigType { Integer, String, Double, Boolean };
|
enum class ConfigType { Integer, String, Double, Boolean, Null };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A type that represents a null value
|
||||||
|
*/
|
||||||
|
struct NullType {
|
||||||
|
/**
|
||||||
|
* @brief Compare two NullType objects
|
||||||
|
*
|
||||||
|
* @return true always. Any two NullType objects are equal
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool
|
||||||
|
operator==(NullType const&) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Prints the specified config type to output stream
|
* @brief Prints the specified config type to output stream
|
||||||
@@ -43,7 +61,7 @@ std::ostream&
|
|||||||
operator<<(std::ostream& stream, ConfigType type);
|
operator<<(std::ostream& stream, ConfigType type);
|
||||||
|
|
||||||
/** @brief Represents the supported Config Values */
|
/** @brief Represents the supported Config Values */
|
||||||
using Value = std::variant<int64_t, std::string, bool, double>;
|
using Value = std::variant<int64_t, std::string, bool, double, NullType>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Prints the specified value to output stream
|
* @brief Prints the specified value to output stream
|
||||||
@@ -73,9 +91,24 @@ getType()
|
|||||||
return ConfigType::Double;
|
return ConfigType::Double;
|
||||||
} else if constexpr (std::is_same_v<Type, bool>) {
|
} else if constexpr (std::is_same_v<Type, bool>) {
|
||||||
return ConfigType::Boolean;
|
return ConfigType::Boolean;
|
||||||
|
} else if constexpr (std::is_same_v<Type, NullType>) {
|
||||||
|
return ConfigType::Null;
|
||||||
} else {
|
} else {
|
||||||
static_assert(util::Unsupported<Type>, "Wrong config type");
|
static_assert(util::Unsupported<Type>, "Wrong config type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace util::config
|
} // namespace util::config
|
||||||
|
|
||||||
|
/** @cond */
|
||||||
|
// Doxygen could not parse this
|
||||||
|
template <>
|
||||||
|
struct fmt::formatter<util::config::NullType> : fmt::formatter<char const*> {
|
||||||
|
[[nodiscard]]
|
||||||
|
auto
|
||||||
|
format(util::config::NullType const&, fmt::format_context& ctx)
|
||||||
|
{
|
||||||
|
return fmt::formatter<char const*>::format("null", ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/** @endcond */
|
||||||
|
|||||||
@@ -183,8 +183,7 @@ target_sources(
|
|||||||
util/newconfig/ClioConfigDefinitionTests.cpp
|
util/newconfig/ClioConfigDefinitionTests.cpp
|
||||||
util/newconfig/ConfigValueTests.cpp
|
util/newconfig/ConfigValueTests.cpp
|
||||||
util/newconfig/ObjectViewTests.cpp
|
util/newconfig/ObjectViewTests.cpp
|
||||||
util/newconfig/JsonConfigFileTests.cpp
|
util/newconfig/ConfigFileJsonTests.cpp
|
||||||
util/newconfig/JsonFileTests.cpp
|
|
||||||
util/newconfig/ValueViewTests.cpp
|
util/newconfig/ValueViewTests.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,18 @@
|
|||||||
|
|
||||||
using namespace util::config;
|
using namespace util::config;
|
||||||
|
|
||||||
|
TEST(ArrayTest, prefix)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Array::prefix("foo.[]"), "foo.[]");
|
||||||
|
EXPECT_EQ(Array::prefix("foo.[].bar"), "foo.[]");
|
||||||
|
EXPECT_EQ(Array::prefix("foo.bar.[].baz"), "foo.bar.[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ArrayDeathTest, prefix)
|
||||||
|
{
|
||||||
|
EXPECT_DEATH(Array::prefix("foo.bar"), ".*");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ArrayTest, addSingleValue)
|
TEST(ArrayTest, addSingleValue)
|
||||||
{
|
{
|
||||||
auto arr = Array{ConfigValue{ConfigType::Double}};
|
auto arr = Array{ConfigValue{ConfigType::Double}};
|
||||||
|
|||||||
480
tests/unit/util/newconfig/ConfigFileJsonTests.cpp
Normal file
480
tests/unit/util/newconfig/ConfigFileJsonTests.cpp
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/LoggerFixtures.hpp"
|
||||||
|
#include "util/NameGenerator.hpp"
|
||||||
|
#include "util/OverloadSet.hpp"
|
||||||
|
#include "util/TmpFile.hpp"
|
||||||
|
#include "util/newconfig/ConfigFileJson.hpp"
|
||||||
|
#include "util/newconfig/Types.hpp"
|
||||||
|
|
||||||
|
#include <boost/json/array.hpp>
|
||||||
|
#include <boost/json/object.hpp>
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
using namespace util::config;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr auto kEPS = 1e-9;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct ConfigFileJsonParseTestBundle {
|
||||||
|
using ValidationMap = std::unordered_map<
|
||||||
|
std::string,
|
||||||
|
std::variant<int64_t, double, bool, std::string, boost::json::object, boost::json::array>>;
|
||||||
|
|
||||||
|
std::string testName;
|
||||||
|
std::string configStr;
|
||||||
|
ValidationMap validationMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConfigFileJsonParseTest : NoLoggerFixture, testing::WithParamInterface<ConfigFileJsonParseTestBundle> {};
|
||||||
|
|
||||||
|
TEST_P(ConfigFileJsonParseTest, parseValues)
|
||||||
|
{
|
||||||
|
ConfigFileJson const configFile{boost::json::parse(GetParam().configStr).as_object()};
|
||||||
|
|
||||||
|
auto const& flatJson = configFile.inner();
|
||||||
|
|
||||||
|
ASSERT_EQ(GetParam().validationMap.size(), flatJson.size());
|
||||||
|
std::ranges::for_each(GetParam().validationMap, [&flatJson](auto const& kvPair) {
|
||||||
|
auto const& key = kvPair.first;
|
||||||
|
auto const& value = kvPair.second;
|
||||||
|
|
||||||
|
EXPECT_TRUE(flatJson.contains(key));
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
util::OverloadSet{
|
||||||
|
[&flatJson, &key](int64_t const v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_number()) << key << ": " << v;
|
||||||
|
EXPECT_EQ(flatJson.at(key).as_int64(), v) << key << ": " << v;
|
||||||
|
},
|
||||||
|
[&flatJson, &key](double const v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_double()) << key << ": " << v;
|
||||||
|
EXPECT_NEAR(flatJson.at(key).as_double(), v, kEPS) << key << ": " << v;
|
||||||
|
},
|
||||||
|
[&flatJson, &key](bool const v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_bool()) << key << ": " << v;
|
||||||
|
EXPECT_EQ(flatJson.at(key).as_bool(), v) << key << ": " << v;
|
||||||
|
},
|
||||||
|
[&flatJson, &key](std::string const& v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_string()) << key << ": " << v;
|
||||||
|
EXPECT_EQ(flatJson.at(key).as_string(), v) << key << ": " << v;
|
||||||
|
},
|
||||||
|
[&flatJson, &key](boost::json::object const& v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_object()) << key << ": " << v;
|
||||||
|
EXPECT_EQ(flatJson.at(key).as_object(), v) << key << ": " << v;
|
||||||
|
},
|
||||||
|
[&flatJson, &key](boost::json::array const& v) {
|
||||||
|
EXPECT_TRUE(flatJson.at(key).is_array()) << key << ": " << v;
|
||||||
|
EXPECT_EQ(flatJson.at(key).as_array(), v) << key << ": " << v;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_CASE_P(
|
||||||
|
ConfigFileJsonParseTestGroup,
|
||||||
|
ConfigFileJsonParseTest,
|
||||||
|
testing::Values(
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "values",
|
||||||
|
.configStr = R"json({
|
||||||
|
"int": 42,
|
||||||
|
"double": 123.456,
|
||||||
|
"bool": true,
|
||||||
|
"string": "some string"
|
||||||
|
})json",
|
||||||
|
.validationMap = {{"int", 42}, {"double", 123.456}, {"bool", true}, {"string", "some string"}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "nested",
|
||||||
|
.configStr = R"json({
|
||||||
|
"level_0": {
|
||||||
|
"int": 42,
|
||||||
|
"level_1":{
|
||||||
|
"double": 123.456,
|
||||||
|
"level_2": {
|
||||||
|
"bool": true,
|
||||||
|
"level_3": {
|
||||||
|
"string": "some string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"level_0.int", 42},
|
||||||
|
{"level_0.level_1.double", 123.456},
|
||||||
|
{"level_0.level_1.level_2.bool", true},
|
||||||
|
{"level_0.level_1.level_2.level_3.string", "some string"}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [1, 2, 3]
|
||||||
|
})json",
|
||||||
|
.validationMap = {{"array.[]", boost::json::array{1, 2, 3}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "nested_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"level_0": {
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"level_1": {
|
||||||
|
"array": [4, 5, 6],
|
||||||
|
"level_2": {
|
||||||
|
"array": [7, 8, 9]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{
|
||||||
|
{"level_0.array.[]", boost::json::array{1, 2, 3}},
|
||||||
|
{"level_0.level_1.array.[]", boost::json::array{4, 5, 6}},
|
||||||
|
{"level_0.level_1.level_2.array.[]", boost::json::array{7, 8, 9}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "mixed",
|
||||||
|
.configStr = R"json({
|
||||||
|
"int": 42,
|
||||||
|
"double": 123.456,
|
||||||
|
"bool": true,
|
||||||
|
"string": "some string",
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"nested": {
|
||||||
|
"int": 42,
|
||||||
|
"double": 123.456,
|
||||||
|
"bool": true,
|
||||||
|
"string": "some string",
|
||||||
|
"array": [1, 2, 3]
|
||||||
|
}
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{
|
||||||
|
{"int", 42},
|
||||||
|
{"double", 123.456},
|
||||||
|
{"bool", true},
|
||||||
|
{"string", "some string"},
|
||||||
|
{"array.[]", boost::json::array{1, 2, 3}},
|
||||||
|
{"nested.int", 42},
|
||||||
|
{"nested.double", 123.456},
|
||||||
|
{"nested.bool", true},
|
||||||
|
{"nested.string", "some string"},
|
||||||
|
{"nested.array.[]", boost::json::array{1, 2, 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{.testName = "empty", .configStr = R"json({})json", .validationMap = {}},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "empty_nested",
|
||||||
|
.configStr = R"json({
|
||||||
|
"level_0": {
|
||||||
|
"level_1": {
|
||||||
|
"level_2": {
|
||||||
|
"level_3": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})json",
|
||||||
|
.validationMap = {}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "empty_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": []
|
||||||
|
})json",
|
||||||
|
.validationMap = {{"array.[]", boost::json::array{}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "empty_nested_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"level_0": {
|
||||||
|
"array": [],
|
||||||
|
"level_1": {
|
||||||
|
"array": [],
|
||||||
|
"level_2": {
|
||||||
|
"array": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{
|
||||||
|
{"level_0.array.[]", boost::json::array{}},
|
||||||
|
{"level_0.level_1.array.[]", boost::json::array{}},
|
||||||
|
{"level_0.level_1.level_2.array.[]", boost::json::array{}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "object_inside_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{ "int": 42 }
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap = {{"array.[].int", boost::json::array{42}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "object_with_optional_fields_inside_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{"int": 42},
|
||||||
|
{"int": 24, "bool": true}
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"array.[].int", boost::json::array{42, 24}},
|
||||||
|
{"array.[].bool", boost::json::array{boost::json::value{}, true}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "full_object_is_at_the_front_of_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{"int": 42, "bool": true},
|
||||||
|
{"int": 2},
|
||||||
|
{"int": 4}
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"array.[].int", boost::json::array{42, 2, 4}},
|
||||||
|
{"array.[].bool", boost::json::array{true, boost::json::value{}, boost::json::value{}}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "full_object_is_in_the_middle_of_array",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{"int": 42},
|
||||||
|
{"int": 2, "bool": true},
|
||||||
|
{"int": 4}
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"array.[].int", boost::json::array{42, 2, 4}},
|
||||||
|
{"array.[].bool", boost::json::array{boost::json::value{}, true, boost::json::value{}}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "no_full_object",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{"int": 42},
|
||||||
|
{"int": 2},
|
||||||
|
{"bool": true}
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"array.[].int", boost::json::array{42, 2, boost::json::value{}}},
|
||||||
|
{"array.[].bool", boost::json::array{boost::json::value{}, boost::json::value{}, true}}}
|
||||||
|
},
|
||||||
|
ConfigFileJsonParseTestBundle{
|
||||||
|
.testName = "array_with_nexted_objects",
|
||||||
|
.configStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{ "object": { "int": 42 } },
|
||||||
|
{ "object": { "string": "some string" } }
|
||||||
|
]
|
||||||
|
})json",
|
||||||
|
.validationMap =
|
||||||
|
{{"array.[].object.int", boost::json::array{42, boost::json::value{}}},
|
||||||
|
{"array.[].object.string", boost::json::array{boost::json::value{}, "some string"}}}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
tests::util::kNAME_GENERATOR
|
||||||
|
);
|
||||||
|
|
||||||
|
struct ConfigFileJsonTest : NoLoggerFixture {};
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonTest, getValue)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"int": 42,
|
||||||
|
"object": { "string": "some string" },
|
||||||
|
"bool": true,
|
||||||
|
"double": 123.456,
|
||||||
|
"null": null
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
|
||||||
|
auto const intValue = jsonFileObj.getValue("int");
|
||||||
|
ASSERT_TRUE(std::holds_alternative<int64_t>(intValue));
|
||||||
|
EXPECT_EQ(std::get<int64_t>(intValue), 42);
|
||||||
|
|
||||||
|
auto const stringValue = jsonFileObj.getValue("object.string");
|
||||||
|
ASSERT_TRUE(std::holds_alternative<std::string>(stringValue));
|
||||||
|
EXPECT_EQ(std::get<std::string>(stringValue), "some string");
|
||||||
|
|
||||||
|
auto const boolValue = jsonFileObj.getValue("bool");
|
||||||
|
ASSERT_TRUE(std::holds_alternative<bool>(boolValue));
|
||||||
|
EXPECT_EQ(std::get<bool>(boolValue), true);
|
||||||
|
|
||||||
|
auto const doubleValue = jsonFileObj.getValue("double");
|
||||||
|
ASSERT_TRUE(std::holds_alternative<double>(doubleValue));
|
||||||
|
EXPECT_NEAR(std::get<double>(doubleValue), 123.456, kEPS);
|
||||||
|
|
||||||
|
auto const nullValue = jsonFileObj.getValue("null");
|
||||||
|
EXPECT_TRUE(std::holds_alternative<NullType>(nullValue));
|
||||||
|
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("object.int"));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigFileJsonDeathTest : ConfigFileJsonTest {};
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonDeathTest, getValueInvalidKey)
|
||||||
|
{
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse("{}").as_object()};
|
||||||
|
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getValue("some_key"), ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonDeathTest, getValueOfArray)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"array": [1, 2, 3]
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getValue("array"), ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonTest, getArray)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"array": [1, "2", 3.14, true],
|
||||||
|
"object": { "array": [3, 4] }
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
|
||||||
|
auto const array = jsonFileObj.getArray("array.[]");
|
||||||
|
ASSERT_EQ(array.size(), 4);
|
||||||
|
ASSERT_TRUE(std::holds_alternative<int64_t>(array.at(0)));
|
||||||
|
EXPECT_EQ(std::get<int64_t>(array.at(0)), 1);
|
||||||
|
ASSERT_TRUE(std::holds_alternative<std::string>(array.at(1)));
|
||||||
|
EXPECT_EQ(std::get<std::string>(array.at(1)), "2");
|
||||||
|
ASSERT_TRUE(std::holds_alternative<double>(array.at(2)));
|
||||||
|
EXPECT_NEAR(std::get<double>(array.at(2)), 3.14, kEPS);
|
||||||
|
ASSERT_TRUE(std::holds_alternative<bool>(array.at(3)));
|
||||||
|
EXPECT_EQ(std::get<bool>(array.at(3)), true);
|
||||||
|
|
||||||
|
auto const arrayFromObject = jsonFileObj.getArray("object.array.[]");
|
||||||
|
ASSERT_EQ(arrayFromObject.size(), 2);
|
||||||
|
EXPECT_EQ(std::get<int64_t>(arrayFromObject.at(0)), 3);
|
||||||
|
EXPECT_EQ(std::get<int64_t>(arrayFromObject.at(1)), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonTest, getArrayObjectInArray)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"array": [
|
||||||
|
{ "int": 42 },
|
||||||
|
{ "string": "some string" }
|
||||||
|
]
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
|
||||||
|
auto const ints = jsonFileObj.getArray("array.[].int");
|
||||||
|
ASSERT_EQ(ints.size(), 2);
|
||||||
|
ASSERT_TRUE(std::holds_alternative<int64_t>(ints.at(0)));
|
||||||
|
EXPECT_EQ(std::get<int64_t>(ints.at(0)), 42);
|
||||||
|
EXPECT_TRUE(std::holds_alternative<NullType>(ints.at(1)));
|
||||||
|
|
||||||
|
auto const strings = jsonFileObj.getArray("array.[].string");
|
||||||
|
ASSERT_EQ(strings.size(), 2);
|
||||||
|
EXPECT_TRUE(std::holds_alternative<NullType>(strings.at(0)));
|
||||||
|
ASSERT_TRUE(std::holds_alternative<std::string>(strings.at(1)));
|
||||||
|
EXPECT_EQ(std::get<std::string>(strings.at(1)), "some string");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonDeathTest, getArrayInvalidKey)
|
||||||
|
{
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse("{}").as_object()};
|
||||||
|
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("some_key"), ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonDeathTest, getArrayNotArray)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"int": 42
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("int"), ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonTest, containsKey)
|
||||||
|
{
|
||||||
|
auto const jsonStr = R"json({
|
||||||
|
"int": 42,
|
||||||
|
"object": { "string": "some string", "array": [1, 2, 3] },
|
||||||
|
"array2": [1, 2, 3],
|
||||||
|
"array_of_objects": [ {"int": 42}, {"string": "some string"} ]
|
||||||
|
})json";
|
||||||
|
auto const jsonFileObj = ConfigFileJson{boost::json::parse(jsonStr).as_object()};
|
||||||
|
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("int"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("other_key"));
|
||||||
|
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("object.string"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("object.int"));
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("object.array.[]"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("object.array"));
|
||||||
|
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("array2.[]"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("array2"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("array2.[].int"));
|
||||||
|
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("array_of_objects.[].int"));
|
||||||
|
EXPECT_TRUE(jsonFileObj.containsKey("array_of_objects.[].string"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("array_of_objects.[]"));
|
||||||
|
EXPECT_FALSE(jsonFileObj.containsKey("array_of_objects.[].object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigFileJsonMakeTest : ConfigFileJsonTest {};
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonMakeTest, invalidFile)
|
||||||
|
{
|
||||||
|
auto const jsonFileObj = ConfigFileJson::makeConfigFileJson("does_not_exist");
|
||||||
|
EXPECT_FALSE(jsonFileObj.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonMakeTest, invalidJson)
|
||||||
|
{
|
||||||
|
auto const file = TmpFile("invalid json");
|
||||||
|
auto const jsonFileObj = ConfigFileJson::makeConfigFileJson(file.path);
|
||||||
|
EXPECT_FALSE(jsonFileObj.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigFileJsonMakeTest, validFile)
|
||||||
|
{
|
||||||
|
auto const file = TmpFile(R"json({ "int": 42 })json");
|
||||||
|
auto const jsonFileObj = ConfigFileJson::makeConfigFileJson(file.path);
|
||||||
|
ASSERT_TRUE(jsonFileObj.has_value());
|
||||||
|
|
||||||
|
auto const& flatJson = jsonFileObj->inner();
|
||||||
|
ASSERT_EQ(flatJson.size(), 1);
|
||||||
|
ASSERT_TRUE(flatJson.contains("int"));
|
||||||
|
ASSERT_TRUE(flatJson.at("int").is_number());
|
||||||
|
EXPECT_EQ(flatJson.at("int").as_int64(), 42);
|
||||||
|
}
|
||||||
@@ -17,62 +17,173 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "util/LoggerFixtures.hpp"
|
||||||
#include "util/newconfig/ConfigConstraints.hpp"
|
#include "util/newconfig/ConfigConstraints.hpp"
|
||||||
#include "util/newconfig/ConfigValue.hpp"
|
#include "util/newconfig/ConfigValue.hpp"
|
||||||
|
#include "util/newconfig/Error.hpp"
|
||||||
#include "util/newconfig/Types.hpp"
|
#include "util/newconfig/Types.hpp"
|
||||||
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace util::config;
|
using namespace util::config;
|
||||||
|
|
||||||
TEST(ConfigValue, GetSetString)
|
struct ConfigValueTest : NoLoggerFixture {};
|
||||||
|
struct ConfigValueDeathTest : ConfigValueTest {};
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, construct)
|
||||||
{
|
{
|
||||||
auto const cvStr = ConfigValue{ConfigType::String}.defaultValue("12345");
|
ConfigValue const cv{ConfigType::Integer};
|
||||||
EXPECT_EQ(cvStr.type(), ConfigType::String);
|
EXPECT_FALSE(cv.hasValue());
|
||||||
EXPECT_TRUE(cvStr.hasValue());
|
EXPECT_FALSE(cv.isOptional());
|
||||||
EXPECT_FALSE(cvStr.isOptional());
|
EXPECT_EQ(cv.type(), ConfigType::Integer);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, GetSetInteger)
|
TEST_F(ConfigValueTest, optional)
|
||||||
{
|
{
|
||||||
auto const cvInt = ConfigValue{ConfigType::Integer}.defaultValue(543);
|
auto const cv = ConfigValue{ConfigType::Integer}.optional();
|
||||||
EXPECT_EQ(cvInt.type(), ConfigType::Integer);
|
EXPECT_FALSE(cv.hasValue());
|
||||||
EXPECT_TRUE(cvInt.hasValue());
|
EXPECT_TRUE(cv.isOptional());
|
||||||
EXPECT_FALSE(cvInt.isOptional());
|
EXPECT_EQ(cv.type(), ConfigType::Integer);
|
||||||
|
}
|
||||||
|
|
||||||
auto const cvOpt = ConfigValue{ConfigType::Integer}.optional();
|
TEST_F(ConfigValueTest, defaultValue)
|
||||||
EXPECT_TRUE(cvOpt.isOptional());
|
{
|
||||||
|
auto const cv = ConfigValue{ConfigType::Integer}.defaultValue(123);
|
||||||
|
EXPECT_TRUE(cv.hasValue());
|
||||||
|
EXPECT_FALSE(cv.isOptional());
|
||||||
|
EXPECT_EQ(cv.type(), ConfigType::Integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueDeathTest, invalidDefaultValue)
|
||||||
|
{
|
||||||
|
EXPECT_DEATH({ [[maybe_unused]] auto const a = ConfigValue{ConfigType::String}.defaultValue(33); }, ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, setValueNull)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer};
|
||||||
|
auto const err = cv.setValue(NullType{});
|
||||||
|
EXPECT_TRUE(err.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, setValueNullOptional)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer}.optional();
|
||||||
|
auto const err = cv.setValue(NullType{});
|
||||||
|
EXPECT_FALSE(err.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, setValueNullDefault)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer}.defaultValue(123);
|
||||||
|
auto const err = cv.setValue(NullType{});
|
||||||
|
EXPECT_FALSE(err.has_value());
|
||||||
|
EXPECT_EQ(cv.getValue(), Value{123});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, setValueWrongType)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer};
|
||||||
|
auto const err = cv.setValue("123");
|
||||||
|
EXPECT_TRUE(err.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueTest, setValueNormalPath)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer};
|
||||||
|
auto const err = cv.setValue(123);
|
||||||
|
EXPECT_FALSE(err.has_value());
|
||||||
|
EXPECT_EQ(cv.getValue(), Value{123});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigValueConstraintTest : ConfigValueTest {
|
||||||
|
struct MockConstraint : Constraint {
|
||||||
|
MOCK_METHOD(std::optional<Error>, checkTypeImpl, (Value const&), (const, override));
|
||||||
|
MOCK_METHOD(std::optional<Error>, checkValueImpl, (Value const&), (const, override));
|
||||||
|
MOCK_METHOD(void, print, (std::ostream&), (const, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
testing::StrictMock<MockConstraint> constraint;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ConfigValueConstraintTest, setValueWithConstraint)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer}.withConstraint(constraint);
|
||||||
|
auto const value = Value{123};
|
||||||
|
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
|
||||||
|
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(std::nullopt));
|
||||||
|
auto const err = cv.setValue(value);
|
||||||
|
EXPECT_FALSE(err.has_value());
|
||||||
|
EXPECT_EQ(cv.getValue(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueConstraintTest, setValueWithConstraintTypeCheckError)
|
||||||
|
{
|
||||||
|
auto cv = ConfigValue{ConfigType::Integer}.withConstraint(constraint);
|
||||||
|
auto const value = 123;
|
||||||
|
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(Error{"type error"}));
|
||||||
|
auto const err = cv.setValue(value);
|
||||||
|
EXPECT_TRUE(err.has_value());
|
||||||
|
EXPECT_EQ(err->error, "Unknown_key type error");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ConfigValueConstraintTest, defaultValueWithConstraint)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
|
||||||
|
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(std::nullopt));
|
||||||
|
auto const cv = ConfigValue{ConfigType::Integer}.defaultValue(123).withConstraint(constraint);
|
||||||
|
EXPECT_EQ(cv.getValue(), Value{123});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigValueConstraintDeathTest : ConfigValueConstraintTest {};
|
||||||
|
|
||||||
|
TEST_F(ConfigValueConstraintDeathTest, defaultValueWithConstraintCheckError)
|
||||||
|
{
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
EXPECT_CALL(constraint, checkTypeImpl).WillOnce(testing::Return(std::nullopt));
|
||||||
|
EXPECT_CALL(constraint, checkValueImpl).WillOnce(testing::Return(Error{"value error"}));
|
||||||
|
[[maybe_unused]] auto const cv =
|
||||||
|
ConfigValue{ConfigType::Integer}.defaultValue(123).withConstraint(constraint);
|
||||||
|
},
|
||||||
|
".*"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A test for each constraint so it's easy to change in the future
|
// A test for each constraint so it's easy to change in the future
|
||||||
TEST(ConfigValue, PortConstraint)
|
struct ConstraintTest : NoLoggerFixture {};
|
||||||
|
|
||||||
|
TEST_F(ConstraintTest, PortConstraint)
|
||||||
{
|
{
|
||||||
auto const portConstraint{PortConstraint{}};
|
auto const portConstraint{PortConstraint{}};
|
||||||
EXPECT_FALSE(portConstraint.checkConstraint(4444).has_value());
|
EXPECT_FALSE(portConstraint.checkConstraint(4444).has_value());
|
||||||
EXPECT_TRUE(portConstraint.checkConstraint(99999).has_value());
|
EXPECT_TRUE(portConstraint.checkConstraint(99999).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, SetValuesOnPortConstraint)
|
TEST_F(ConstraintTest, SetValuesOnPortConstraint)
|
||||||
{
|
{
|
||||||
auto cvPort = ConfigValue{ConfigType::Integer}.defaultValue(4444).withConstraint(gValidatePort);
|
auto cvPort = ConfigValue{ConfigType::Integer}.defaultValue(4444).withConstraint(gValidatePort);
|
||||||
auto const err = cvPort.setValue(99999);
|
auto const err = cvPort.setValue(99999);
|
||||||
EXPECT_TRUE(err.has_value());
|
EXPECT_TRUE(err.has_value());
|
||||||
EXPECT_EQ(err->error, "Port does not satisfy the constraint bounds");
|
EXPECT_EQ(err->error, "Unknown_key Port does not satisfy the constraint bounds");
|
||||||
EXPECT_TRUE(cvPort.setValue(33.33).has_value());
|
EXPECT_TRUE(cvPort.setValue(33.33).has_value());
|
||||||
EXPECT_TRUE(cvPort.setValue(33.33).value().error == "value does not match type integer");
|
EXPECT_EQ(cvPort.setValue(33.33).value().error, "Unknown_key value does not match type integer");
|
||||||
EXPECT_FALSE(cvPort.setValue(1).has_value());
|
EXPECT_FALSE(cvPort.setValue(1).has_value());
|
||||||
|
|
||||||
auto cvPort2 = ConfigValue{ConfigType::String}.defaultValue("4444").withConstraint(gValidatePort);
|
auto cvPort2 = ConfigValue{ConfigType::String}.defaultValue("4444").withConstraint(gValidatePort);
|
||||||
auto const strPortError = cvPort2.setValue("100000");
|
auto const strPortError = cvPort2.setValue("100000");
|
||||||
EXPECT_TRUE(strPortError.has_value());
|
EXPECT_TRUE(strPortError.has_value());
|
||||||
EXPECT_EQ(strPortError->error, "Port does not satisfy the constraint bounds");
|
EXPECT_EQ(strPortError->error, "Unknown_key Port does not satisfy the constraint bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, OneOfConstraintOneValue)
|
TEST_F(ConstraintTest, OneOfConstraintOneValue)
|
||||||
{
|
{
|
||||||
std::array<char const*, 1> const arr = {"tracer"};
|
std::array<char const*, 1> const arr = {"tracer"};
|
||||||
auto const databaseConstraint{OneOf{"database.type", arr}};
|
auto const databaseConstraint{OneOf{"database.type", arr}};
|
||||||
@@ -88,7 +199,7 @@ TEST(ConfigValue, OneOfConstraintOneValue)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, OneOfConstraint)
|
TEST_F(ConstraintTest, OneOfConstraint)
|
||||||
{
|
{
|
||||||
std::array<char const*, 3> const arr = {"123", "trace", "haha"};
|
std::array<char const*, 3> const arr = {"123", "trace", "haha"};
|
||||||
auto const oneOfCons{OneOf{"log_level", arr}};
|
auto const oneOfCons{OneOf{"log_level", arr}};
|
||||||
@@ -105,14 +216,14 @@ TEST(ConfigValue, OneOfConstraint)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, IpConstraint)
|
TEST_F(ConstraintTest, IpConstraint)
|
||||||
{
|
{
|
||||||
auto ip = ConfigValue{ConfigType::String}.defaultValue("127.0.0.1").withConstraint(gValidateIp);
|
auto ip = ConfigValue{ConfigType::String}.defaultValue("127.0.0.1").withConstraint(gValidateIp);
|
||||||
EXPECT_FALSE(ip.setValue("http://127.0.0.1").has_value());
|
EXPECT_FALSE(ip.setValue("http://127.0.0.1").has_value());
|
||||||
EXPECT_FALSE(ip.setValue("http://127.0.0.1.com").has_value());
|
EXPECT_FALSE(ip.setValue("http://127.0.0.1.com").has_value());
|
||||||
auto const err = ip.setValue("123.44");
|
auto const err = ip.setValue("123.44");
|
||||||
EXPECT_TRUE(err.has_value());
|
EXPECT_TRUE(err.has_value());
|
||||||
EXPECT_EQ(err->error, "Ip is not a valid ip address");
|
EXPECT_EQ(err->error, "Unknown_key Ip is not a valid ip address");
|
||||||
EXPECT_FALSE(ip.setValue("126.0.0.2"));
|
EXPECT_FALSE(ip.setValue("126.0.0.2"));
|
||||||
|
|
||||||
EXPECT_TRUE(ip.setValue("644.3.3.0"));
|
EXPECT_TRUE(ip.setValue("644.3.3.0"));
|
||||||
@@ -123,7 +234,7 @@ TEST(ConfigValue, IpConstraint)
|
|||||||
EXPECT_FALSE(ip.setValue("http://example.com:8080/path"));
|
EXPECT_FALSE(ip.setValue("http://example.com:8080/path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, positiveNumConstraint)
|
TEST_F(ConstraintTest, positiveNumConstraint)
|
||||||
{
|
{
|
||||||
auto const numCons{NumberValueConstraint{0, 5}};
|
auto const numCons{NumberValueConstraint{0, 5}};
|
||||||
EXPECT_FALSE(numCons.checkConstraint(0));
|
EXPECT_FALSE(numCons.checkConstraint(0));
|
||||||
@@ -136,7 +247,7 @@ TEST(ConfigValue, positiveNumConstraint)
|
|||||||
EXPECT_EQ(numCons.checkConstraint(8)->error, fmt::format("Number must be between {} and {}", 0, 5));
|
EXPECT_EQ(numCons.checkConstraint(8)->error, fmt::format("Number must be between {} and {}", 0, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, SetValuesOnNumberConstraint)
|
TEST_F(ConstraintTest, SetValuesOnNumberConstraint)
|
||||||
{
|
{
|
||||||
auto positiveNum = ConfigValue{ConfigType::Integer}.defaultValue(20u).withConstraint(gValidateUint16);
|
auto positiveNum = ConfigValue{ConfigType::Integer}.defaultValue(20u).withConstraint(gValidateUint16);
|
||||||
auto const err = positiveNum.setValue(-22, "key");
|
auto const err = positiveNum.setValue(-22, "key");
|
||||||
@@ -145,7 +256,7 @@ TEST(ConfigValue, SetValuesOnNumberConstraint)
|
|||||||
EXPECT_FALSE(positiveNum.setValue(99, "key"));
|
EXPECT_FALSE(positiveNum.setValue(99, "key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValue, PositiveDoubleConstraint)
|
TEST_F(ConstraintTest, PositiveDoubleConstraint)
|
||||||
{
|
{
|
||||||
auto const doubleCons{PositiveDouble{}};
|
auto const doubleCons{PositiveDouble{}};
|
||||||
EXPECT_FALSE(doubleCons.checkConstraint(0.2));
|
EXPECT_FALSE(doubleCons.checkConstraint(0.2));
|
||||||
@@ -162,7 +273,7 @@ struct ConstraintTestBundle {
|
|||||||
Constraint const& constraint;
|
Constraint const& constraint;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ConstraintDeathTest : public testing::Test, public testing::WithParamInterface<ConstraintTestBundle> {};
|
struct ConstraintDeathTest : testing::TestWithParam<ConstraintTestBundle> {};
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
EachConstraints,
|
EachConstraints,
|
||||||
@@ -195,7 +306,7 @@ TEST_P(ConstraintDeathTest, TestEachConstraint)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValueDeathTest, SetInvalidValueTypeStringAndBool)
|
TEST(ConstraintDeathTest, SetInvalidValueTypeStringAndBool)
|
||||||
{
|
{
|
||||||
EXPECT_DEATH(
|
EXPECT_DEATH(
|
||||||
{
|
{
|
||||||
@@ -207,7 +318,7 @@ TEST(ConfigValueDeathTest, SetInvalidValueTypeStringAndBool)
|
|||||||
EXPECT_DEATH({ [[maybe_unused]] auto a = ConfigValue{ConfigType::Boolean}.defaultValue(-66); }, ".*");
|
EXPECT_DEATH({ [[maybe_unused]] auto a = ConfigValue{ConfigType::Boolean}.defaultValue(-66); }, ".*");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ConfigValueDeathTest, OutOfBounceIntegerConstraint)
|
TEST(ConstraintDeathTest, OutOfBounceIntegerConstraint)
|
||||||
{
|
{
|
||||||
EXPECT_DEATH(
|
EXPECT_DEATH(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
/*
|
|
||||||
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/TmpFile.hpp"
|
|
||||||
#include "util/newconfig/ConfigFileJson.hpp"
|
|
||||||
#include "util/newconfig/FakeConfigData.hpp"
|
|
||||||
|
|
||||||
#include <boost/json/parse.hpp>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
TEST(CreateConfigFile, filePath)
|
|
||||||
{
|
|
||||||
auto const jsonFileObj = ConfigFileJson::makeConfigFileJson(TmpFile(kJSON_DATA).path);
|
|
||||||
EXPECT_TRUE(jsonFileObj.has_value());
|
|
||||||
|
|
||||||
EXPECT_TRUE(jsonFileObj->containsKey("array.[].sub"));
|
|
||||||
auto const arrSub = jsonFileObj->getArray("array.[].sub");
|
|
||||||
EXPECT_EQ(arrSub.size(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(CreateConfigFile, incorrectFilePath)
|
|
||||||
{
|
|
||||||
auto const jsonFileObj = util::config::ConfigFileJson::makeConfigFileJson("123/clio");
|
|
||||||
EXPECT_FALSE(jsonFileObj.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParseJson : testing::Test {
|
|
||||||
ParseJson() : jsonFileObj{boost::json::parse(kJSON_DATA).as_object()}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigFileJson const jsonFileObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_F(ParseJson, validateValues)
|
|
||||||
{
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("header.text1"));
|
|
||||||
EXPECT_EQ(std::get<std::string>(jsonFileObj.getValue("header.text1")), "value");
|
|
||||||
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("header.sub.sub2Value"));
|
|
||||||
EXPECT_EQ(std::get<std::string>(jsonFileObj.getValue("header.sub.sub2Value")), "TSM");
|
|
||||||
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("dosguard.port"));
|
|
||||||
EXPECT_EQ(std::get<int64_t>(jsonFileObj.getValue("dosguard.port")), 44444);
|
|
||||||
|
|
||||||
EXPECT_FALSE(jsonFileObj.containsKey("idk"));
|
|
||||||
EXPECT_FALSE(jsonFileObj.containsKey("optional.withNoDefault"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ParseJson, validateArrayValue)
|
|
||||||
{
|
|
||||||
// validate array.[].sub matches expected values
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("array.[].sub"));
|
|
||||||
auto const arrSub = jsonFileObj.getArray("array.[].sub");
|
|
||||||
EXPECT_EQ(arrSub.size(), 3);
|
|
||||||
|
|
||||||
std::vector<double> expectedArrSubVal{111.11, 4321.55, 5555.44};
|
|
||||||
std::vector<double> actualArrSubVal{};
|
|
||||||
|
|
||||||
for (auto it = arrSub.begin(); it != arrSub.end(); ++it) {
|
|
||||||
ASSERT_TRUE(std::holds_alternative<double>(*it));
|
|
||||||
actualArrSubVal.emplace_back(std::get<double>(*it));
|
|
||||||
}
|
|
||||||
EXPECT_TRUE(std::ranges::equal(expectedArrSubVal, actualArrSubVal));
|
|
||||||
|
|
||||||
// validate array.[].sub2 matches expected values
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("array.[].sub2"));
|
|
||||||
auto const arrSub2 = jsonFileObj.getArray("array.[].sub2");
|
|
||||||
EXPECT_EQ(arrSub2.size(), 3);
|
|
||||||
std::vector<std::string> expectedArrSub2Val{"subCategory", "temporary", "london"};
|
|
||||||
std::vector<std::string> actualArrSub2Val{};
|
|
||||||
|
|
||||||
for (auto it = arrSub2.begin(); it != arrSub2.end(); ++it) {
|
|
||||||
ASSERT_TRUE(std::holds_alternative<std::string>(*it));
|
|
||||||
actualArrSub2Val.emplace_back(std::get<std::string>(*it));
|
|
||||||
}
|
|
||||||
EXPECT_TRUE(std::ranges::equal(expectedArrSub2Val, actualArrSub2Val));
|
|
||||||
|
|
||||||
EXPECT_TRUE(jsonFileObj.containsKey("dosguard.whitelist.[]"));
|
|
||||||
auto const whitelistArr = jsonFileObj.getArray("dosguard.whitelist.[]");
|
|
||||||
EXPECT_EQ(whitelistArr.size(), 2);
|
|
||||||
EXPECT_EQ("125.5.5.1", std::get<std::string>(whitelistArr.at(0)));
|
|
||||||
EXPECT_EQ("204.2.2.1", std::get<std::string>(whitelistArr.at(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct JsonValueDeathTest : ParseJson {};
|
|
||||||
|
|
||||||
TEST_F(JsonValueDeathTest, invalidGetArray)
|
|
||||||
{
|
|
||||||
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("header.text1"), ".*");
|
|
||||||
}
|
|
||||||
@@ -89,9 +89,9 @@ TEST_F(JsonFromTempFile, validateArrayValue)
|
|||||||
EXPECT_EQ("204.2.2.1", std::get<std::string>(whitelistArr.at(1)));
|
EXPECT_EQ("204.2.2.1", std::get<std::string>(whitelistArr.at(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JsonValueDeathTest : JsonFromTempFile {};
|
struct ConfigValueJsonGetArrayDeathTest : JsonFromTempFile {};
|
||||||
|
|
||||||
TEST_F(JsonValueDeathTest, invalidGetValues)
|
TEST_F(ConfigValueJsonGetArrayDeathTest, invalidGetValues)
|
||||||
{
|
{
|
||||||
// not possible for json value to call a value that doesn't exist
|
// not possible for json value to call a value that doesn't exist
|
||||||
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("header.text1"), ".*");
|
EXPECT_DEATH([[maybe_unused]] auto a = jsonFileObj.getArray("header.text1"), ".*");
|
||||||
|
|||||||
Reference in New Issue
Block a user