From 40fea2c26119096e6c29b856155bd969470053aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 09:57:44 +0000 Subject: [PATCH 1/4] Initial plan From 5a3a4eed8647ea7b2d27af0af6c281d993d1b762 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:14:08 +0000 Subject: [PATCH 2/4] Implement JSON support for PushObjectJson method Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- casbin/model/evaluator.cpp | 32 ++++++++- tests/enforcer_test.cpp | 141 +++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index acf3211f..375691f3 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -15,6 +15,7 @@ */ #include "casbin/model/evaluator.h" +#include #include #include "casbin/util/util.h" @@ -61,7 +62,36 @@ void ExprtkEvaluator::PushObjectString(const std::string& target, const std::str void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::string& proprity, const nlohmann::json& var) { auto identifier = target + "." + proprity; - // this->symbol_table.add_stringvar(identifier, const_cast(var)); + + // Recursively flatten JSON object into dot-notation identifiers + std::function flatten; + flatten = [&](const std::string& prefix, const nlohmann::json& j) { + if (j.is_object()) { + // Recursively process nested objects + for (auto it = j.begin(); it != j.end(); ++it) { + std::string key = it.key(); + flatten(prefix + "." + key, it.value()); + } + } else if (j.is_string()) { + // Add string value + this->AddIdentifier(prefix, j.get()); + } else if (j.is_number_integer()) { + // Convert integer to string + this->AddIdentifier(prefix, std::to_string(j.get())); + } else if (j.is_number_float()) { + // Convert float to string + this->AddIdentifier(prefix, std::to_string(j.get())); + } else if (j.is_boolean()) { + // Convert boolean to string + this->AddIdentifier(prefix, j.get() ? "true" : "false"); + } else if (j.is_null()) { + // Handle null as empty string + this->AddIdentifier(prefix, ""); + } + // Arrays are not supported in the original test, so we skip them + }; + + flatten(identifier, var); } void ExprtkEvaluator::LoadFunctions() { diff --git a/tests/enforcer_test.cpp b/tests/enforcer_test.cpp index dc0c1b88..18d7a573 100644 --- a/tests/enforcer_test.cpp +++ b/tests/enforcer_test.cpp @@ -416,4 +416,145 @@ TEST(TestEnforcerEx, TestMapParams) { // ASSERT_TRUE(!EvalAndGetTop(scope, s6)); // } +TEST(TestEnforcer, JsonData) { + using json = nlohmann::json; + + // Create evaluator and initialize + auto evaluator = std::make_shared(); + evaluator->InitialObject("r"); + + // Test simple JSON with various data types + json myJson = { + {"DoubleCase", 3.141}, + {"IntegerCase", 2}, + {"BoolenCase", true}, + {"StringCase", "Bob"}, + {"x", { + {"y", { + {"z", 1} + }}, + {"x", 2} + }} + }; + + evaluator->PushObjectJson("r", "data", myJson); + + // Test double value (stored as string "3.141000") + ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.141000'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test integer value (stored as string "2") + ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '2'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test boolean value (stored as string "true") + ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'true'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test string value + ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'Bob'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test nested JSON - x.y.z (stored as string "1") + ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '1'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test nested JSON - x.x (stored as string "2") + ASSERT_TRUE(evaluator->Eval("r.data.x.x == '2'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test negative cases + ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.14'")); + ASSERT_FALSE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '1'")); + ASSERT_FALSE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'false'")); + ASSERT_FALSE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'BoB'")); + ASSERT_FALSE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '2'")); + ASSERT_FALSE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.data.x.x == '1'")); + ASSERT_FALSE(evaluator->GetBoolean()); +} + +TEST(TestEnforcer, JsonDataComplex) { + using json = nlohmann::json; + + // Create evaluator and initialize + auto evaluator = std::make_shared(); + evaluator->InitialObject("r"); + + // Test complex nested JSON similar to the issue example + json sub = { + {"ID", "zk"}, + {"proxy", "vpn"}, + {"Department", "nlp"}, + {"month", "Jan"}, + {"week", "Mon"}, + {"time", "morning"}, + {"Longitude", "123"}, + {"Latitude", "456"}, + {"Altitude", "789"}, + {"OS", "HarmonyOS"}, + {"CPU", "XeonPlatinum8480+"}, + {"NetworkType", "WLan"}, + {"ProtocolType", "Bluetooth"}, + {"EncryptionType", "3DES"}, + {"ESecurityProtocol", "HTTPS"} + }; + + json obj = { + {"SecurityLevel", "3"}, + {"Source", "ISS"}, + {"DistributionMethod", "C"} + }; + + evaluator->PushObjectJson("r", "sub", sub); + evaluator->PushObjectJson("r", "obj", obj); + evaluator->PushObjectString("r", "act", "read"); + + // Test sub attributes + ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.sub.proxy == 'vpn'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.sub.OS == 'HarmonyOS'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.sub.CPU == 'XeonPlatinum8480+'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test obj attributes + ASSERT_TRUE(evaluator->Eval("r.obj.SecurityLevel == '3'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.obj.Source == 'ISS'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.obj.DistributionMethod == 'C'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test act attribute + ASSERT_TRUE(evaluator->Eval("r.act == 'read'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + // Test combined conditions + ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk' and r.obj.SecurityLevel == '3' and r.act == 'read'")); + ASSERT_TRUE(evaluator->GetBoolean()); + + ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp' and r.obj.Source == 'ISS'")); + ASSERT_TRUE(evaluator->GetBoolean()); +} + } // namespace From 0aa55fd4648ffb8b17c2965ea2ae83b1f2780e3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:18:57 +0000 Subject: [PATCH 3/4] Apply clang-format to modified files Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- casbin/model/evaluator.cpp | 11 ++-- tests/enforcer_test.cpp | 111 ++++++++++++++++--------------------- 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index 375691f3..d086cb05 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -62,7 +62,7 @@ void ExprtkEvaluator::PushObjectString(const std::string& target, const std::str void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::string& proprity, const nlohmann::json& var) { auto identifier = target + "." + proprity; - + // Recursively flatten JSON object into dot-notation identifiers std::function flatten; flatten = [&](const std::string& prefix, const nlohmann::json& j) { @@ -90,7 +90,7 @@ void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::strin } // Arrays are not supported in the original test, so we skip them }; - + flatten(identifier, var); } @@ -174,17 +174,16 @@ std::unordered_map ExprtkEvaluator::requestValues() co std::vector var_list; symbol_table.get_stringvar_list(var_list); std::unordered_map result; - for (const auto& e : var_list ) { - if (e[0] == 'r') { + for (const auto& e : var_list) { + if (e[0] == 'r') { auto token = e.substr(2, e.size() - 2); - auto value = symbol_table.get_stringvar("r." + token)->ref().c_str(); + auto value = symbol_table.get_stringvar("r." + token)->ref().c_str(); result.emplace(token, value); } } return result; } - void ExprtkEvaluator::AddIdentifier(const std::string& identifier, const std::string& var) { if (!symbol_table.symbol_exists(identifier)) { identifiers_[identifier] = std::make_unique(""); diff --git a/tests/enforcer_test.cpp b/tests/enforcer_test.cpp index 18d7a573..6124c650 100644 --- a/tests/enforcer_test.cpp +++ b/tests/enforcer_test.cpp @@ -418,141 +418,124 @@ TEST(TestEnforcerEx, TestMapParams) { TEST(TestEnforcer, JsonData) { using json = nlohmann::json; - + // Create evaluator and initialize auto evaluator = std::make_shared(); evaluator->InitialObject("r"); - + // Test simple JSON with various data types - json myJson = { - {"DoubleCase", 3.141}, - {"IntegerCase", 2}, - {"BoolenCase", true}, - {"StringCase", "Bob"}, - {"x", { - {"y", { - {"z", 1} - }}, - {"x", 2} - }} - }; - + json myJson = {{"DoubleCase", 3.141}, {"IntegerCase", 2}, {"BoolenCase", true}, {"StringCase", "Bob"}, {"x", {{"y", {{"z", 1}}}, {"x", 2}}}}; + evaluator->PushObjectJson("r", "data", myJson); - + // Test double value (stored as string "3.141000") ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.141000'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test integer value (stored as string "2") ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '2'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test boolean value (stored as string "true") ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'true'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test string value ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'Bob'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test nested JSON - x.y.z (stored as string "1") ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '1'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test nested JSON - x.x (stored as string "2") ASSERT_TRUE(evaluator->Eval("r.data.x.x == '2'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test negative cases ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.14'")); ASSERT_FALSE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '1'")); ASSERT_FALSE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'false'")); ASSERT_FALSE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'BoB'")); ASSERT_FALSE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '2'")); ASSERT_FALSE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.data.x.x == '1'")); ASSERT_FALSE(evaluator->GetBoolean()); } TEST(TestEnforcer, JsonDataComplex) { using json = nlohmann::json; - + // Create evaluator and initialize auto evaluator = std::make_shared(); evaluator->InitialObject("r"); - + // Test complex nested JSON similar to the issue example - json sub = { - {"ID", "zk"}, - {"proxy", "vpn"}, - {"Department", "nlp"}, - {"month", "Jan"}, - {"week", "Mon"}, - {"time", "morning"}, - {"Longitude", "123"}, - {"Latitude", "456"}, - {"Altitude", "789"}, - {"OS", "HarmonyOS"}, - {"CPU", "XeonPlatinum8480+"}, - {"NetworkType", "WLan"}, - {"ProtocolType", "Bluetooth"}, - {"EncryptionType", "3DES"}, - {"ESecurityProtocol", "HTTPS"} - }; - - json obj = { - {"SecurityLevel", "3"}, - {"Source", "ISS"}, - {"DistributionMethod", "C"} - }; - + json sub = {{"ID", "zk"}, + {"proxy", "vpn"}, + {"Department", "nlp"}, + {"month", "Jan"}, + {"week", "Mon"}, + {"time", "morning"}, + {"Longitude", "123"}, + {"Latitude", "456"}, + {"Altitude", "789"}, + {"OS", "HarmonyOS"}, + {"CPU", "XeonPlatinum8480+"}, + {"NetworkType", "WLan"}, + {"ProtocolType", "Bluetooth"}, + {"EncryptionType", "3DES"}, + {"ESecurityProtocol", "HTTPS"}}; + + json obj = {{"SecurityLevel", "3"}, {"Source", "ISS"}, {"DistributionMethod", "C"}}; + evaluator->PushObjectJson("r", "sub", sub); evaluator->PushObjectJson("r", "obj", obj); evaluator->PushObjectString("r", "act", "read"); - + // Test sub attributes ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.sub.proxy == 'vpn'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.sub.OS == 'HarmonyOS'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.sub.CPU == 'XeonPlatinum8480+'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test obj attributes ASSERT_TRUE(evaluator->Eval("r.obj.SecurityLevel == '3'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.obj.Source == 'ISS'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.obj.DistributionMethod == 'C'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test act attribute ASSERT_TRUE(evaluator->Eval("r.act == 'read'")); ASSERT_TRUE(evaluator->GetBoolean()); - + // Test combined conditions ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk' and r.obj.SecurityLevel == '3' and r.act == 'read'")); ASSERT_TRUE(evaluator->GetBoolean()); - + ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp' and r.obj.Source == 'ISS'")); ASSERT_TRUE(evaluator->GetBoolean()); } From e82ac7434d128d19ce39734a3f31aba78995f2f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:22:59 +0000 Subject: [PATCH 4/4] Address code review feedback: fix typos and improve comments Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- casbin/model/evaluator.cpp | 3 ++- tests/enforcer_test.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index d086cb05..9b609af0 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -88,7 +88,8 @@ void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::strin // Handle null as empty string this->AddIdentifier(prefix, ""); } - // Arrays are not supported in the original test, so we skip them + // Note: JSON arrays are intentionally not handled by this implementation. + // If an array is encountered, it is silently ignored and no identifier is added. }; flatten(identifier, var); diff --git a/tests/enforcer_test.cpp b/tests/enforcer_test.cpp index 6124c650..d3a0bf3c 100644 --- a/tests/enforcer_test.cpp +++ b/tests/enforcer_test.cpp @@ -424,7 +424,7 @@ TEST(TestEnforcer, JsonData) { evaluator->InitialObject("r"); // Test simple JSON with various data types - json myJson = {{"DoubleCase", 3.141}, {"IntegerCase", 2}, {"BoolenCase", true}, {"StringCase", "Bob"}, {"x", {{"y", {{"z", 1}}}, {"x", 2}}}}; + json myJson = {{"DoubleCase", 3.141}, {"IntegerCase", 2}, {"BooleanCase", true}, {"StringCase", "Bob"}, {"x", {{"y", {{"z", 1}}}, {"x", 2}}}}; evaluator->PushObjectJson("r", "data", myJson); @@ -437,7 +437,7 @@ TEST(TestEnforcer, JsonData) { ASSERT_TRUE(evaluator->GetBoolean()); // Test boolean value (stored as string "true") - ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'true'")); + ASSERT_TRUE(evaluator->Eval("r.data.BooleanCase == 'true'")); ASSERT_TRUE(evaluator->GetBoolean()); // Test string value @@ -459,7 +459,7 @@ TEST(TestEnforcer, JsonData) { ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '1'")); ASSERT_FALSE(evaluator->GetBoolean()); - ASSERT_TRUE(evaluator->Eval("r.data.BoolenCase == 'false'")); + ASSERT_TRUE(evaluator->Eval("r.data.BooleanCase == 'false'")); ASSERT_FALSE(evaluator->GetBoolean()); ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'BoB'"));