-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add YAML-to-struct conversion utilities and improve logging setup
- Added YAML converters for `ModelSettings`, `PopulationDemographic`, `TransmissionSettings`, and `date::year_month_day` structs. - Introduced `ConfigData` struct to group configuration data sections. - Updated `Config` class to load, reload, and notify observers for configuration changes using the new `ConfigData` structure. - Integrated `sol2` and `Lua` for dynamic configuration validation. - Added `Logger` class for initializing loggers using `spdlog`, with a dedicated logger for `ConfigValidator` and network operations. - Updated CMakeLists to include dependencies: Sol2, Lua, spdlog, and date libraries. - Added unit tests for YAML converters, including date and configuration-related structs, using Google Test. - Removed example tests and added relevant configuration tests. - Added prompts for generating GTest test cases and YAML struct converters for `ModelSettings`.
- Loading branch information
Showing
23 changed files
with
942 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
#include "ConfigValidator.h" | ||
|
||
#include <iostream> | ||
|
||
#include "Config.h" | ||
|
||
bool ConfigValidator::Validate(const ConfigData &config) { | ||
try { | ||
// Add more validations as needed | ||
return true; | ||
} catch (const std::exception &e) { | ||
std::cerr << "Validation failed: " << e.what() << std::endl; | ||
return false; | ||
} | ||
} | ||
|
||
bool ConfigValidator::ValidateAgainstSchema(const ConfigData &config, | ||
const YAML::Node &schema) { | ||
// Implement dynamic validation logic here (e.g., using Lua or schema-based | ||
// validation) | ||
return true; // Placeholder | ||
} | ||
|
||
// Recursive function to convert YAML node to Lua table using Sol2 | ||
sol::table ConfigValidator::PushYamlToLua(sol::state &lua, | ||
const YAML::Node &node) { | ||
sol::table lua_table = lua.create_table(); | ||
|
||
for (auto it = node.begin(); it != node.end(); ++it) { | ||
std::string key; | ||
try { | ||
key = it->first.as<std::string>(); | ||
} catch (const YAML::BadConversion &e) { | ||
// Handle invalid key conversion | ||
key = "invalid_key"; // Or handle appropriately | ||
} | ||
|
||
const YAML::Node &value = it->second; | ||
|
||
try { | ||
if (value.IsScalar()) { | ||
if (value.Tag() == "tag:yaml.org,2002:int") { | ||
lua_table[key] = value.as<int>(); | ||
} else if (value.Tag() == "tag:yaml.org,2002:float") { | ||
lua_table[key] = value.as<double>(); | ||
} else if (value.Tag() == "tag:yaml.org,2002:bool") { | ||
lua_table[key] = value.as<bool>(); | ||
} else if (value.IsNull()) { | ||
lua_table[key] = sol::lua_nil; | ||
} else { | ||
lua_table[key] = value.as<std::string>(); | ||
} | ||
} else if (value.IsMap()) { | ||
lua_table[key] = PushYamlToLua(lua, value); | ||
} else if (value.IsSequence()) { | ||
sol::table array_table = lua.create_table(); | ||
int index = 1; | ||
for (const auto &element : value) { | ||
if (element.IsScalar()) { | ||
if (element.Tag() == "tag:yaml.org,2002:int") { | ||
array_table[index++] = element.as<int>(); | ||
} else if (element.Tag() == "tag:yaml.org,2002:float") { | ||
array_table[index++] = element.as<double>(); | ||
} else if (element.Tag() == "tag:yaml.org,2002:bool") { | ||
array_table[index++] = element.as<bool>(); | ||
} else if (element.IsNull()) { | ||
array_table[index++] = sol::lua_nil; | ||
} else { | ||
array_table[index++] = element.as<std::string>(); | ||
} | ||
} else { | ||
array_table[index++] = PushYamlToLua(lua, element); | ||
} | ||
} | ||
lua_table[key] = array_table; | ||
} | ||
} catch (const YAML::BadConversion &e) { | ||
// Handle conversion error, possibly logging and setting Lua to nil or a | ||
// default value | ||
lua_table[key] = sol::lua_nil; | ||
} | ||
} | ||
|
||
return lua_table; | ||
} | ||
|
||
// Load YAML configuration into Lua | ||
void ConfigValidator::LoadConfigToLua(sol::state &lua, | ||
const YAML::Node &config) { | ||
sol::table lua_config = PushYamlToLua(lua, config); | ||
lua["config"] = lua_config; | ||
// Debugging: Print Lua table contents | ||
std::cout << "Lua 'config' table contents:\n"; | ||
for (const auto &pair : lua_config) { | ||
std::string key = pair.first.as<std::string>(); | ||
sol::object value = pair.second; | ||
std::cout << key << " = "; | ||
switch (value.get_type()) { | ||
case sol::type::lua_nil: | ||
std::cout << "nil"; | ||
break; | ||
case sol::type::boolean: | ||
std::cout << (value.as<bool>() ? "true" : "false"); | ||
break; | ||
case sol::type::number: | ||
std::cout << value.as<double>(); | ||
break; | ||
case sol::type::string: | ||
std::cout << "\"" << value.as<std::string>() << "\""; | ||
break; | ||
case sol::type::table: | ||
std::cout << "table"; | ||
break; | ||
default: | ||
std::cout << "other"; | ||
break; | ||
} | ||
std::cout << "\n"; | ||
} | ||
} | ||
|
||
// Example validation function using Lua for dynamic rules | ||
bool ConfigValidator::ValidateAgainstLua(const ConfigData &config, | ||
const YAML::Node &schema, | ||
sol::state &lua) { | ||
LoadConfigToLua(lua, schema); | ||
|
||
// You can define or load Lua conditions here based on the schema | ||
std::string condition = R"( | ||
if physics.gravity > 9.8 and simulation.duration > 1000 then | ||
return false | ||
else | ||
return true | ||
end | ||
)"; | ||
|
||
sol::protected_function_result result = | ||
lua.safe_script(condition, sol::script_pass_on_error); | ||
if (!result.valid()) { | ||
sol::error err = result; | ||
std::cerr << "Lua validation error: " << err.what() << std::endl; | ||
return false; | ||
} | ||
|
||
return result; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// ConfigValidator.h | ||
#ifndef CONFIGVALIDATOR_H | ||
#define CONFIGVALIDATOR_H | ||
|
||
#include <yaml-cpp/yaml.h> | ||
|
||
#include <sol/sol.hpp> | ||
|
||
class ConfigData; | ||
|
||
class ConfigValidator { | ||
public: | ||
ConfigValidator() = default; | ||
~ConfigValidator() = default; | ||
|
||
// Prevent copying and moving | ||
ConfigValidator(const ConfigValidator &) = delete; | ||
ConfigValidator(ConfigValidator &&) = delete; | ||
ConfigValidator &operator=(const ConfigValidator &) = delete; | ||
ConfigValidator &operator=(ConfigValidator &&) = delete; | ||
|
||
// Validate the config data | ||
bool Validate(const ConfigData &config); | ||
|
||
bool ValidateAgainstLua(const ConfigData &config, const YAML::Node &schema, | ||
sol::state &lua); | ||
|
||
// Optionally, validate against YAML schema rules | ||
bool ValidateAgainstSchema(const ConfigData &config, | ||
const YAML::Node &schema); | ||
|
||
// Helper method to load entire config into Lua | ||
void LoadConfigToLua(sol::state &lua, const YAML::Node &config); | ||
|
||
// Helper method to convert YAML to Lua tables | ||
sol::table PushYamlToLua(sol::state &lua, const YAML::Node &node); | ||
|
||
private: | ||
// Helper functions for different validation rules | ||
// void ValidateTimestep(double timestep); | ||
// void ValidateGravity(double gravity); | ||
// Other validation logic for fields | ||
}; | ||
|
||
#endif // CONFIGVALIDATOR_H | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#include "Configuration/ConfigValidator.h" | ||
|
||
#include <gtest/gtest.h> | ||
#include <spdlog/spdlog.h> | ||
|
||
class ConfigValidatorTest : public ::testing::Test { | ||
protected: | ||
ConfigValidator validator; | ||
|
||
void SetUp() override { | ||
// Optional: Initialize any shared resources | ||
} | ||
void TearDown() override { | ||
// Optional: Clean up any shared resources | ||
} | ||
}; | ||
|
||
TEST_F(ConfigValidatorTest, Validate) { | ||
YAML::Node node = YAML::Load("{ settings: { pi: 3.14159}, alpha: 0.5 }"); | ||
// YAML::Emitter out; | ||
// out << node; | ||
// spdlog::info("node: {}", out.c_str()); | ||
|
||
// Create a Lua state and load Sol2's standard libraries | ||
sol::state lua; | ||
lua.open_libraries(sol::lib::base, sol::lib::package); | ||
|
||
validator.LoadConfigToLua(lua, node); | ||
|
||
sol::table config = lua["config"]; | ||
|
||
sol::table settings = config["settings"]; | ||
|
||
EXPECT_DOUBLE_EQ(settings.get<double>("pi"), 3.14159); | ||
|
||
EXPECT_DOUBLE_EQ(config["alpha"], 0.5); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
``` | ||
As an expert in C++ programming, would you mind help me to write a gtest for the following function. | ||
Suggest me a test file name and using Test Fixture class. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
``` | ||
Here is the example of using yaml-cpp to convert yaml to Vec3 class | ||
namespace YAML { | ||
template<> | ||
struct convert<Vec3> { | ||
static Node encode(const Vec3& rhs) { | ||
Node node; | ||
node.push_back(rhs.x); | ||
node.push_back(rhs.y); | ||
node.push_back(rhs.z); | ||
return node; | ||
} | ||
static bool decode(const Node& node, Vec3& rhs) { | ||
if(!node.IsSequence() || node.size() != 3) { | ||
return false; | ||
} | ||
rhs.x = node[0].as<double>(); | ||
rhs.y = node[1].as<double>(); | ||
rhs.z = node[2].as<double>(); | ||
return true; | ||
} | ||
}; | ||
} | ||
Then you could use Vec3 wherever you could use any other type: | ||
YAML::Node node = YAML::Load("start: [1, 3, 0]"); | ||
Vec3 v = node["start"].as<Vec3>(); | ||
node["end"] = Vec3(2, -1, 0); | ||
As an expert in C++ developer, would you mind make the similar convert function for the following struct: | ||
struct ModelSettings { | ||
int days_between_stdout_output; // Frequency of stdout output, in days | ||
int initial_seed_number; // Seed for random number generator | ||
bool record_genome_db; // Flag to record genomic data | ||
date::year_month_day starting_date; // Simulation start date (YYYY/MM/DD) | ||
date::year_month_day | ||
start_of_comparison_period; // Start of comparison period (YYYY/MM/DD) | ||
date::year_month_day ending_date; // Simulation end date (YYYY/MM/DD) | ||
int start_collect_data_day; // Day to start collecting data | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
``` | ||
As an expret in C++ programming, would you mind help me to convert the yaml which is from the input file to C++ struct. | ||
For date type, you can use the date library from HowardHinnant. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,32 @@ | ||
|
||
#include "Config.h" | ||
|
||
#include <iostream> | ||
|
||
bool Config::ValidateNode(const YAML::Node &node, const YAML::Node &schema) { | ||
for (auto it = schema.begin(); it != schema.end(); ++it) { | ||
std::string key = it->first.as<std::string>(); | ||
const YAML::Node &schema_field = it->second; | ||
|
||
// Check if the field is required and present | ||
if (schema_field["required"] && schema_field["required"].as<bool>() | ||
&& !node[key]) { | ||
std::cerr << "Missing required field: " << key << std::endl; | ||
return false; | ||
} | ||
|
||
// If the field exists, check the type | ||
if (node[key]) { | ||
std::string expected_type = schema_field["type"].as<std::string>(); | ||
if (expected_type == "double" && !node[key].IsScalar()) { | ||
std::cerr << "Invalid type for field: " << key << " (expected double)" | ||
<< std::endl; | ||
return false; | ||
} | ||
if (expected_type == "string" && !node[key].IsScalar()) { | ||
std::cerr << "Invalid type for field: " << key << " (expected string)" | ||
<< std::endl; | ||
return false; | ||
} | ||
|
||
// Additional checks like min, max can be added | ||
if (expected_type == "double" && schema_field["min"]) { | ||
double value = node[key].as<double>(); | ||
if (value < schema_field["min"].as<double>()) { | ||
std::cerr << "Value for " << key | ||
<< " is less than the minimum allowed: " | ||
<< schema_field["min"].as<double>() << std::endl; | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
return true; | ||
#include <yaml-cpp/yaml.h> | ||
|
||
#include <mutex> | ||
|
||
#include "YAMLConverters.h" | ||
|
||
void Config::Load(const std::string &filename) { | ||
std::shared_lock lock(mutex_); | ||
config_file_path_ = filename; | ||
YAML::Node config = YAML::LoadFile(filename); | ||
config_data_.model_settings = config["ModelSettings"].as<ModelSettings>(); | ||
config_data_.transmission_settings = | ||
config["TransmissionSettings"].as<TransmissionSettings>(); | ||
config_data_.population_demographic = | ||
config["PopulationDemographic"].as<PopulationDemographic>(); | ||
NotifyObservers(); | ||
} | ||
|
||
void Config::Reload() { Load(config_file_path_); } | ||
|
||
void Config::RegisterObserver(ConfigObserver observer) { | ||
std::unique_lock lock(mutex_); | ||
observers_.push_back(observer); | ||
} | ||
|
||
void Config::NotifyObservers() { | ||
for (const auto &observer : observers_) { observer(config_data_); } | ||
} | ||
|
Oops, something went wrong.