diff --git a/core/story-manager/src/nodes/base_node.cpp b/core/story-manager/src/nodes/base_node.cpp index 1f42f55..9808acd 100644 --- a/core/story-manager/src/nodes/base_node.cpp +++ b/core/story-manager/src/nodes/base_node.cpp @@ -20,14 +20,6 @@ BaseNode::~BaseNode() } -std::string BaseNode::GetEntryLabel(const std::string &id) -{ - std::stringstream ss; - ss << ".nodeEntry" << std::setw(4) << std::setfill('0') << id; - return ss.str(); -} - - void BaseNode::FromJson(const nlohmann::json &j) { try @@ -35,14 +27,14 @@ void BaseNode::FromJson(const nlohmann::json &j) m_uuid = j["uuid"].get(); m_internal_data = j["internal-data"]; m_type = j["type"].get(); - m_title = j.value("title", "Default node"); + m_title = JsonReader::get(j, "title", "Default node"); nlohmann::json posJson = j["position"]; SetPosition(posJson["x"].get(), posJson["y"].get()); } catch (std::exception& e) { - std::cout << "ERROR: " << e.what() << std::endl; + std::cout << "[BASE NODE] ERROR: " << e.what() << std::endl; } } diff --git a/core/story-manager/src/nodes/base_node.h b/core/story-manager/src/nodes/base_node.h index d7bb466..cd96330 100644 --- a/core/story-manager/src/nodes/base_node.h +++ b/core/story-manager/src/nodes/base_node.h @@ -6,6 +6,7 @@ #include #include "json.hpp" +#include "json_reader.h" #include "i_story_page.h" #include "i_story_project.h" #include "story_options.h" @@ -67,8 +68,6 @@ public: BaseNode(const std::string &type, const std::string &typeName, Behavior behavior = BEHAVIOR_EXECUTION); virtual ~BaseNode(); - static std::string GetEntryLabel(const std::string &id); - virtual void Initialize() = 0; void SetPosition(float x, float y); @@ -77,10 +76,6 @@ public: virtual float GetX() const; virtual float GetY() const; - std::string GetMyEntryLabel() const { - return GetEntryLabel(m_uuid); - } - // Coded type, internal use std::string GetType() const { diff --git a/core/story-manager/src/nodes/call_function_node.h b/core/story-manager/src/nodes/call_function_node.h index 7ba56d2..c9aa219 100644 --- a/core/story-manager/src/nodes/call_function_node.h +++ b/core/story-manager/src/nodes/call_function_node.h @@ -27,10 +27,10 @@ public: static InputBinding FromJson(const nlohmann::json& j) { InputBinding ib; - ib.paramName = j.value("paramName", ""); - std::string modeStr = j.value("mode", "connected"); + ib.paramName = JsonReader::get(j,"paramName", ""); + std::string modeStr = JsonReader::get(j, "mode", "connected"); ib.mode = (modeStr == "constant") ? MODE_CONSTANT : MODE_CONNECTED; - ib.constantValue = j.value("constantValue", ""); + ib.constantValue = JsonReader::get(j, "constantValue", ""); return ib; } }; @@ -48,8 +48,8 @@ public: static OutputMapping FromJson(const nlohmann::json& j) { OutputMapping om; - om.returnValueName = j.value("returnValueName", ""); - om.targetVariable = j.value("targetVariable", ""); + om.returnValueName = JsonReader::get(j, "returnValueName", ""); + om.targetVariable = JsonReader::get(j, "targetVariable", ""); return om; } }; diff --git a/core/story-manager/src/nodes/function_entry_node.h b/core/story-manager/src/nodes/function_entry_node.h index 0304021..a3e5af6 100644 --- a/core/story-manager/src/nodes/function_entry_node.h +++ b/core/story-manager/src/nodes/function_entry_node.h @@ -21,9 +21,9 @@ public: static Parameter FromJson(const nlohmann::json& j) { Parameter p; - p.name = j.value("name", ""); - p.type = j.value("type", "int"); - p.defaultValue = j.value("defaultValue", ""); + p.name = JsonReader::get(j, "name", ""); + p.type = JsonReader::get(j, "type", "int"); + p.defaultValue = JsonReader::get(j, "defaultValue", ""); return p; } }; diff --git a/core/story-manager/src/nodes/function_exit_node.h b/core/story-manager/src/nodes/function_exit_node.h index 276e3cf..7d8ff51 100644 --- a/core/story-manager/src/nodes/function_exit_node.h +++ b/core/story-manager/src/nodes/function_exit_node.h @@ -19,8 +19,8 @@ public: static ReturnValue FromJson(const nlohmann::json& j) { ReturnValue rv; - rv.name = j.value("name", ""); - rv.type = j.value("type", "int"); + rv.name = JsonReader::get(j, "name", ""); + rv.type = JsonReader::get(j, "type", "int"); return rv; } }; @@ -32,6 +32,8 @@ public: SetWeight(900); // High weight, near the end SetBehavior(BaseNode::BEHAVIOR_EXECUTION); SetupExecutionPorts(true, 0, true); // Has input, no output (it's the end) + + SaveData(); } void Initialize() override { @@ -39,7 +41,7 @@ public: nlohmann::json j = GetInternalData(); m_returnValues.clear(); - m_exitLabel = j.value("exitLabel", "Return"); + m_exitLabel = JsonReader::get(j, "exitLabel", "Return"); if (j.contains("returnValues") && j["returnValues"].is_array()) { for (const auto& rvJson : j["returnValues"]) { diff --git a/core/story-manager/src/story_project.cpp b/core/story-manager/src/story_project.cpp index ee55ca5..160efb6 100644 --- a/core/story-manager/src/story_project.cpp +++ b/core/story-manager/src/story_project.cpp @@ -13,6 +13,7 @@ #include "sys_lib.h" #include "assembly_generator_chip32_tac.h" #include "nodes_factory.h" +#include "json_reader.h" StoryProject::StoryProject(ILogger &log) : m_log(log) @@ -282,8 +283,6 @@ bool StoryProject::ModelFromJson(const nlohmann::json &model, NodesFactory &fact std::string type = element["type"].get(); - std::cout << "!!!!!!!!!!!!!!!!!" << type << std::endl; - auto n = factory.CreateNode(type); if (n) { diff --git a/core/story-manager/src/utils/json_reader.h b/core/story-manager/src/utils/json_reader.h new file mode 100644 index 0000000..8a1be69 --- /dev/null +++ b/core/story-manager/src/utils/json_reader.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +using json = nlohmann::json; + +class JsonReader { +public: + /** + * @brief Récupère une valeur d'un objet JSON. + * * Cette fonction est similaire à value(), mais ajoute un logging et + * lance optionnellement une exception si la clé est manquante ou si la conversion échoue. + * * @tparam ValueType Le type C++ de la valeur attendue. + * @param j L'objet JSON dans lequel chercher. + * @param key La clé (champ) à chercher. + * @param defaultValue La valeur à retourner en cas d'échec ou de clé manquante. + * @param logOnError Si true, affiche un message d'erreur sur cerr. + * @param throwOnError Si true, lance une std::runtime_error en cas d'échec. + * @return ValueType La valeur trouvée ou la defaultValue. + */ + template + static ValueType get( + const json& j, + const std::string& key, + const ValueType& defaultValue, + bool logOnError = true, + bool throwOnError = false + ) { + // 1. Vérification de la présence de la clé + if (!j.contains(key)) { + const std::string error_msg = "Clé '" + key + "' manquante dans l'objet JSON. Utilisation de la valeur par défaut."; + + if (logOnError) { + std::cout << "⚠️ ERREUR (JsonGetter) : " << error_msg << std::endl; + } + if (throwOnError) { + throw std::runtime_error(error_msg); + } + return defaultValue; + } + + // 2. Récupération de la valeur avec gestion de l'erreur de type + try { + // Utiliser 'at()' est plus sûr car il lève une exception en cas de type incorrect + // si la conversion n'est pas possible, ce qui est mieux que value() pour le diagnostic. + return j.at(key).get(); + + } catch (const nlohmann::json::type_error& e) { + // Le type existe, mais il est incompatible (ex: string au lieu de int) + const std::string error_msg = "Erreur de type pour la clé '" + key + "'. Attendu: " + typeid(ValueType).name() + ". Détails: " + e.what() + ". Utilisation de la valeur par défaut."; + + if (logOnError) { + std::cout << "❌ ERREUR (JsonGetter) : " << error_msg << std::endl; + } + if (throwOnError) { + throw std::runtime_error(error_msg); + } + return defaultValue; + } + // Note: l'exception out_of_range est gérée par le 'contains' ci-dessus. + } +}; diff --git a/core/story-manager/src/utils/json_wrapper.cpp b/core/story-manager/src/utils/json_wrapper.cpp deleted file mode 100644 index 57f56ca..0000000 --- a/core/story-manager/src/utils/json_wrapper.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "json_wrapper.h" -#include "json.hpp" -#include -#include -#include -#include - - -using json = nlohmann::json; - -class JsonWrapper::Impl { -public: - json j; -}; - -JsonWrapper::JsonWrapper() : impl(std::make_unique()) { - impl->j = json::object(); -} - -JsonWrapper::JsonWrapper(JsonWrapper&& other) noexcept : impl(std::move(other.impl)) {} - -JsonWrapper& JsonWrapper::operator=(JsonWrapper&& other) noexcept { - if (this != &other) { - impl = std::move(other.impl); - } - return *this; -} - -JsonWrapper::JsonWrapper(const std::string& jsonString) : impl(std::make_unique()) { - try { - impl->j = json::parse(jsonString); - } catch (...) { - impl->j = json::object(); - } -} - -JsonWrapper::~JsonWrapper() = default; - -bool JsonWrapper::hasKey(const std::string& key) const { - return impl->j.contains(key); -} - -std::optional JsonWrapper::getString(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get(); - } catch (...) { - errorKey = key; - return std::nullopt; - } -} - -std::optional JsonWrapper::getInt(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get(); - } catch (...) { - errorKey = key; - return std::nullopt; - } -} - -std::optional JsonWrapper::getDouble(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get(); - } catch (...) { - errorKey = key; - return std::nullopt; - } -} - -std::optional JsonWrapper::getBool(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get(); - } catch (...) { - errorKey = key; - return std::nullopt; - } -} - -void JsonWrapper::setString(const std::string& key, const std::string& value) { - impl->j[key] = value; -} - -void JsonWrapper::setInt(const std::string& key, int value) { - impl->j[key] = value; -} - -void JsonWrapper::setDouble(const std::string& key, double value) { - impl->j[key] = value; -} - -void JsonWrapper::setBool(const std::string& key, bool value) { - impl->j[key] = value; -} - -void JsonWrapper::setArrayImpl(const std::string& key, const void* values, size_t typeHash) { - // Ici on doit caster et stocker dans json, mais sans RTTI avancé on limite - // Par exemple on peut specialiser cette méthode pour les types courants - if (typeHash == typeid(int).hash_code()) { - const auto& v = *reinterpret_cast*>(values); - impl->j[key] = v; - } else if (typeHash == typeid(double).hash_code()) { - const auto& v = *reinterpret_cast*>(values); - impl->j[key] = v; - } else if (typeHash == typeid(std::string).hash_code()) { - const auto& v = *reinterpret_cast*>(values); - impl->j[key] = v; - } else if (typeHash == typeid(bool).hash_code()) { - const auto& v = *reinterpret_cast*>(values); - impl->j[key] = v; - } else { - // Pour les types non pris en charge, on peut lever une exception ou ignorer - throw std::runtime_error("Type non supporté pour setArray"); - } -} - -template -std::optional> JsonWrapper::getArrayImpl(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get>(); - } catch (...) { - errorKey = key; - return std::nullopt; - } -} - -template -T JsonWrapper::getImpl(const std::string& key, std::string& errorKey) const { - try { - return impl->j.at(key).get(); - } catch (...) { - errorKey = key; - throw; - } -} - -template -T JsonWrapper::asImpl(std::string& errorKey) const { - try { - return impl->j.get(); - } catch (...) { - errorKey = "[root]"; - throw; - } -} - -JsonWrapper JsonWrapper::fromImpl(const void* value, size_t typeHash) { - JsonWrapper wrapper; - if (typeHash == typeid(int).hash_code()) { - wrapper.impl->j = *reinterpret_cast(value); - } else if (typeHash == typeid(double).hash_code()) { - wrapper.impl->j = *reinterpret_cast(value); - } else if (typeHash == typeid(std::string).hash_code()) { - wrapper.impl->j = *reinterpret_cast(value); - } else if (typeHash == typeid(bool).hash_code()) { - wrapper.impl->j = *reinterpret_cast(value); - } else { - // Si c’est un type complexe, il faut une surcharge ou un specialization dans le .cpp - // Exemple: si T a to_json/from_json, on peut faire une conversion nlohmann::json value = T; - // Ici on fait un cast "générique" (moins safe) - // On peut faire un throw ici pour forcer l’utilisateur à spécialiser. - throw std::runtime_error("Type non supporté pour from()"); - } - return std::move(wrapper); -} - -// Explicit instantiations for getArrayImpl, getImpl, asImpl for common types - -template std::optional> JsonWrapper::getArrayImpl(const std::string&, std::string&) const; -template std::optional> JsonWrapper::getArrayImpl(const std::string&, std::string&) const; -template std::optional> JsonWrapper::getArrayImpl(const std::string&, std::string&) const; -template std::optional> JsonWrapper::getArrayImpl(const std::string&, std::string&) const; - -template int JsonWrapper::getImpl(const std::string&, std::string&) const; -template double JsonWrapper::getImpl(const std::string&, std::string&) const; -template std::string JsonWrapper::getImpl(const std::string&, std::string&) const; -template bool JsonWrapper::getImpl(const std::string&, std::string&) const; - -template int JsonWrapper::asImpl(std::string&) const; -template double JsonWrapper::asImpl(std::string&) const; -template std::string JsonWrapper::asImpl(std::string&) const; -template bool JsonWrapper::asImpl(std::string&) const; - -std::string JsonWrapper::dump(int indent) const { - if (indent < 0) return impl->j.dump(); - return impl->j.dump(indent); -} diff --git a/core/story-manager/src/utils/json_wrapper.h b/core/story-manager/src/utils/json_wrapper.h deleted file mode 100644 index b3e145f..0000000 --- a/core/story-manager/src/utils/json_wrapper.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -class JsonWrapper { -public: - JsonWrapper(); - explicit JsonWrapper(const std::string& jsonString); - ~JsonWrapper(); - - // Ajout move constructor et move assign - JsonWrapper(JsonWrapper&&) noexcept; - JsonWrapper& operator=(JsonWrapper&&) noexcept; - - // Supprimer copie pour éviter erreur - JsonWrapper(const JsonWrapper&) = delete; - JsonWrapper& operator=(const JsonWrapper&) = delete; - - bool hasKey(const std::string& key) const; - - // Valeurs simples - std::optional getString(const std::string& key, std::string& errorKey) const; - std::optional getInt(const std::string& key, std::string& errorKey) const; - std::optional getDouble(const std::string& key, std::string& errorKey) const; - std::optional getBool(const std::string& key, std::string& errorKey) const; - - void setString(const std::string& key, const std::string& value); - void setInt(const std::string& key, int value); - void setDouble(const std::string& key, double value); - void setBool(const std::string& key, bool value); - - // Array génériques - template - void setArray(const std::string& key, const std::vector& values) { - setArrayImpl(key, reinterpret_cast(&values), typeid(T).hash_code()); - } - - template - std::optional> getArray(const std::string& key, std::string& errorKey) const { - return getArrayImpl(key, errorKey); - } - - // Sérialisation/Désérialisation générique - template - T get(const std::string& key, std::string& errorKey) const { - return getImpl(key, errorKey); - } - - template - T as(std::string& errorKey) const { - return asImpl(errorKey); - } - - template - static JsonWrapper from(const T& value) { - return fromImpl(reinterpret_cast(&value), typeid(T).hash_code()); - } - - // Dump JSON - std::string dump(int indent = -1) const; - -private: - class Impl; - std::unique_ptr impl; - - // Implémentations privées non template (définies dans .cpp) - void setArrayImpl(const std::string& key, const void* values, size_t typeHash); - - template - std::optional> getArrayImpl(const std::string& key, std::string& errorKey) const; - - template - T getImpl(const std::string& key, std::string& errorKey) const; - - template - T asImpl(std::string& errorKey) const; - - static JsonWrapper fromImpl(const void* value, size_t typeHash); -}; diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index 4777f57..8f5e74d 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -48,8 +48,6 @@ add_executable(${PROJECT_NAME} ../story-manager/src/nodes/break_node.cpp ../story-manager/src/nodes/continue_node.cpp - ../story-manager/src/utils/json_wrapper.cpp - ../chip32/chip32_assembler.cpp ../chip32/chip32_vm.c ../chip32/chip32_binary_format.c diff --git a/core/tests/test_json.cpp b/core/tests/test_json.cpp index 45ddbe9..1e9a3c9 100644 --- a/core/tests/test_json.cpp +++ b/core/tests/test_json.cpp @@ -1,101 +1,75 @@ #include #include -#include "json_wrapper.h" +#include +#include +#include "json_reader.h" #include "json.hpp" -using Catch::Matchers::WithinRel; -using namespace nlohmann; -TEST_CASE("JsonWrapper basic types", "[json]") { - JsonWrapper json; - json.setString("name", "Alice"); - json.setInt("age", 30); - json.setDouble("pi", 3.14159); - json.setBool("is_valid", true); +using namespace Catch::Matchers; - std::string err; +TEST_CASE("JsonReader::get - Scénarios de base", "[JsonReader]") { + json data = R"({ + "nom": "Bob", + "age": 42, + "is_admin": true + })"_json; - SECTION("Get string") { - auto name = json.getString("name", err); - REQUIRE(name.has_value()); - REQUIRE(name.value() == "Alice"); + // --- Scénario 1 : Succès (Clé présente, Type correct) --- + SECTION("Succès - Récupération d'un entier") { + int age = JsonReader::get(data, "age", 0, false, false); + REQUIRE(age == 42); } - SECTION("Get int") { - auto age = json.getInt("age", err); - REQUIRE(age.has_value()); - REQUIRE(age.value() == 30); + SECTION("Succès - Récupération d'une chaîne") { + std::string nom = JsonReader::get(data, "nom", "N/A", false, false); + REQUIRE(nom == "Bob"); } - SECTION("Get double") { - auto pi = json.getDouble("pi", err); - REQUIRE(pi.has_value()); - REQUIRE_THAT(pi.value(), WithinRel(3.14159, 1e-6)); - } - - SECTION("Get bool") { - auto valid = json.getBool("is_valid", err); - REQUIRE(valid.has_value()); - REQUIRE(valid.value() == true); + // --- Scénario 2 : Clé Manquante (Retourne la valeur par défaut) --- + SECTION("Clé Manquante - Retourne la valeur par défaut") { + // Log désactivé pour ne pas polluer la sortie de test + double taux = JsonReader::get(data, "taux_inexistant", 1.5, false, false); + REQUIRE(taux == 1.5); } } -TEST_CASE("JsonWrapper arrays", "[json]") { - JsonWrapper json; +TEST_CASE("JsonReader::get - Gestion des Erreurs", "[JsonReader][Erreur]") { + json data_err = R"({ + "taux_texte": "0.99", + "liste": [1, 2] + })"_json; - json.setArray("scores", {10, 20, 30}); - json.setArray("tags", {"news", "tech"}); - json.setArray("measurements", {1.1, 2.2, 3.3}); - json.setArray("flags", {true, false, true}); + // --- Scénario 3 : Erreur de Type (Retourne la valeur par défaut) --- + SECTION("Erreur de Type - Retourne la valeur par défaut (Int vs String)") { + // Tente de lire "0.99" (string) comme un int. + int taux = JsonReader::get(data_err, "taux_texte", -1, false, false); + REQUIRE(taux == -1); + } - std::string err; + // --- Scénario 4 : Clé Manquante (Lance une exception) --- + SECTION("Clé Manquante - Lance une runtime_error") { + REQUIRE_THROWS_AS( + JsonReader::get(data_err, "cle_manquante_fatale", "N/A", false, true), + std::runtime_error + ); + // Vérifie le message pour s'assurer qu'il mentionne la clé + REQUIRE_THROWS_WITH( + JsonReader::get(data_err, "cle_manquante_fatale", "N/A", false, true), + ContainsSubstring("cle_manquante_fatale") + ); + } - auto scores = json.getArray("scores", err); - REQUIRE(scores.has_value()); - REQUIRE(scores->size() == 3); - REQUIRE(scores->at(1) == 20); - - auto tags = json.getArray("tags", err); - REQUIRE(tags.has_value()); - REQUIRE(tags->at(0) == "news"); - - auto measurements = json.getArray("measurements", err); - REQUIRE(measurements.has_value()); - REQUIRE_THAT(measurements->at(2), WithinRel(3.3, 1e-6)); - - auto flags = json.getArray("flags", err); - REQUIRE(flags.has_value()); - REQUIRE(flags->at(0) == true); -} - -struct Connection { - std::string host; - int port;ccM9XAGZ$mz^b*52T5p&sMA@ujPbCUNW -}; - -// from_json / to_json must be defined for Connection -inline void to_json(nlohmann::json& j, const Connection& c) { - j = nlohmann::json{{"host", c.host}, {"port", c.port}}; -} - -inline void from_json(const nlohmann::json& j, Connection& c) { - j.at("host").get_to(c.host); - j.at("port").get_to(c.port); -} - -template Connection JsonWrapper::asImpl(std::string&) const; -template Connection JsonWrapper::getImpl(const std::string&, std::string&) const; -template std::optional> JsonWrapper::getArrayImpl(const std::string&, std::string&) const; - - -TEST_CASE("JsonWrapper generic serialization/deserialization", "[json][struct]") { - - - Connection original{"127.0.0.1", 5000}; - auto wrapper = JsonWrapper::from(original); - - std::string err; - auto restored = wrapper.as(err); - - REQUIRE(restored.host == "127.0.0.1"); - REQUIRE(restored.port == 5000); -} + // --- Scénario 5 : Erreur de Type (Lance une exception) --- + SECTION("Erreur de Type - Lance une runtime_error") { + // Tente de lire une [array] comme un int + REQUIRE_THROWS_AS( + JsonReader::get(data_err, "liste", 0, false, true), + std::runtime_error + ); + // Vérifie le message pour s'assurer qu'il mentionne la clé + REQUIRE_THROWS_WITH( + JsonReader::get(data_err, "liste", 0, false, true), + ContainsSubstring("liste") && ContainsSubstring("Erreur de type") + ); + } +} \ No newline at end of file