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

tpalfy 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 9d835e078b NIFI-14174: Protobuf Reader fails to generate schema for 
proto files containing circular reference
9d835e078b is described below

commit 9d835e078b567dd32797a31fcae64f26365cd646
Author: Mark Bathori <[email protected]>
AuthorDate: Wed Jan 22 10:59:09 2025 +0100

    NIFI-14174: Protobuf Reader fails to generate schema for proto files 
containing circular reference
    
    This closes #9657.
    
    Signed-off-by: Tamas Palfy <[email protected]>
---
 .../protobuf/schema/ProtoSchemaParser.java         | 25 ++++++++++++----
 .../nifi/services/protobuf/ProtoTestUtil.java      |  6 ++++
 .../protobuf/schema/TestProtoSchemaParser.java     | 33 ++++++++++++++++++++++
 .../test/resources/test_circular_reference.desc    | 10 +++++++
 .../test/resources/test_circular_reference.proto   | 30 ++++++++++++++++++++
 5 files changed, 99 insertions(+), 5 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/main/java/org/apache/nifi/services/protobuf/schema/ProtoSchemaParser.java
 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/main/java/org/apache/nifi/services/protobuf/schema/ProtoSchemaParser.java
index 3e5777c1dd..18c4514c0d 100644
--- 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/main/java/org/apache/nifi/services/protobuf/schema/ProtoSchemaParser.java
+++ 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/main/java/org/apache/nifi/services/protobuf/schema/ProtoSchemaParser.java
@@ -29,13 +29,17 @@ import org.apache.nifi.serialization.record.DataType;
 import org.apache.nifi.serialization.record.RecordField;
 import org.apache.nifi.serialization.record.RecordFieldType;
 import org.apache.nifi.serialization.record.RecordSchema;
+import org.apache.nifi.serialization.record.SchemaIdentifier;
+import org.apache.nifi.serialization.record.StandardSchemaIdentifier;
 import org.apache.nifi.serialization.record.type.EnumDataType;
 import org.apache.nifi.serialization.record.type.MapDataType;
 import org.apache.nifi.serialization.record.type.RecordDataType;
 import org.apache.nifi.services.protobuf.FieldType;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -43,6 +47,8 @@ import java.util.Objects;
  */
 public class ProtoSchemaParser {
 
+    private final Map<String, RecordSchema> parsedRecordSchemas = new 
HashMap<>();
+
     private final Schema schema;
 
     public ProtoSchemaParser(Schema schema) {
@@ -57,13 +63,22 @@ public class ProtoSchemaParser {
     public RecordSchema createSchema(String messageTypeName) {
         final MessageType messageType = (MessageType) 
schema.getType(messageTypeName);
         Objects.requireNonNull(messageType, String.format("Message type with 
name [%s] not found in the provided proto files", messageTypeName));
-        List<RecordField> recordFields = new ArrayList<>();
 
-        recordFields.addAll(processFields(messageType.getDeclaredFields()));
-        recordFields.addAll(processFields(messageType.getExtensionFields()));
-        recordFields.addAll(processOneOfFields(messageType));
+        if (parsedRecordSchemas.containsKey(messageTypeName)) {
+            return parsedRecordSchemas.get(messageTypeName);
+        } else {
+            final SchemaIdentifier identifier = new 
StandardSchemaIdentifier.Builder().name(messageTypeName).build();
+            final SimpleRecordSchema recordSchema = new 
SimpleRecordSchema(identifier);
+            parsedRecordSchemas.put(messageTypeName, recordSchema);
 
-        return new SimpleRecordSchema(recordFields);
+            List<RecordField> recordFields = new ArrayList<>();
+            
recordFields.addAll(processFields(messageType.getDeclaredFields()));
+            
recordFields.addAll(processFields(messageType.getExtensionFields()));
+            recordFields.addAll(processOneOfFields(messageType));
+
+            recordSchema.setFields(recordFields);
+            return recordSchema;
+        }
     }
 
     /**
diff --git 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/ProtoTestUtil.java
 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/ProtoTestUtil.java
index c0d273da95..2d0b8bd3b8 100644
--- 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/ProtoTestUtil.java
+++ 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/ProtoTestUtil.java
@@ -58,6 +58,12 @@ public class ProtoTestUtil {
         return schemaLoader.loadSchema();
     }
 
+    public static Schema loadCircularReferenceTestSchema() {
+        final SchemaLoader schemaLoader = new 
SchemaLoader(FileSystems.getDefault());
+        
schemaLoader.initRoots(Collections.singletonList(Location.get(BASE_TEST_PATH + 
"test_circular_reference.proto")), Collections.emptyList());
+        return schemaLoader.loadSchema();
+    }
+
     public static InputStream generateInputDataForProto3() throws IOException, 
Descriptors.DescriptorValidationException {
         DescriptorProtos.FileDescriptorSet descriptorSet = 
DescriptorProtos.FileDescriptorSet.parseFrom(new FileInputStream(BASE_TEST_PATH 
+ "test_proto3.desc"));
         Descriptors.FileDescriptor fileDescriptor = 
Descriptors.FileDescriptor.buildFrom(descriptorSet.getFile(0), new 
Descriptors.FileDescriptor[0]);
diff --git 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/schema/TestProtoSchemaParser.java
 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/schema/TestProtoSchemaParser.java
index 42bb858eab..14009576fe 100644
--- 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/schema/TestProtoSchemaParser.java
+++ 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/java/org/apache/nifi/services/protobuf/schema/TestProtoSchemaParser.java
@@ -25,11 +25,14 @@ import 
org.apache.nifi.serialization.record.type.RecordDataType;
 import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
+import java.util.Optional;
 
+import static 
org.apache.nifi.services.protobuf.ProtoTestUtil.loadCircularReferenceTestSchema;
 import static 
org.apache.nifi.services.protobuf.ProtoTestUtil.loadProto2TestSchema;
 import static 
org.apache.nifi.services.protobuf.ProtoTestUtil.loadProto3TestSchema;
 import static 
org.apache.nifi.services.protobuf.ProtoTestUtil.loadRepeatedProto3TestSchema;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class TestProtoSchemaParser {
 
@@ -115,4 +118,34 @@ public class TestProtoSchemaParser {
         final RecordSchema actual = schemaParser.createSchema("Proto2Message");
         assertEquals(expected, actual);
     }
+
+    @Test
+    public void testSchemaParserWithSelfCircularReference() {
+        final ProtoSchemaParser schemaParser = new 
ProtoSchemaParser(loadCircularReferenceTestSchema());
+        final RecordSchema recordCSchema = schemaParser.createSchema("C");
+
+        final Optional<RecordField> parentField = 
recordCSchema.getField("parent");
+        assertTrue(parentField.isPresent());
+        assertEquals(RecordFieldType.RECORD, 
parentField.get().getDataType().getFieldType());
+
+        assertEquals(recordCSchema, ((RecordDataType) 
parentField.get().getDataType()).getChildSchema());
+    }
+
+    @Test
+    public void testSchemaParserWithMutualCircularReference() {
+        final ProtoSchemaParser schemaParser = new 
ProtoSchemaParser(loadCircularReferenceTestSchema());
+        final RecordSchema recordASchema = schemaParser.createSchema("A");
+        final Optional<RecordField> bOfA = recordASchema.getField("b");
+
+        assertTrue(bOfA.isPresent());
+        assertEquals(RecordFieldType.RECORD, 
bOfA.get().getDataType().getFieldType());
+
+        final RecordSchema recordBSchema = schemaParser.createSchema("B");
+        final Optional<RecordField> aOfB = recordBSchema.getField("a");
+
+        assertTrue(aOfB.isPresent());
+        assertEquals(RecordFieldType.RECORD, 
aOfB.get().getDataType().getFieldType());
+        assertEquals(recordBSchema, ((RecordDataType) 
bOfA.get().getDataType()).getChildSchema());
+        assertEquals(recordASchema, ((RecordDataType) 
aOfB.get().getDataType()).getChildSchema());
+    }
 }
diff --git 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.desc
 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.desc
new file mode 100644
index 0000000000..a8defd3833
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.desc
@@ -0,0 +1,10 @@
+
+�
+test_circular_reference.proto"
+A
+b (2.BRb"
+B
+a (2.ARa"5
+C
+value (    Rvalue
+parent (2.CRparentbproto3
\ No newline at end of file
diff --git 
a/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.proto
 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.proto
new file mode 100644
index 0000000000..0eb4bab38c
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-protobuf-bundle/nifi-protobuf-services/src/test/resources/test_circular_reference.proto
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+syntax = "proto3";
+
+message A {
+  B b = 1;
+}
+
+message B {
+  A a = 1;
+}
+
+message C {
+  string value = 1;
+  C parent = 2;
+}
\ No newline at end of file

Reply via email to