#include "config/config_loader.hpp" #include "common/string_utils.hpp" #include "logging/logger.hpp" #include #include namespace smartbotic::config { using namespace common; Result ConfigLoader::loadFromFile(const std::filesystem::path& path) { if (!std::filesystem::exists(path)) { return Error(ErrorCode::NotFound, "Config file not found: " + path.string()); } std::ifstream file(path); if (!file.is_open()) { return Error(ErrorCode::Internal, "Failed to open config file: " + path.string()); } try { nlohmann::json config = nlohmann::json::parse(file); return expandEnvVars(config); } catch (const nlohmann::json::exception& e) { return Error(ErrorCode::InvalidArgument, "Failed to parse config file: " + std::string(e.what())); } } Result ConfigLoader::loadFromString(const std::string& content) { try { nlohmann::json config = nlohmann::json::parse(content); return expandEnvVars(config); } catch (const nlohmann::json::exception& e) { return Error(ErrorCode::InvalidArgument, "Failed to parse config: " + std::string(e.what())); } } nlohmann::json ConfigLoader::expandEnvVars(const nlohmann::json& config) { if (config.is_string()) { return nlohmann::json(StringUtils::expandEnvVars(config.get())); } if (config.is_array()) { nlohmann::json result = nlohmann::json::array(); for (const auto& item : config) { result.push_back(expandEnvVars(item)); } return result; } if (config.is_object()) { nlohmann::json result = nlohmann::json::object(); for (auto it = config.begin(); it != config.end(); ++it) { result[it.key()] = expandEnvVars(it.value()); } return result; } return config; } std::optional ConfigLoader::get(const nlohmann::json& config, const std::string& path) { auto parts = StringUtils::split(path, '.'); const nlohmann::json* current = &config; for (const auto& part : parts) { if (!current->is_object() || !current->contains(part)) { return std::nullopt; } current = &(*current)[part]; } return std::make_optional(*current); } nlohmann::json ConfigLoader::merge(const nlohmann::json& base, const nlohmann::json& override) { nlohmann::json result = base; if (override.is_object() && base.is_object()) { for (auto it = override.begin(); it != override.end(); ++it) { if (result.contains(it.key()) && result[it.key()].is_object() && it.value().is_object()) { result[it.key()] = merge(result[it.key()], it.value()); } else { result[it.key()] = it.value(); } } } else { result = override; } return result; } Result ConfigLoader::validate(const nlohmann::json& config, const nlohmann::json& schema) { // Basic JSON Schema validation // Note: For production, use a full JSON Schema validator library if (schema.contains("type")) { std::string expectedType = schema["type"]; bool valid = false; if (expectedType == "object") valid = config.is_object(); else if (expectedType == "array") valid = config.is_array(); else if (expectedType == "string") valid = config.is_string(); else if (expectedType == "number") valid = config.is_number(); else if (expectedType == "integer") valid = config.is_number_integer(); else if (expectedType == "boolean") valid = config.is_boolean(); else if (expectedType == "null") valid = config.is_null(); if (!valid) { return Error(ErrorCode::ValidationError, "Type mismatch: expected " + expectedType); } } if (schema.contains("required") && config.is_object()) { for (const auto& field : schema["required"]) { if (!config.contains(field.get())) { return Error(ErrorCode::ValidationError, "Missing required field: " + field.get()); } } } if (schema.contains("properties") && config.is_object()) { for (auto it = schema["properties"].begin(); it != schema["properties"].end(); ++it) { if (config.contains(it.key())) { auto result = validate(config[it.key()], it.value()); if (result.failed()) { return result; } } } } return Result(); } // Config class implementation Config::Config(nlohmann::json data) : data_(std::move(data)) {} Result Config::fromFile(const std::filesystem::path& path) { auto result = ConfigLoader::loadFromFile(path); if (result.failed()) { return result.error(); } return Config(std::move(result.value())); } bool Config::has(const std::string& path) const { return ConfigLoader::get(data_, path).has_value(); } Result Config::reload(const std::filesystem::path& path) { auto result = ConfigLoader::loadFromFile(path); if (result.failed()) { return result.error(); } data_ = std::move(result.value()); return Result(); } } // namespace smartbotic::config