config_loader.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #include "config/config_loader.hpp"
  2. #include "common/string_utils.hpp"
  3. #include "logging/logger.hpp"
  4. #include <fstream>
  5. #include <regex>
  6. namespace smartbotic::config {
  7. using namespace common;
  8. Result<nlohmann::json> ConfigLoader::loadFromFile(const std::filesystem::path& path) {
  9. if (!std::filesystem::exists(path)) {
  10. return Error(ErrorCode::NotFound, "Config file not found: " + path.string());
  11. }
  12. std::ifstream file(path);
  13. if (!file.is_open()) {
  14. return Error(ErrorCode::Internal, "Failed to open config file: " + path.string());
  15. }
  16. try {
  17. nlohmann::json config = nlohmann::json::parse(file);
  18. return expandEnvVars(config);
  19. } catch (const nlohmann::json::exception& e) {
  20. return Error(ErrorCode::InvalidArgument,
  21. "Failed to parse config file: " + std::string(e.what()));
  22. }
  23. }
  24. Result<nlohmann::json> ConfigLoader::loadFromString(const std::string& content) {
  25. try {
  26. nlohmann::json config = nlohmann::json::parse(content);
  27. return expandEnvVars(config);
  28. } catch (const nlohmann::json::exception& e) {
  29. return Error(ErrorCode::InvalidArgument,
  30. "Failed to parse config: " + std::string(e.what()));
  31. }
  32. }
  33. nlohmann::json ConfigLoader::expandEnvVars(const nlohmann::json& config) {
  34. if (config.is_string()) {
  35. return nlohmann::json(StringUtils::expandEnvVars(config.get<std::string>()));
  36. }
  37. if (config.is_array()) {
  38. nlohmann::json result = nlohmann::json::array();
  39. for (const auto& item : config) {
  40. result.push_back(expandEnvVars(item));
  41. }
  42. return result;
  43. }
  44. if (config.is_object()) {
  45. nlohmann::json result = nlohmann::json::object();
  46. for (auto it = config.begin(); it != config.end(); ++it) {
  47. result[it.key()] = expandEnvVars(it.value());
  48. }
  49. return result;
  50. }
  51. return config;
  52. }
  53. std::optional<nlohmann::json> ConfigLoader::get(const nlohmann::json& config,
  54. const std::string& path) {
  55. auto parts = StringUtils::split(path, '.');
  56. const nlohmann::json* current = &config;
  57. for (const auto& part : parts) {
  58. if (!current->is_object() || !current->contains(part)) {
  59. return std::nullopt;
  60. }
  61. current = &(*current)[part];
  62. }
  63. return std::make_optional(*current);
  64. }
  65. nlohmann::json ConfigLoader::merge(const nlohmann::json& base, const nlohmann::json& override) {
  66. nlohmann::json result = base;
  67. if (override.is_object() && base.is_object()) {
  68. for (auto it = override.begin(); it != override.end(); ++it) {
  69. if (result.contains(it.key()) && result[it.key()].is_object() && it.value().is_object()) {
  70. result[it.key()] = merge(result[it.key()], it.value());
  71. } else {
  72. result[it.key()] = it.value();
  73. }
  74. }
  75. } else {
  76. result = override;
  77. }
  78. return result;
  79. }
  80. Result<void> ConfigLoader::validate(const nlohmann::json& config, const nlohmann::json& schema) {
  81. // Basic JSON Schema validation
  82. // Note: For production, use a full JSON Schema validator library
  83. if (schema.contains("type")) {
  84. std::string expectedType = schema["type"];
  85. bool valid = false;
  86. if (expectedType == "object") valid = config.is_object();
  87. else if (expectedType == "array") valid = config.is_array();
  88. else if (expectedType == "string") valid = config.is_string();
  89. else if (expectedType == "number") valid = config.is_number();
  90. else if (expectedType == "integer") valid = config.is_number_integer();
  91. else if (expectedType == "boolean") valid = config.is_boolean();
  92. else if (expectedType == "null") valid = config.is_null();
  93. if (!valid) {
  94. return Error(ErrorCode::ValidationError,
  95. "Type mismatch: expected " + expectedType);
  96. }
  97. }
  98. if (schema.contains("required") && config.is_object()) {
  99. for (const auto& field : schema["required"]) {
  100. if (!config.contains(field.get<std::string>())) {
  101. return Error(ErrorCode::ValidationError,
  102. "Missing required field: " + field.get<std::string>());
  103. }
  104. }
  105. }
  106. if (schema.contains("properties") && config.is_object()) {
  107. for (auto it = schema["properties"].begin(); it != schema["properties"].end(); ++it) {
  108. if (config.contains(it.key())) {
  109. auto result = validate(config[it.key()], it.value());
  110. if (result.failed()) {
  111. return result;
  112. }
  113. }
  114. }
  115. }
  116. return Result<void>();
  117. }
  118. // Config class implementation
  119. Config::Config(nlohmann::json data) : data_(std::move(data)) {}
  120. Result<Config> Config::fromFile(const std::filesystem::path& path) {
  121. auto result = ConfigLoader::loadFromFile(path);
  122. if (result.failed()) {
  123. return result.error();
  124. }
  125. return Config(std::move(result.value()));
  126. }
  127. bool Config::has(const std::string& path) const {
  128. return ConfigLoader::get(data_, path).has_value();
  129. }
  130. Result<void> Config::reload(const std::filesystem::path& path) {
  131. auto result = ConfigLoader::loadFromFile(path);
  132. if (result.failed()) {
  133. return result.error();
  134. }
  135. data_ = std::move(result.value());
  136. return Result<void>();
  137. }
  138. } // namespace smartbotic::config