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