feat(loading): robust error reporting and mutation
cfg now reports which fields are missing when loading. Further, the mutate method has been added for easier mutation of the underlying configuration struct
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
#
|
#
|
||||||
# *********************************************************************** #
|
# *********************************************************************** #
|
||||||
project('libconfig', ['cpp', 'c'], version: 'v2.1.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
|
project('libconfig', ['cpp', 'c'], version: 'v2.2.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
|
||||||
|
|
||||||
# Add default visibility for all C++ targets
|
# Add default visibility for all C++ targets
|
||||||
add_project_arguments('-fvisibility=default', language: 'cpp')
|
add_project_arguments('-fvisibility=default', language: 'cpp')
|
||||||
|
|||||||
28
readme.md
28
readme.md
@@ -33,10 +33,12 @@ and strongly typed.
|
|||||||
#include "fourdst/config/config.h"
|
#include "fourdst/config/config.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <print>
|
#include <print>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
struct MyPhysicsOptions {
|
struct MyPhysicsOptions {
|
||||||
int gravity = 10;
|
int gravity = 10;
|
||||||
float friction = 0.5f;
|
float friction = 0.5f;
|
||||||
|
std::optional<float> dampening = 0.1f;
|
||||||
bool enable_wind = false;
|
bool enable_wind = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,12 +66,38 @@ int main() {
|
|||||||
// You can load a config from a file
|
// You can load a config from a file
|
||||||
try {
|
try {
|
||||||
cfg.load("my_config.toml");
|
cfg.load("my_config.toml");
|
||||||
|
// You can also pass an optional bool as a second argument to turn on verbose error
|
||||||
|
// reporting. This will display a tree of missing or invalid fields. Note that due to limitations
|
||||||
|
// in C++'s ability to detect default iniailized values vs initializer list values
|
||||||
|
// missing fields which you set an initializer list for are still considered missing.
|
||||||
|
// **ONLY fields marked with std::optional are exempt from this rule.**
|
||||||
} catch (const fourdst::config::exceptions::ConfigError& e) {
|
} catch (const fourdst::config::exceptions::ConfigError& e) {
|
||||||
std::println("Error loading config: {}", e.what());
|
std::println("Error loading config: {}", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can access the config values
|
// You can access the config values
|
||||||
std::println("My Simulation Name: {}, My Simulation Gravity: {}", cfg->name, cfg->physics.gravity);
|
std::println("My Simulation Name: {}, My Simulation Gravity: {}", cfg->name, cfg->physics.gravity);
|
||||||
|
|
||||||
|
// libconfig intentioanlly discourages direct modification of config values. However, if you need
|
||||||
|
// to modify values after loading them you can use the mutate function. This takes a lambda
|
||||||
|
// which recives a mutable reference to the underlying config struct.
|
||||||
|
|
||||||
|
cfg.mutate([](MySimulationConfig& config) {
|
||||||
|
config->physics.enable_wind = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Making these mutations will put the config into a "MODIFIED" state and will cache the unmodified values.
|
||||||
|
// You can reset the state and revert to the unmodified values with the reset function.
|
||||||
|
|
||||||
|
cfg.reset();
|
||||||
|
|
||||||
|
// The current state of the config can be checked with the get_state() function or the describe_state() function.
|
||||||
|
// get_state returns an enum value while describe_state returns a human readable string.
|
||||||
|
|
||||||
|
std::println("Config State: {}", cfg.describe_state());
|
||||||
|
fourdst::config::ConfigState state = cfg.get_state();
|
||||||
|
|
||||||
|
// Possible states are DEFAULT, LOADED_FROM_FILE, and MODIFIED
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
62
src/config/include/fourdst/config/ansi.h
Normal file
62
src/config/include/fourdst/config/ansi.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#define ISATTY _isatty
|
||||||
|
#define FILENO _fileno
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#define ISATTY isatty
|
||||||
|
#define FILENO fileno
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fourdst::config::utils {
|
||||||
|
|
||||||
|
bool supports_ansi_colors() {
|
||||||
|
if (std::getenv("NO_COLOR")) return false;
|
||||||
|
if (std::getenv("FORCE_COLOR")) return true;
|
||||||
|
|
||||||
|
if (!ISATTY(FILENO(stdout))) return false;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
if (hOut == INVALID_HANDLE_VALUE) return false;
|
||||||
|
DWORD dwMode = 0;
|
||||||
|
if (!GetConsoleMode(hOut, &dwMode)) return false;
|
||||||
|
return (dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
|
||||||
|
#else
|
||||||
|
const char* term = std::getenv("TERM");
|
||||||
|
if (!term) return false;
|
||||||
|
const std::string term_str(term);
|
||||||
|
return term_str != "dumb";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const static bool TERM_COLOR_SUPPORT = supports_ansi_colors();
|
||||||
|
|
||||||
|
class ANSIColor {
|
||||||
|
public:
|
||||||
|
explicit ANSIColor(const std::string& value) : m_value(value) {}
|
||||||
|
|
||||||
|
std::string_view get() const {
|
||||||
|
if (TERM_COLOR_SUPPORT) {
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string m_value{""};
|
||||||
|
ANSIColor() = default;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static ANSIColor RED{"\033[31m"};
|
||||||
|
static ANSIColor GREEN{"\033[32m"};
|
||||||
|
static ANSIColor BLUE{"\033[34m"};
|
||||||
|
static ANSIColor CYAN{"\033[36m"};
|
||||||
|
static ANSIColor RESET{"\033[0m"};
|
||||||
|
}
|
||||||
@@ -14,8 +14,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "fourdst/config/exceptions/exceptions.h"
|
#include "fourdst/config/exceptions/exceptions.h"
|
||||||
|
#include "fourdst/config/validate.h"
|
||||||
|
|
||||||
#include "rfl.hpp"
|
#include "rfl.hpp"
|
||||||
#include "rfl/toml.hpp"
|
#include "rfl/toml.hpp"
|
||||||
@@ -65,7 +67,9 @@ namespace fourdst::config {
|
|||||||
/**
|
/**
|
||||||
* @brief Configuration has been successfully populated from a file.
|
* @brief Configuration has been successfully populated from a file.
|
||||||
*/
|
*/
|
||||||
LOADED_FROM_FILE
|
LOADED_FROM_FILE,
|
||||||
|
|
||||||
|
MODIFIED
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -127,7 +131,7 @@ namespace fourdst::config {
|
|||||||
* @brief Get a mutable pointer to the configuration content.
|
* @brief Get a mutable pointer to the configuration content.
|
||||||
* @return Pointer to the mutable configuration content.
|
* @return Pointer to the mutable configuration content.
|
||||||
*/
|
*/
|
||||||
T* write() const { return &m_content; }
|
[[deprecated("write has been depreceated for mutate() and will be removed in versions >= v3.0.0")]] T* write() const { return &m_content; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Dereference operator to access the underlying configuration struct.
|
* @brief Dereference operator to access the underlying configuration struct.
|
||||||
@@ -246,7 +250,7 @@ namespace fourdst::config {
|
|||||||
* }
|
* }
|
||||||
* @endcode
|
* @endcode
|
||||||
*/
|
*/
|
||||||
void load(const std::string_view path) {
|
void load(const std::string_view path, const bool verbose = false) {
|
||||||
if (m_state == ConfigState::LOADED_FROM_FILE) {
|
if (m_state == ConfigState::LOADED_FROM_FILE) {
|
||||||
throw exceptions::ConfigLoadError(
|
throw exceptions::ConfigLoadError(
|
||||||
"Config has already been loaded from file. Reloading is not supported.");
|
"Config has already been loaded from file. Reloading is not supported.");
|
||||||
@@ -261,8 +265,32 @@ namespace fourdst::config {
|
|||||||
const rfl::Result<wrapper> result = rfl::toml::load<wrapper>(std::string(path));
|
const rfl::Result<wrapper> result = rfl::toml::load<wrapper>(std::string(path));
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
std::vector<std::string> missing_fields;
|
||||||
|
|
||||||
|
try {
|
||||||
|
toml::table root_tbl = toml::parse_file(std::string(path));
|
||||||
|
|
||||||
|
if (!root_tbl.empty()) {
|
||||||
|
const auto loaded_root_name = std::string(root_tbl.begin()->first);
|
||||||
|
const toml::table* t_tbl = root_tbl[loaded_root_name].as_table();
|
||||||
|
|
||||||
|
if (t_tbl) {
|
||||||
|
validate::ConfigValidator<T>::check(t_tbl, loaded_root_name, missing_fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const toml::parse_error&) {
|
||||||
|
throw exceptions::ConfigParseError("Unable to parse TOML file for an unknown reason. This normally means the toml file is empty or completely malformed. Please check the file content and ensure it is valid TOML. If the file is empty, consider adding at least an empty table (e.g., [main]) to it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!missing_fields.empty() && verbose) {
|
||||||
|
std::cerr << validate::report_all_missing_fields(missing_fields) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
throw exceptions::ConfigParseError(
|
throw exceptions::ConfigParseError(
|
||||||
std::format("Failed to load config from file: {}", path));
|
std::format("Failed to load config from file: {}. Reason: {}",
|
||||||
|
path,
|
||||||
|
result.error().what())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -328,13 +356,35 @@ namespace fourdst::config {
|
|||||||
return "DEFAULT";
|
return "DEFAULT";
|
||||||
case ConfigState::LOADED_FROM_FILE:
|
case ConfigState::LOADED_FROM_FILE:
|
||||||
return "LOADED_FROM_FILE";
|
return "LOADED_FROM_FILE";
|
||||||
|
case ConfigState::MODIFIED:
|
||||||
|
return "MODIFIED";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename MutatorFunc>
|
||||||
|
void mutate(MutatorFunc&& mutator) {
|
||||||
|
m_content_mutex.lock();
|
||||||
|
m_content_orig = m_content;
|
||||||
|
mutator(m_content);
|
||||||
|
m_state = ConfigState::MODIFIED;
|
||||||
|
m_content_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
m_content_mutex.lock();
|
||||||
|
if (m_state == ConfigState::MODIFIED) {
|
||||||
|
m_content = m_content_orig;
|
||||||
|
m_state = ConfigState::LOADED_FROM_FILE;
|
||||||
|
}
|
||||||
|
m_content_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T m_content;
|
T m_content;
|
||||||
|
T m_content_orig;
|
||||||
|
std::mutex m_content_mutex;
|
||||||
std::string m_root_name = "main";
|
std::string m_root_name = "main";
|
||||||
ConfigState m_state = ConfigState::DEFAULT;
|
ConfigState m_state = ConfigState::DEFAULT;
|
||||||
RootNameLoadPolicy m_root_name_load_policy = RootNameLoadPolicy::KEEP_CURRENT;
|
RootNameLoadPolicy m_root_name_load_policy = RootNameLoadPolicy::KEEP_CURRENT;
|
||||||
|
|||||||
157
src/config/include/fourdst/config/validate.h
Normal file
157
src/config/include/fourdst/config/validate.h
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fourdst/config/ansi.h"
|
||||||
|
|
||||||
|
#include <rfl.hpp>
|
||||||
|
#include <toml++/toml.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <optional>
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace fourdst::config::validate {
|
||||||
|
|
||||||
|
template <typename T> struct is_optional_impl : std::false_type {};
|
||||||
|
template <typename T> struct is_optional_impl<std::optional<T>> : std::true_type {};
|
||||||
|
template <typename Type> constexpr bool is_optional_v = is_optional_impl<std::remove_cvref_t<Type>>::value;
|
||||||
|
|
||||||
|
template <typename T> struct is_vector_impl : std::false_type {};
|
||||||
|
template <typename T, typename A> struct is_vector_impl<std::vector<T, A>> : std::true_type {};
|
||||||
|
template <typename Type> constexpr bool is_vector_v = is_vector_impl<std::remove_cvref_t<Type>>::value;
|
||||||
|
|
||||||
|
template <typename T> struct is_map_impl : std::false_type {};
|
||||||
|
template <typename K, typename V, typename C, typename A> struct is_map_impl<std::map<K, V, C, A>> : std::true_type {};
|
||||||
|
template <typename K, typename V, typename H, typename E, typename A> struct is_map_impl<std::unordered_map<K, V, H, E, A>> : std::true_type {};
|
||||||
|
template <typename Type> constexpr bool is_map_v = is_map_impl<std::remove_cvref_t<Type>>::value;
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
constexpr bool is_string_like_v = std::is_same_v<std::remove_cvref_t<Type>, std::string> ||
|
||||||
|
std::is_same_v<std::remove_cvref_t<Type>, std::string_view>;
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
constexpr bool is_reflectable_struct_v = std::is_class_v<std::remove_cvref_t<Type>> &&
|
||||||
|
!is_string_like_v<Type> &&
|
||||||
|
!is_vector_v<Type> &&
|
||||||
|
!is_optional_v<Type> &&
|
||||||
|
!is_map_v<Type>;
|
||||||
|
|
||||||
|
template <typename StructType>
|
||||||
|
struct ConfigValidator {
|
||||||
|
using NT = rfl::named_tuple_t<StructType>;
|
||||||
|
|
||||||
|
static void check(const toml::table* tbl, const std::string& current_path, std::vector<std::string>& missing) {
|
||||||
|
if (!tbl) return;
|
||||||
|
check_tuple<NT>(tbl, current_path, missing);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename Tuple>
|
||||||
|
struct TupleChecker;
|
||||||
|
|
||||||
|
template <typename... Fields>
|
||||||
|
struct TupleChecker<rfl::NamedTuple<Fields...>> {
|
||||||
|
static void check(const toml::table* tbl, const std::string& path, std::vector<std::string>& missing) {
|
||||||
|
(check_field<Fields>(tbl, path, missing), ...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TupleType>
|
||||||
|
static void check_tuple(const toml::table* tbl, const std::string& path, std::vector<std::string>& missing) {
|
||||||
|
TupleChecker<TupleType>::check(tbl, path, missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Field>
|
||||||
|
static void check_field(const toml::table* tbl, const std::string& path, std::vector<std::string>& missing) {
|
||||||
|
std::string_view name_sv = Field::name();
|
||||||
|
std::string name(name_sv);
|
||||||
|
|
||||||
|
using RawType = typename Field::Type;
|
||||||
|
using Type = std::remove_cvref_t<RawType>;
|
||||||
|
|
||||||
|
std::string full_path = path.empty() ? name : path + "." + name;
|
||||||
|
const toml::node* node = tbl->get(name_sv);
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
if constexpr (!is_optional_v<Type>) {
|
||||||
|
missing.push_back(full_path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr (is_reflectable_struct_v<Type>) {
|
||||||
|
if (node->is_table()) {
|
||||||
|
ConfigValidator<Type>::check(node->as_table(), full_path, missing);
|
||||||
|
}
|
||||||
|
} else if constexpr (is_vector_v<Type>) {
|
||||||
|
using ElementType = std::remove_cvref_t<typename Type::value_type>;
|
||||||
|
if constexpr (is_reflectable_struct_v<ElementType>) {
|
||||||
|
if (node->is_array()) {
|
||||||
|
const auto& arr = *node->as_array();
|
||||||
|
for (size_t i = 0; i < arr.size(); ++i) {
|
||||||
|
if (arr.get(i)->is_table()) {
|
||||||
|
ConfigValidator<ElementType>::check(
|
||||||
|
arr.get(i)->as_table(),
|
||||||
|
std::format("{}[{}]", full_path, i),
|
||||||
|
missing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if constexpr (is_optional_v<Type>) {
|
||||||
|
using InnerType = std::remove_cvref_t<typename Type::value_type>;
|
||||||
|
if constexpr (is_reflectable_struct_v<InnerType>) {
|
||||||
|
if (node->is_table()) {
|
||||||
|
ConfigValidator<InnerType>::check(node->as_table(), full_path, missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MissingFieldTree {
|
||||||
|
std::map<std::string, MissingFieldTree> children;
|
||||||
|
bool is_missing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void print_missing_field_tree(const MissingFieldTree& tree, std::string indent, bool is_last, const std::string& name, std::string& output) {
|
||||||
|
if (!name.empty()) {
|
||||||
|
std::string display_name = tree.is_missing ? std::format("{}{}{}", utils::RED.get(), name, utils::RESET.get()) : name;
|
||||||
|
output += std::format("{}{} {}\n", indent, is_last ? "└──" : "├──", display_name);
|
||||||
|
indent += is_last ? " " : "│ ";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto const& [child_name, child_tree] : tree.children) {
|
||||||
|
print_missing_field_tree(child_tree, indent, count == tree.children.size() - 1, child_name, output);
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string report_all_missing_fields(const std::vector<std::string>& missing) {
|
||||||
|
if (missing.empty()) return "";
|
||||||
|
|
||||||
|
MissingFieldTree root;
|
||||||
|
for (const auto& path : missing) {
|
||||||
|
std::stringstream ss(path);
|
||||||
|
std::string part;
|
||||||
|
MissingFieldTree* current = &root;
|
||||||
|
while (std::getline(ss, part, '.')) {
|
||||||
|
current = ¤t->children[part];
|
||||||
|
}
|
||||||
|
current->is_missing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string output = "\nConfiguration Missing Field Path(s):\n";
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto const& [name, child_tree] : root.children) {
|
||||||
|
print_missing_field_tree(child_tree, "", count == root.children.size() - 1, name, output);
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,20 @@ std::string get_good_example_file() {
|
|||||||
return std::string(source_root) + "/tests/config/example_config_files/example.good.toml";
|
return std::string(source_root) + "/tests/config/example_config_files/example.good.toml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_good_missing_keys_example_file() {
|
||||||
|
const char* source_root = getenv("MESON_SOURCE_ROOT");
|
||||||
|
if (source_root == nullptr) {
|
||||||
|
throw std::runtime_error("MESON_SOURCE_ROOT environment variable is not set.");
|
||||||
|
}
|
||||||
|
return std::string(source_root) + "/tests/config/example_config_files/example.good.missing.field.toml";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
enum class BAD_FILES {
|
enum class BAD_FILES {
|
||||||
UNKNOWN_KEY,
|
UNKNOWN_KEY,
|
||||||
INVALID_TYPE,
|
INVALID_TYPE,
|
||||||
INCORRECT_ARRAY_SIZE
|
INCORRECT_ARRAY_SIZE,
|
||||||
|
MISSING_NONDEFAULT_KEY
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string get_bad_example_file(BAD_FILES type) {
|
std::string get_bad_example_file(BAD_FILES type) {
|
||||||
@@ -37,6 +46,8 @@ std::string get_bad_example_file(BAD_FILES type) {
|
|||||||
return std::string(source_root) + "/tests/config/example_config_files/example.invalidtype.toml";
|
return std::string(source_root) + "/tests/config/example_config_files/example.invalidtype.toml";
|
||||||
case BAD_FILES::INCORRECT_ARRAY_SIZE:
|
case BAD_FILES::INCORRECT_ARRAY_SIZE:
|
||||||
return std::string(source_root) + "/tests/config/example_config_files/example.incorrectarraysize.toml";
|
return std::string(source_root) + "/tests/config/example_config_files/example.incorrectarraysize.toml";
|
||||||
|
case BAD_FILES::MISSING_NONDEFAULT_KEY:
|
||||||
|
return std::string(source_root) + "/tests/config/example_config_files/example.missing.nondefault.field.toml";
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Invalid BAD_FILES type.");
|
throw std::runtime_error("Invalid BAD_FILES type.");
|
||||||
}
|
}
|
||||||
@@ -131,3 +142,36 @@ TEST_F(configTest, save_schema) {
|
|||||||
Config<TestConfigSchema> cfg;
|
Config<TestConfigSchema> cfg;
|
||||||
EXPECT_NO_THROW(cfg.save_schema("TestConfigSchema.schema.json"));
|
EXPECT_NO_THROW(cfg.save_schema("TestConfigSchema.schema.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(configTest, missing_default_keys) {
|
||||||
|
using namespace fourdst::config;
|
||||||
|
Config<TestConfigSchema> cfg;
|
||||||
|
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(configTest, missing_nondefault_keys) {
|
||||||
|
using namespace fourdst::config;
|
||||||
|
Config<TestConfigSchema> cfg;
|
||||||
|
EXPECT_THROW(cfg.load(get_bad_example_file(BAD_FILES::MISSING_NONDEFAULT_KEY)), exceptions::ConfigParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(configTest, mutate_and_reset) {
|
||||||
|
using namespace fourdst::config;
|
||||||
|
Config<TestConfigSchema> cfg;
|
||||||
|
EXPECT_NO_THROW(cfg.load(get_good_example_file()));
|
||||||
|
|
||||||
|
EXPECT_TRUE(cfg->physics.diffusion);
|
||||||
|
EXPECT_EQ(cfg.get_state(), ConfigState::LOADED_FROM_FILE);
|
||||||
|
EXPECT_NO_THROW(
|
||||||
|
cfg.mutate([](auto& data) {
|
||||||
|
data.physics.diffusion = false;
|
||||||
|
});
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(cfg->physics.diffusion);
|
||||||
|
EXPECT_EQ(cfg.get_state(), ConfigState::MODIFIED);
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(cfg.reset());
|
||||||
|
EXPECT_TRUE(cfg->physics.diffusion);
|
||||||
|
EXPECT_EQ(cfg.get_state(), ConfigState::LOADED_FROM_FILE);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[main]
|
||||||
|
description = "This is an example configuration file."
|
||||||
|
author = "Example Author"
|
||||||
|
|
||||||
|
[main.physics]
|
||||||
|
diffusion = true
|
||||||
|
convection = false
|
||||||
|
radiation = true
|
||||||
|
flags = [1, 0, 1]
|
||||||
|
|
||||||
|
[main.simulation]
|
||||||
|
output_frequency = 10
|
||||||
|
|
||||||
|
[main.output]
|
||||||
|
format = "csv"
|
||||||
|
directory = "results/"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[main]
|
||||||
|
description = "This is an example configuration file."
|
||||||
|
author = "Example Author"
|
||||||
|
|
||||||
|
[main.physics]
|
||||||
|
flags = [1, 0, 1]
|
||||||
|
|
||||||
|
[main.simulation]
|
||||||
|
time_step = 0.01
|
||||||
|
total_time = 100.0
|
||||||
|
output_frequency = 10
|
||||||
|
|
||||||
|
[main.output]
|
||||||
|
format = "csv"
|
||||||
|
directory = "results/"
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
struct PhysicsConfigOptions {
|
struct PhysicsConfigOptions {
|
||||||
bool diffusion;
|
bool diffusion;
|
||||||
bool convection;
|
std::optional<bool> convection;
|
||||||
bool radiation;
|
std::optional<bool> radiation;
|
||||||
std::array<int, 3> flags;
|
std::array<int, 3> flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ struct SimulationConfigOptions {
|
|||||||
struct OutputConfigOptions {
|
struct OutputConfigOptions {
|
||||||
std::string directory = "./output";
|
std::string directory = "./output";
|
||||||
std::string format = "hdf5";
|
std::string format = "hdf5";
|
||||||
bool save_plots = false;
|
std::optional<bool> save_plots = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TestConfigSchema {
|
struct TestConfigSchema {
|
||||||
|
|||||||
Reference in New Issue
Block a user