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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 259c58eb1e1 NIFI-16021 Improved JoltTransformJSON invalid 
specification messaging (#11426)
259c58eb1e1 is described below

commit 259c58eb1e1bb2e187cb649d7b99d06e39accfca
Author: dan-s1 <[email protected]>
AuthorDate: Wed Jul 22 14:00:04 2026 -0400

    NIFI-16021 Improved JoltTransformJSON invalid specification messaging 
(#11426)
    
    - Improved JoltTransformJSON error message when specified JOLT spec is 
invalid and ensured leading single and multi-line Java comments are supported 
when the Jolt spec is specified as text. Also added tests to ensure 
ValidateJson causes Json parsing exception when any Java comments is specified 
before or inside a schema.
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../processors/jolt/AbstractJoltTransform.java     |  10 +-
 .../processors/jolt/TestJoltTransformJSON.java     |  69 +++++++++++--
 .../specs/chainrSpecWithSingleLineComment.json     |   2 +-
 .../nifi/processors/standard/TestValidateJson.java | 115 +++++++++++++++++++++
 4 files changed, 187 insertions(+), 9 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java
 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java
index dfc8126a9b8..597819fa075 100644
--- 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java
+++ 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java
@@ -190,7 +190,15 @@ public abstract class AbstractJoltTransform extends 
AbstractProcessor {
                     }
                 }
             } catch (final Exception e) {
-                String message = String.format("Specification not valid for 
the selected transformation: %s", e.getMessage());
+                final String reason;
+                final Throwable cause = e.getCause();
+                if (cause == null) {
+                    reason = e.getMessage();
+                } else {
+                    reason = "%s [%s]".formatted(cause.getMessage(), 
e.getMessage());
+                }
+
+                final String message = "Specification not valid: 
%s".formatted(reason);
                 results.add(new ValidationResult.Builder()
                         .valid(false)
                         .subject(JOLT_SPEC.getDisplayName())
diff --git 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java
 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java
index 8efe2e9db92..f5e2ebc0ee6 100644
--- 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java
+++ 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java
@@ -18,6 +18,7 @@ package org.apache.nifi.processors.jolt;
 
 import io.joltcommunity.jolt.Diffy;
 import io.joltcommunity.jolt.JsonUtils;
+import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.flowfile.attributes.CoreAttributes;
 import org.apache.nifi.jolt.util.JoltTransformStrategy;
 import org.apache.nifi.processor.Processor;
@@ -40,6 +41,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -109,6 +111,19 @@ class TestJoltTransformJSON {
         runner.assertValid();
     }
 
+    @Test
+    void testNonExistingJOLTSpec() throws IOException {
+        final Path testResourcesDir = Paths.get("src/test/resources");
+        final Path nonexistingSpec = 
testResourcesDir.resolve("nonExistingSpec.json");
+        runner.setProperty(JoltTransformJSON.JOLT_SPEC, 
nonexistingSpec.toAbsolutePath().toString());
+        runner.enqueue(JSON_INPUT);
+        runner.assertNotValid();
+
+        final Collection<ValidationResult> validationResults = 
runner.validate();
+        final String explanation = 
validationResults.iterator().next().getExplanation();
+        assertTrue(explanation.contains("file"));
+    }
+
     @Test
     void testIncorrectJOLTSpec() {
         runner.setProperty(JoltTransformJSON.JOLT_SPEC, chainrSpecContents);
@@ -211,10 +226,9 @@ class TestJoltTransformJSON {
     }
 
     @ParameterizedTest()
-    @MethodSource("getChainrArguments")
-    void testTransformInputWithChainr(Path specPath) throws IOException {
-        final String spec = Files.readString(specPath);
-        runner.setProperty(JoltTransformJSON.JOLT_SPEC, spec);
+    @MethodSource("getChainrArgumentContents")
+    void testTransformInputWithChainrContents(String chainrSpecContents) 
throws IOException {
+        runner.setProperty(JoltTransformJSON.JOLT_SPEC, chainrSpecContents);
         runner.enqueue(JSON_INPUT);
         runner.run();
 
@@ -615,9 +629,50 @@ class TestJoltTransformJSON {
         );
     }
 
-    private static Stream<Arguments> getChainrArguments() {
+    private static Stream<Arguments> getChainrArgumentContents() throws 
IOException {
+        final String chainrSpecWithLeadingMultLineJavaComment = """
+                /*
+                    Multiline Java comment
+                */
+                [
+                  {
+                    "operation": "shift",
+                    "spec": {
+                      "rating": {
+                        "primary": {
+                          "value": "Rating",
+                          "max": "RatingRange"
+                        },
+                        "*": {
+                          "max": "SecondaryRatings.&1.Range",
+                          "value": "SecondaryRatings.&1.Value",
+                          "$": "SecondaryRatings.&1.Id"
+                        }
+                      }
+                    }
+                  },
+                  {
+                    "operation": "default",
+                    "spec": {
+                      "Range": 5,
+                      "SecondaryRatings": {
+                        "*": {
+                          "Range": 5
+                        }
+                      }
+                    }
+                  }
+                ]
+                """;
+
+        final Path chainrSpecWithoutCommentsPath = Paths.get(CHAINR_SPEC_PATH);
+        final String chainrSpecWithoutComments = 
Files.readString(chainrSpecWithoutCommentsPath);
+        final Path chainrSpecWithLeadingSingleLineCommentPath = 
Paths.get("src/test/resources/specs/chainrSpecWithSingleLineComment.json");
+        final String chainrSpecWithLeadingSingleLineComment = 
Files.readString(chainrSpecWithLeadingSingleLineCommentPath);
+
         return Stream.of(
-                Arguments.argumentSet("has no single line comments", 
Paths.get(CHAINR_SPEC_PATH)),
-                Arguments.argumentSet("has a single line comment", 
Paths.get("src/test/resources/specs/chainrSpecWithSingleLineComment.json")));
+                Arguments.argumentSet("No Java comments", 
chainrSpecWithoutComments),
+                Arguments.argumentSet("Leading single line Java comment", 
chainrSpecWithLeadingSingleLineComment),
+                Arguments.argumentSet("Leading multi-line Java comment", 
chainrSpecWithLeadingMultLineJavaComment));
     }
 }
diff --git 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json
 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json
index 3b8d0d0b6b1..29a93cd147d 100644
--- 
a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json
+++ 
b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json
@@ -1,5 +1,5 @@
+// Single line comment
 [
-  // Single line comment
   {
     "operation": "shift",
     "spec": {
diff --git 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java
 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java
index 9ee0450c7c3..e5d1cbc7764 100644
--- 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java
+++ 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.processors.standard;
 
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.json.schema.JsonSchema;
 import org.apache.nifi.json.schema.SchemaVersion;
@@ -252,6 +253,120 @@ class TestValidateJson {
         runner.clearTransferState();
     }
 
+    @ParameterizedTest
+    @MethodSource("schemasWithLeadingComments")
+    void testSchemaContentWithLeadingComments(String schema) {
+        runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema);
+        runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, 
SCHEMA_VERSION);
+        runner.enqueue(getFileContent("simple-example-with-comments.json"));
+        runner.assertValid();
+
+        final AssertionFailedError assertionFailedError = 
assertThrows(AssertionFailedError.class, () -> runner.run());
+        final String stackTrace = 
ExceptionUtils.getStackTrace(assertionFailedError);
+        assertTrue(stackTrace.contains("JsonParseException") && 
!stackTrace.contains("FileNotFoundException"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("schemasWithEmbeddedComments")
+    void testSchemaContentWithEmbeddedComments(String schema) {
+        runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema);
+        runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, 
SCHEMA_VERSION);
+        runner.enqueue(getFileContent("simple-example-with-comments.json"));
+        runner.assertValid();
+
+        final AssertionFailedError assertionFailedError = 
assertThrows(AssertionFailedError.class, () -> runner.run());
+        final String stackTrace = 
ExceptionUtils.getStackTrace(assertionFailedError);
+        assertTrue(stackTrace.contains("JsonParseException"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("schemasWithTrailingComments")
+    void testSchemaContentWithTrailingCommentsIgnored(String schema) {
+        runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema);
+        runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, 
SCHEMA_VERSION);
+        runner.enqueue(getFileContent("simple-example-with-comments.json"));
+        runner.assertValid();
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(ValidateJson.REL_VALID);
+    }
+
+    private static Stream<Arguments> schemasWithLeadingComments() {
+        final String schemasWithSingleLineComment = """
+                 // Single line Java comment
+                {
+                    "$schema": "http://json-schema.org/draft-07/schema#";,
+                    "type": "object",
+                    "additionalProperties": true
+                }
+                """;
+        final String schemaWithMultiLineComment = """
+                /*
+                    Multi-line Java comment
+                */
+                {
+                   "$schema": "http://json-schema.org/draft-07/schema#";,
+                   "type": "object",
+                   "additionalProperties": true
+                }
+                """;
+        return Stream.of(
+                Arguments.argumentSet("Leading Java single line comment", 
schemasWithSingleLineComment),
+                Arguments.argumentSet("Leading Java multi-line comment", 
schemaWithMultiLineComment)
+        );
+    }
+
+    private static Stream<Arguments> schemasWithEmbeddedComments() {
+        final String schemasWithSingleLineComment = """
+                {
+                    // Single line Java comment
+                    "$schema": "http://json-schema.org/draft-07/schema#";,
+                    "type": "object",
+                    "additionalProperties": true
+                }
+                """;
+        final String schemaWithMultiLineComment = """
+                {
+                    /*
+                        Multi-line Java comment
+                    */
+                   "$schema": "http://json-schema.org/draft-07/schema#";,
+                   "type": "object",
+                   "additionalProperties": true
+                }
+                """;
+        return Stream.of(
+                Arguments.argumentSet("Embedded Java single line comment", 
schemasWithSingleLineComment),
+                Arguments.argumentSet("Embedded Java multi-line comment", 
schemaWithMultiLineComment)
+        );
+    }
+
+    private static Stream<Arguments> schemasWithTrailingComments() {
+        final String schemasWithSingleLineComment = """
+                {
+                    "$schema": "http://json-schema.org/draft-07/schema#";,
+                    "type": "object",
+                    "additionalProperties": true
+                }
+                // Single line Java comment
+                """;
+        final String schemaWithMultiLineComment = """
+                {
+                   "$schema": "http://json-schema.org/draft-07/schema#";,
+                   "type": "object",
+                   "additionalProperties": true
+                }
+                 /*
+                        Multi-line Java comment
+                  */
+                """;
+        return Stream.of(
+                Arguments.argumentSet("Trailing Java single line comment", 
schemasWithSingleLineComment),
+                Arguments.argumentSet("Trailing Java multi-line comment", 
schemaWithMultiLineComment)
+        );
+    }
+
     private void assertValidationErrors(Relationship relationship, boolean 
expected) {
         final Map<String, String> attributes = 
runner.getFlowFilesForRelationship(relationship).getFirst().getAttributes();
 

Reply via email to