You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by lo...@apache.org on 2025/09/04 15:15:24 UTC

(nifi-minifi-cpp) 02/08: Review update

This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a commit to branch MINIFICPP-2458
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 5971b19cc5cc453a7bbab2513ef890c9dd0ea23c
Author: Gabor Gyimesi <ga...@gmail.com>
AuthorDate: Thu Jul 10 15:47:19 2025 +0200

    Review update
---
 PROCESSORS.md                                      | 18 +++++---
 .../processors/EvaluateJsonPath.cpp                | 10 +++--
 .../processors/EvaluateJsonPath.h                  |  8 +++-
 .../tests/unit/EvaluateJsonPathTests.cpp           | 52 ++++++++++------------
 4 files changed, 48 insertions(+), 40 deletions(-)

diff --git a/PROCESSORS.md b/PROCESSORS.md
index dfeef9556..d5815d89c 100644
--- a/PROCESSORS.md
+++ b/PROCESSORS.md
@@ -638,12 +638,18 @@ Evaluates one or more JsonPath expressions against the content of a FlowFile. Th
 
 In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.
 
-| Name                          | Default Value      | Allowable Values                        | Description                                                                                                                                                                                                                                                                           |
-|-------------------------------|--------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **Destination**               | flowfile-attribute | flowfile-content<br/>flowfile-attribute | Indicates whether the results of the JsonPath evaluation are written to the FlowFile content or a FlowFile attribute. If using attribute, must specify the Attribute Name property. If set to flowfile-content, only one JsonPath may be specified, and the property name is ignored. |
-| **Null Value Representation** | empty string       | empty string<br/>the string 'null'      | Indicates the desired representation of JSON Path expressions resulting in a null value.                                                                                                                                                                                              |
-| **Path Not Found Behavior**   | ignore             | warn<br/>ignore<br/>skip                | Indicates how to handle missing JSON path expressions when destination is set to 'flowfile-attribute'. Selecting 'warn' will generate a warning when a JSON path expression is not found. Selecting 'skip' will omit attributes for any unmatched JSON path expressions.              |
-| **Return Type**               | auto-detect        | auto-detect<br/>json<br/>scalar         | Indicates the desired return type of the JSON Path expressions. Selecting 'auto-detect' will set the return type to 'json' for a Destination of 'flowfile-content', and 'scalar' for a Destination of 'flowfile-attribute'.                                                           |
+| Name                          | Default Value      | Allowable Values                        | Description                                                                                                                                                                                                                                                              |
+|-------------------------------|--------------------|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Destination**               | flowfile-attribute | flowfile-content<br/>flowfile-attribute | Indicates whether the results of the JsonPath evaluation are written to the FlowFile content or a FlowFile attribute.                                                                                                                                                    |
+| **Null Value Representation** | empty string       | empty string<br/>the string 'null'      | Indicates the desired representation of JSON Path expressions resulting in a null value.                                                                                                                                                                                 |
+| **Path Not Found Behavior**   | ignore             | warn<br/>ignore<br/>skip                | Indicates how to handle missing JSON path expressions when destination is set to 'flowfile-attribute'. Selecting 'warn' will generate a warning when a JSON path expression is not found. Selecting 'skip' will omit attributes for any unmatched JSON path expressions. |
+| **Return Type**               | auto-detect        | auto-detect<br/>json<br/>scalar         | Indicates the desired return type of the JSON Path expressions. Selecting 'auto-detect' will set the return type to 'json' for a Destination of 'flowfile-content', and 'scalar' for a Destination of 'flowfile-attribute'.                                              |
+
+### Dynamic Properties
+
+| Name              | Value                           | Description                                                                                                                                                                                                                                                                                                                                                                             |
+|-------------------|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Evaluation Result | JsonPath expression to evaluate | Dynamic property values are evaluated as JsonPaths. In case of 'flowfile-conent' destination, only one dynamic property with JsonPath may be specified, in this case the name of the property is ignored. In case of 'flowfile-attribute' destination, the result of the JsonPath evaluation is written to the 'Evaluation Result' property.<br/>**Supports Expression Language: true** |
 
 ### Relationships
 
diff --git a/extensions/standard-processors/processors/EvaluateJsonPath.cpp b/extensions/standard-processors/processors/EvaluateJsonPath.cpp
index 4b39572da..a8e553f7c 100644
--- a/extensions/standard-processors/processors/EvaluateJsonPath.cpp
+++ b/extensions/standard-processors/processors/EvaluateJsonPath.cpp
@@ -43,6 +43,9 @@ void EvaluateJsonPath::initialize() {
 }
 
 void EvaluateJsonPath::onSchedule(core::ProcessContext& context, core::ProcessSessionFactory&) {
+  if (getDynamicProperties().size() < 1) {
+    throw Exception(PROCESS_SCHEDULE_EXCEPTION, "At least one dynamic property must be specified with a valid JSON path expression");
+  }
   destination_ = utils::parseEnumProperty<evaluate_json_path::DestinationType>(context, EvaluateJsonPath::Destination);
   if (destination_ == evaluate_json_path::DestinationType::FlowFileContent && getDynamicProperties().size() > 1) {
     throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Only one dynamic property is allowed for JSON path when destination is set to flowfile-content");
@@ -89,14 +92,15 @@ void EvaluateJsonPath::writeQueryResult(core::ProcessSession& session, core::Flo
   }
 }
 
-void EvaluateJsonPath::onTrigger(core::ProcessContext&, core::ProcessSession& session) {
+void EvaluateJsonPath::onTrigger(core::ProcessContext& context, core::ProcessSession& session) {
   auto flow_file = session.get();
   if (!flow_file) {
+    context.yield();
+    logger_->log_debug("No FlowFile available, yielding");
     return;
   }
 
-  const auto flow_file_read_result = session.readBuffer(flow_file);
-  const auto json_string = std::string(reinterpret_cast<const char*>(flow_file_read_result.buffer.data()), flow_file_read_result.buffer.size());
+  const auto json_string = to_string(session.readBuffer(flow_file));
   if (json_string.empty()) {
     logger_->log_error("FlowFile content is empty, transferring to Failure relationship");
     session.transfer(flow_file, Failure);
diff --git a/extensions/standard-processors/processors/EvaluateJsonPath.h b/extensions/standard-processors/processors/EvaluateJsonPath.h
index 79af1a806..4d1d76926 100644
--- a/extensions/standard-processors/processors/EvaluateJsonPath.h
+++ b/extensions/standard-processors/processors/EvaluateJsonPath.h
@@ -123,8 +123,7 @@ class EvaluateJsonPath final : public core::ProcessorImpl {
         "value unless 'Path Not Found Behaviour' is set to 'skip', and the FlowFile will always be routed to 'matched.'";
 
   EXTENSIONAPI static constexpr auto Destination = core::PropertyDefinitionBuilder<2>::createProperty("Destination")
-      .withDescription("Indicates whether the results of the JsonPath evaluation are written to the FlowFile content or a FlowFile attribute. If using attribute, must specify the Attribute Name "
-          "property. If set to flowfile-content, only one JsonPath may be specified, and the property name is ignored.")
+      .withDescription("Indicates whether the results of the JsonPath evaluation are written to the FlowFile content or a FlowFile attribute.")
       .withAllowedValues(magic_enum::enum_names<evaluate_json_path::DestinationType>())
       .withDefaultValue(magic_enum::enum_name(evaluate_json_path::DestinationType::FlowFileAttribute))
       .isRequired(true)
@@ -166,6 +165,11 @@ class EvaluateJsonPath final : public core::ProcessorImpl {
   EXTENSIONAPI static constexpr auto Relationships = std::array{Failure, Matched, Unmatched};
 
   EXTENSIONAPI static constexpr bool SupportsDynamicProperties = true;
+  EXTENSIONAPI static constexpr auto EvaluationResult = core::DynamicProperty{"Evaluation Result", "JsonPath expression to evaluate", "Dynamic property values are evaluated as JsonPaths. "
+      "In case of 'flowfile-conent' destination, only one dynamic property with JsonPath may be specified, in this case the name of the property is ignored. "
+      "In case of 'flowfile-attribute' destination, the result of the JsonPath evaluation is written to the 'Evaluation Result' property.", true};
+  EXTENSIONAPI static constexpr auto DynamicProperties = std::array{EvaluationResult};
+
   EXTENSIONAPI static constexpr bool SupportsDynamicRelationships = false;
   EXTENSIONAPI static constexpr core::annotation::Input InputRequirement = core::annotation::Input::INPUT_REQUIRED;
   EXTENSIONAPI static constexpr bool IsSingleThreaded = false;
diff --git a/extensions/standard-processors/tests/unit/EvaluateJsonPathTests.cpp b/extensions/standard-processors/tests/unit/EvaluateJsonPathTests.cpp
index 12c354303..379d98fce 100644
--- a/extensions/standard-processors/tests/unit/EvaluateJsonPathTests.cpp
+++ b/extensions/standard-processors/tests/unit/EvaluateJsonPathTests.cpp
@@ -36,6 +36,10 @@ class EvaluateJsonPathTestFixture {
   processors::EvaluateJsonPath* evaluate_json_path_processor_;
 };
 
+TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "At least one dynamic property must be specified", "[EvaluateJsonPathTests]") {
+  REQUIRE_THROWS_WITH(controller_.trigger({{.content = "foo"}}), "Process Schedule Operation: At least one dynamic property must be specified with a valid JSON path expression");
+}
+
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "When destination is set to flowfile content only one dynamic property is allowed", "[EvaluateJsonPathTests]") {
   controller_.plan->setProperty(evaluate_json_path_processor_, processors::EvaluateJsonPath::Destination, "flowfile-content");
   controller_.plan->setDynamicProperty(evaluate_json_path_processor_, "attribute1", "value1");
@@ -46,6 +50,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "When destination is set to flowfi
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Input flowfile has invalid JSON as content", "[EvaluateJsonPathTests]") {
   ProcessorTriggerResult result;
   std::string error_log;
+  controller_.plan->setDynamicProperty(evaluate_json_path_processor_, "attribute1", "value1");
   SECTION("Flow file content is empty") {
     result = controller_.trigger({{.content = ""}});
     error_log = "FlowFile content is empty, transferring to Failure relationship";
@@ -115,12 +120,10 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "JSON paths are not found in conte
   CHECK(controller_.plan->getContent(result_flow_file) == "{}");
 
   for (const auto& [key, value] : expected_attributes) {
-    std::string attribute_value;
     if (!expect_attributes) {
-      CHECK_FALSE(result_flow_file->getAttribute(key, attribute_value));
+      CHECK_FALSE(result_flow_file->getAttribute(key));
     } else {
-      CHECK(result_flow_file->getAttribute(key, attribute_value));
-      CHECK(attribute_value == value);
+      CHECK(*result_flow_file->getAttribute(key) == value);
     }
   }
 
@@ -157,9 +160,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "JSON paths are not found in conte
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Unmatched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == "{}");
-
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("attribute", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("attribute"));
 
   if (warn_path_not_found_behavior) {
     CHECK(utils::verifyLogLinePresenceInPollTime(0s, "JSON path '$.firstName' not found for attribute key 'attribute'"));
@@ -173,6 +174,11 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "JSON path query result does not m
     controller_.plan->setProperty(evaluate_json_path_processor_, processors::EvaluateJsonPath::Destination, "flowfile-attribute");
   }
 
+  SECTION("Return type is set to scalar with flowfile-content destination") {
+    controller_.plan->setProperty(evaluate_json_path_processor_, processors::EvaluateJsonPath::ReturnType, "scalar");
+    controller_.plan->setProperty(evaluate_json_path_processor_, processors::EvaluateJsonPath::Destination, "flowfile-content");
+  }
+
   std::string json_content = R"({"name": {"firstName": "John", "lastName": "Doe"}})";
   auto result = controller_.trigger({{.content = json_content}});
 
@@ -183,8 +189,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "JSON path query result does not m
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Failure).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == json_content);
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("attribute", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("attribute"));
   CHECK(utils::verifyLogLinePresenceInPollTime(0s, "JSON path '$.name' returned a non-scalar value or multiple values for attribute key 'attribute', transferring to Failure relationship"));
 }
 
@@ -202,8 +207,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query JSON object and write it to
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == R"({"firstName":"John","lastName":"Doe"})");
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("jsonPath", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("jsonPath"));
 }
 
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query multiple scalars and write them to attributes", "[EvaluateJsonPathTests]") {
@@ -222,12 +226,9 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query multiple scalars and write
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == json_content);
-  auto attribute_value = result_flow_file->getAttribute("firstName");
-  CHECK(attribute_value.value() == "John");
-  attribute_value = result_flow_file->getAttribute("lastName");
-  CHECK(attribute_value.value() == "Doe");
-  attribute_value = result_flow_file->getAttribute("id");
-  CHECK(attribute_value.value() == "1234");
+  CHECK(*result_flow_file->getAttribute("firstName") == "John");
+  CHECK(*result_flow_file->getAttribute("lastName") == "Doe");
+  CHECK(*result_flow_file->getAttribute("id") == "1234");
 }
 
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query a single scalar and write it to flow file", "[EvaluateJsonPathTests]") {
@@ -244,8 +245,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query a single scalar and write i
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == "John");
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("firstName", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("firstName"));
 }
 
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query has multiple results", "[EvaluateJsonPathTests]") {
@@ -262,8 +262,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query has multiple results", "[Ev
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == "[\"John\",\"Jane\"]");
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("firstName", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("firstName"));
 }
 
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query result is null value in flow file content", "[EvaluateJsonPathTests]") {
@@ -290,8 +289,7 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query result is null value in flo
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == expected_content);
-  std::string attribute_value;
-  CHECK_FALSE(result_flow_file->getAttribute("firstName", attribute_value));
+  CHECK_FALSE(result_flow_file->getAttribute("firstName"));
 }
 
 TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query result is null value in flow file attribute", "[EvaluateJsonPathTests]") {
@@ -319,12 +317,8 @@ TEST_CASE_METHOD(EvaluateJsonPathTestFixture, "Query result is null value in flo
   const auto result_flow_file = result.at(processors::EvaluateJsonPath::Matched).at(0);
 
   CHECK(controller_.plan->getContent(result_flow_file) == json_content);
-  auto attribute = result_flow_file->getAttribute("firstName");
-  REQUIRE(attribute);
-  CHECK(attribute.value() == "John");
-  attribute = result_flow_file->getAttribute("email");
-  REQUIRE(attribute);
-  CHECK(attribute.value() == expected_null_value);
+  CHECK(*result_flow_file->getAttribute("firstName") == "John");
+  CHECK(*result_flow_file->getAttribute("email") == expected_null_value);
 }
 
 }  // namespace org::apache::nifi::minifi::test