| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- #include "config/config_loader.hpp"
- #include "common/string_utils.hpp"
- #include "logging/logger.hpp"
- #include <fstream>
- #include <regex>
- namespace smartbotic::config {
- using namespace common;
- Result<nlohmann::json> 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<nlohmann::json> 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<std::string>()));
- }
- 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<nlohmann::json> 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<void> 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<std::string>())) {
- return Error(ErrorCode::ValidationError,
- "Missing required field: " + field.get<std::string>());
- }
- }
- }
- 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<void>();
- }
- // Config class implementation
- Config::Config(nlohmann::json data) : data_(std::move(data)) {}
- Result<Config> 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<void> 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<void>();
- }
- } // namespace smartbotic::config
|