From 76ab89994017fe575bf161e200fc29f736c84cf2 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Mon, 27 Jun 2022 16:23:12 +0530 Subject: [PATCH 1/6] Add support to ballerina table type --- ballerina/tests/errors_test.bal | 45 ++++- ballerina/tests/table_type_test.bal | 156 ++++++++++++++++++ .../io/ballerina/stdlib/serdes/Constants.java | 13 +- .../ballerina/stdlib/serdes/Deserializer.java | 61 +++++++ .../stdlib/serdes/SchemaGenerator.java | 125 +++++++++++++- .../ballerina/stdlib/serdes/Serializer.java | 67 ++++++++ 6 files changed, 457 insertions(+), 10 deletions(-) create mode 100644 ballerina/tests/table_type_test.bal diff --git a/ballerina/tests/errors_test.bal b/ballerina/tests/errors_test.bal index a5f2611..a3513d5 100644 --- a/ballerina/tests/errors_test.bal +++ b/ballerina/tests/errors_test.bal @@ -20,7 +20,7 @@ type EmployeeTable table>; @test:Config{} public isolated function testUnsupportedDataType() returns error? { - string expected = "Unsupported data type: table"; + string expected = "Unsupported data type: anydata"; Proto3Schema|error ser = new(EmployeeTable); @@ -106,3 +106,46 @@ public isolated function testRecordWithNonReferencedMapFieldError() returns erro Error err = ser; test:assertEquals(err.message(), expectedErrorMsg); } + +type RecordWithNonReferencedTableField record { + table> ages; +}; + +@test:Config {} +public isolated function testRecordWithNonReferencedTableFieldError() returns error? { + string expectedErrorMsg = "Record field of table type only supported with reference table type"; + + Proto3Schema|error ser = new(RecordWithNonReferencedTableField); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + +type TableA table>; +type TableB map; +type UnionOfTables TableA|TableA; + +@test:Config {} +public function testTableUnionMemberNotYetSupporteError() returns error? { + string expectedErrorMsg = "Serdes not yet support table type as union member"; + + Proto3Schema|error ser = new(UnionOfTables); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + +type UnionWithArrayOfTables TableA[]|TableB[][]; + +@test:Config {} +public isolated function testTableArrayUnionMemberNotYetSupporteError() returns error? { + string expectedErrorMsg = "Serdes not yet support array of tables as union member"; + + Proto3Schema|error ser = new(UnionWithArrayOfTables); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} diff --git a/ballerina/tests/table_type_test.bal b/ballerina/tests/table_type_test.bal new file mode 100644 index 0000000..0174d32 --- /dev/null +++ b/ballerina/tests/table_type_test.bal @@ -0,0 +1,156 @@ +// Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. 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. + +import ballerina/test; + +type Row record { + int id; + string name; +}; + +type RecordTable table; + +type Score map; +type ScoreTable table; + +type Item record { + readonly int id; + string name; + decimal price; +}; + +type ItemTable table key(id); +type ArrayOfTable RecordTable[]; + +type RecordWithTableField record { + ScoreTable scoreTable; + RecordTable recordTable; +}; + +type MapWithTableConstraint map; + +@test:Config {} +public isolated function testTableWithRecord() returns error? { + RecordTable data = table [ + {id: 1, name: "Plato"}, + {id: 2, name: "Aristotle"}, + {id:3, name: "Socrates"} + ]; + + Proto3Schema ser = check new(RecordTable); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(RecordTable); + RecordTable decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} + +@test:Config {} +public isolated function testTableWithMap() returns error? { + ScoreTable data = table [ + {"Manchester City" : 93}, + {"Liverpool": 92}, + {"Chelsea": 74} + ]; + Proto3Schema ser = check new(ScoreTable); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(ScoreTable); + ScoreTable decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} + + +@test:Config {} +public isolated function testTableWithKey() returns error? { + ItemTable data = table [ + {id: 1, name: "Item A", price: 1e10}, + {id: 20, name: "Item X", price: 2e5} + ]; + Proto3Schema ser = check new(ItemTable); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(ItemTable); + ItemTable decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); + test:assertEquals(decoded[20], data[20]); +} + +@test:Config {} +public isolated function testArrayOfTables() returns error? { + ArrayOfTable data = [ + table [ + {id: 1, name: "Plato"}, + {id: 2, name: "Aristotle"} + ], + table [{id: 3, name: "Socrates"}] + ]; + + Proto3Schema ser = check new(ArrayOfTable); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(ArrayOfTable); + ArrayOfTable decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} + +@test:Config {} +public isolated function testTableRecordField() returns error? { + RecordWithTableField data = { + recordTable: table [ + {id: 1, name: "Plato"}, + {id: 2, name: "Aristotle"}, + {id: 3, name: "Socrates"} + ], + scoreTable: table [ + {"Manchester City": 93}, + {"Liverpool": 92}, + {"Chelsea": 74} + ] + }; + + Proto3Schema ser = check new(RecordWithTableField); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(RecordWithTableField); + RecordWithTableField decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} + + +@test:Config {} +public isolated function testTableAsMapConstraint() returns error? { + MapWithTableConstraint data = { + "Football clubs": table [ + {"Manchester City": 93}, + {"Liverpool": 92}, + {"Chelsea": 74} + ], + "NBA clubs": table [ + {"Boston celtics": 90, + "Golden state warriors": 103 + } + ] + }; + + Proto3Schema ser = check new(MapWithTableConstraint); + check ser.generateProtoFile("MapWithTableConstraint.proto"); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new(MapWithTableConstraint); + MapWithTableConstraint decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java index 3dcf80e..aadf9fe 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java @@ -38,7 +38,7 @@ private Constants() {} // Constants related to java type public static final String INTEGER = "Integer"; - + // Constants related to protobuf schema public static final String SCHEMA_NAME = "schema"; public static final String UNION_BUILDER_NAME = "UnionBuilder"; @@ -58,6 +58,8 @@ private Constants() {} public static final String MAP_FIELD = "mapField"; public static final String KEY_NAME = "key"; public static final String VALUE_NAME = "value"; + public static final String TABLE_BUILDER = "TableBuilder"; + public static final String TABLE_ENTRY = "TableEntry"; public static final String SEPARATOR = "_"; public static final String TYPE_SEPARATOR = "___"; @@ -79,9 +81,14 @@ private Constants() {} public static final String SCHEMA_GENERATION_FAILURE = "Failed to generate schema: "; public static final String FAILED_WRITE_FILE = "Failed to write proto file: "; public static final String MAP_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support map type as union member"; - public static final String ARRAY_OF_MAP_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of maps as union " - + "member"; + public static final String TABLE_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support table type as union member"; + public static final String ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of maps as" + + " union member"; + public static final String ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of tables as " + + "union member"; public static final String RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field of map type " + "only supported with reference map type"; + public static final String RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field of table type " + + "only supported with reference table type"; public static final BString BALLERINA_TYPEDESC_ATTRIBUTE_NAME = StringUtils.fromString("dataType"); } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java b/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java index 42c403f..70fb69f 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java @@ -28,6 +28,7 @@ import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.StringUtils; @@ -38,6 +39,7 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTable; import io.ballerina.runtime.api.values.BTypedesc; import java.math.BigDecimal; @@ -59,6 +61,7 @@ import static io.ballerina.stdlib.serdes.Constants.SCALE; import static io.ballerina.stdlib.serdes.Constants.SCHEMA_NAME; import static io.ballerina.stdlib.serdes.Constants.SEPARATOR; +import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UNSUPPORTED_DATA_TYPE; import static io.ballerina.stdlib.serdes.Constants.VALUE; @@ -132,6 +135,10 @@ private static Object dynamicMessageToBallerinaType(DynamicMessage dynamicMessag return getMapTypeValueFromMessage(dynamicMessage, (MapType) referredType); } + case TypeTags.TABLE_TAG: { + return getTableTypeValueFromMessage(dynamicMessage, (TableType) referredType); + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -290,6 +297,13 @@ private static Object getArrayTypeValueFromMessage(Object value, Type elementTyp break; } + case TypeTags.TABLE_TAG: { + TableType tableType = (TableType) referredElementType; + Object map = getTableTypeValueFromMessage((DynamicMessage) element, tableType); + bArray.append(map); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); } @@ -357,6 +371,13 @@ private static Object getRecordTypeValueFromMessage(DynamicMessage dynamicMessag break; } + case TypeTags.TABLE_TAG: { + Object tableMessage = dynamicMessage.getField(fieldDescriptor); + ballerinaValue = getTableTypeValueFromMessage((DynamicMessage) tableMessage, + (TableType) referredEntryFieldType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredEntryFieldType.getName(), SERDES_ERROR); } @@ -429,6 +450,12 @@ private static Object getMapTypeValueFromMessage(DynamicMessage dynamicMessage, break; } + case TypeTags.TABLE_TAG: { + ballerinaValue = getTableTypeValueFromMessage((DynamicMessage) value, + (TableType) referredConstrainedType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); } @@ -437,6 +464,40 @@ private static Object getMapTypeValueFromMessage(DynamicMessage dynamicMessage, return ballerinaMap; } + private static Object getTableTypeValueFromMessage(DynamicMessage dynamicMessage, TableType tableType) { + BTable table = ValueCreator.createTableValue(tableType); + + Type constrainedType = tableType.getConstrainedType(); + Type referredConstrainedType = TypeUtils.getReferredType(constrainedType); + + FieldDescriptor tableEntryFieldDescriptor = dynamicMessage.getDescriptorForType().findFieldByName(TABLE_ENTRY); + Collection tableEntries = (Collection) dynamicMessage.getField(tableEntryFieldDescriptor); + + for (Object tableEntry : tableEntries) { + DynamicMessage tableEntryMessage = (DynamicMessage) tableEntry; + Object ballerinaValue; + + switch (referredConstrainedType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: { + ballerinaValue = getRecordTypeValueFromMessage(tableEntryMessage, + (RecordType) referredConstrainedType); + break; + } + + case TypeTags.MAP_TAG: { + ballerinaValue = getMapTypeValueFromMessage(tableEntryMessage, (MapType) referredConstrainedType); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); + + } + table.add(ballerinaValue); + } + return table; + } + private static RecordType getBallerinaRecordTypeFromUnion(UnionType unionType, String targetBallerinaTypeName) { RecordType targetRecordType = null; diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java index 2a4c3d9..beca2ea 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.TypeUtils; @@ -47,7 +48,8 @@ import static com.google.protobuf.Descriptors.DescriptorValidationException; import static io.ballerina.stdlib.serdes.Constants.ARRAY_BUILDER_NAME; import static io.ballerina.stdlib.serdes.Constants.ARRAY_FIELD_NAME; -import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_MEMBER_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED; import static io.ballerina.stdlib.serdes.Constants.ATOMIC_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.BOOL; import static io.ballerina.stdlib.serdes.Constants.BYTES; @@ -64,12 +66,15 @@ import static io.ballerina.stdlib.serdes.Constants.PRECISION; import static io.ballerina.stdlib.serdes.Constants.PROTO3; import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; +import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.REPEATED_LABEL; import static io.ballerina.stdlib.serdes.Constants.SCALE; import static io.ballerina.stdlib.serdes.Constants.SCHEMA_GENERATION_FAILURE; import static io.ballerina.stdlib.serdes.Constants.SCHEMA_NAME; import static io.ballerina.stdlib.serdes.Constants.SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.STRING; +import static io.ballerina.stdlib.serdes.Constants.TABLE_BUILDER; +import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UINT32; import static io.ballerina.stdlib.serdes.Constants.UNION_BUILDER_NAME; @@ -96,8 +101,8 @@ public class SchemaGenerator { public static Object generateSchema(BObject serdes, BTypedesc bTypedesc) { try { ProtobufFileBuilder protobufFile = new ProtobufFileBuilder(); - ProtobufMessageBuilder protobufMessageBuilder = - buildProtobufMessageFromBallerinaTypedesc(bTypedesc.getDescribingType()); + ProtobufMessageBuilder protobufMessageBuilder = buildProtobufMessageFromBallerinaTypedesc( + bTypedesc.getDescribingType()); Descriptor messageDescriptor = protobufFile.addMessageType(protobufMessageBuilder).build(); serdes.addNativeData(SCHEMA_NAME, messageDescriptor); serdes.addNativeData(PROTO3, protobufFile.toString()); @@ -182,6 +187,14 @@ private static ProtobufMessageBuilder buildProtobufMessageFromBallerinaTypedesc( break; } + case TypeTags.TABLE_TAG: { + TableType tableType = (TableType) referredType; + messageName = Constants.TABLE_BUILDER; + messageBuilder = new ProtobufMessageBuilder(messageName); + generateMessageDefinitionForTableType(messageBuilder, tableType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -245,6 +258,11 @@ private static Map.Entry mapUnionMemberToMapEntry(Type type) { throw createSerdesError(MAP_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); } + if (referredType.getTag() == TypeTags.TABLE_TAG) { + // TODO: support table member + throw createSerdesError(Constants.TABLE_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + } + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -327,6 +345,7 @@ private static void generateMessageDefinitionForUnionType(ProtobufMessageBuilder } // TODO: support map member + // TODO: support table member default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredMemberType.getName(), SERDES_ERROR); @@ -429,8 +448,8 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder nestedMessageName = ballerinaType + TYPE_SEPARATOR + nestedMessageName; } - ProtobufMessageBuilder nestedMessageBuilder = - new ProtobufMessageBuilder(nestedMessageName, messageBuilder); + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); generateMessageDefinitionForArrayType(nestedMessageBuilder, nestedArrayType, ARRAY_FIELD_NAME, 1); messageBuilder.addNestedMessage(nestedMessageBuilder); @@ -468,7 +487,7 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder if (isUnionMember) { // TODO: support array of map union member - throw createSerdesError(ARRAY_OF_MAP_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + throw createSerdesError(ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); } String nestedMessageName = MAP_BUILDER; @@ -482,6 +501,25 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder break; } + case TypeTags.TABLE_TAG: { + TableType tableType = (TableType) referredElementType; + + if (isUnionMember) { + // TODO: support array of table union member + throw createSerdesError(ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + } + + String nestedMessageName = TABLE_BUILDER; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName); + generateMessageDefinitionForTableType(nestedMessageBuilder, tableType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(REPEATED_LABEL, + nestedMessageName, fieldName, fieldNumber); + messageBuilder.addField(messageField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); } @@ -583,6 +621,23 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde break; } + case TypeTags.TABLE_TAG: { + if (fieldEntryType.getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { + throw createSerdesError(RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + } + + String nestedMessageName = fieldEntryType.getName() + TYPE_SEPARATOR + TABLE_BUILDER; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForTableType(nestedMessageBuilder, (TableType) referredFieldEntryType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldEntryName, fieldNumber); + messageBuilder.addField(messageField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredFieldEntryType.getName(), SERDES_ERROR); } @@ -685,6 +740,21 @@ private static void generateMessageDefinitionForMapType(ProtobufMessageBuilder m break; } + case TypeTags.TABLE_TAG: { + TableType tableType = (TableType) constrainedType; + String nestedMessageName = TABLE_BUILDER; + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + mapEntryBuilder); + generateMessageDefinitionForTableType(nestedMessageBuilder, tableType); + mapEntryBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder valueField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, VALUE_NAME, valueFieldNumber); + mapEntryBuilder.addField(valueField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); } @@ -694,4 +764,47 @@ private static void generateMessageDefinitionForMapType(ProtobufMessageBuilder m MAP_FIELD, 1); messageBuilder.addField(mapEntryField); } + + private static void generateMessageDefinitionForTableType(ProtobufMessageBuilder messageBuilder, + TableType tableType) { + int entryFieldNumber = 1; + Type constrainedType = tableType.getConstrainedType(); + Type referredConstrainedType = TypeUtils.getReferredType(constrainedType); + + switch (referredConstrainedType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: { + RecordType nestedRecordType = (RecordType) referredConstrainedType; + String nestedMessageName = nestedRecordType.getName(); + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForRecordType(nestedMessageBuilder, nestedRecordType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder valueField = new ProtobufMessageFieldBuilder(REPEATED_LABEL, + nestedMessageName, TABLE_ENTRY, entryFieldNumber); + messageBuilder.addField(valueField); + break; + } + + case TypeTags.MAP_TAG: { + MapType nestedMapType = (MapType) constrainedType; + String nestedMessageName = MAP_BUILDER; + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForMapType(nestedMessageBuilder, nestedMapType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder valueField = new ProtobufMessageFieldBuilder(REPEATED_LABEL, + nestedMessageName, TABLE_ENTRY, entryFieldNumber); + messageBuilder.addField(valueField); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); + } + + } } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java index 759e653..71cccbf 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java @@ -28,6 +28,7 @@ import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.TableType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -36,6 +37,7 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTable; import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.runtime.api.values.BValue; import io.ballerina.stdlib.serdes.protobuf.DataTypeMapper; @@ -59,6 +61,7 @@ import static io.ballerina.stdlib.serdes.Constants.SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.SERIALIZATION_ERROR_MESSAGE; import static io.ballerina.stdlib.serdes.Constants.STRING; +import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; import static io.ballerina.stdlib.serdes.Constants.TYPE_MISMATCH_ERROR_MESSAGE; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UNION_FIELD_NAME; @@ -136,6 +139,11 @@ private static Builder buildDynamicMessageFromType(Object anydata, Descriptor me return generateMessageForMapType(messageBuilder, ballerinaMap); } + case TypeTags.TABLE_TAG: { + BTable table = (BTable) anydata; + return generateMessageForTableType(messageBuilder, table); + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -244,6 +252,7 @@ private static Builder generateMessageForUnionType(Builder messageBuilder, Objec return messageBuilder; } // TODO: support map + // TODO: support table } BValue bValue = (BValue) anydata; @@ -329,6 +338,15 @@ private static Builder generateMessageForArrayType(Builder messageBuilder, Field break; } + case TypeTags.TABLE_TAG: { + Descriptor nestedSchema = fieldDescriptor.getMessageType(); + Builder nestedMessageBuilder = DynamicMessage.newBuilder(nestedSchema); + BTable table = (BTable) element; + DynamicMessage tableMessage = generateMessageForTableType(nestedMessageBuilder, table).build(); + messageBuilder.addRepeatedField(fieldDescriptor, tableMessage); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); } @@ -400,6 +418,14 @@ private static Builder generateMessageForRecordType(Builder messageBuilder, BMap break; } + case TypeTags.TABLE_TAG: { + BTable table = (BTable) recordFieldValue; + Builder tableBuilder = DynamicMessage.newBuilder(fieldDescriptor.getMessageType()); + DynamicMessage nestedMessage = generateMessageForTableType(tableBuilder, table).build(); + messageBuilder.setField(fieldDescriptor, nestedMessage); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredRecordFieldType.getName(), SERDES_ERROR); } @@ -485,9 +511,50 @@ private static Builder generateMessageForMapType(Builder messageBuilder, BMap table = (BTable) value; + Builder tableBuilder = DynamicMessage.newBuilder(valueFieldDescriptor.getMessageType()); + DynamicMessage nestedMapMessage = generateMessageForTableType(tableBuilder, table).build(); + mapFieldMessage.setField(valueFieldDescriptor, nestedMapMessage); + messageBuilder.addRepeatedField(mapFieldDescriptor, mapFieldMessage.build()); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); + } + } + return messageBuilder; + } + + + private static Builder generateMessageForTableType(Builder messageBuilder, BTable table) { + Type constrainedType = ((TableType) TypeUtils.getType(table)).getConstrainedType(); + Type referredConstrainedType = TypeUtils.getReferredType(constrainedType); + FieldDescriptor tableEntryField = messageBuilder.getDescriptorForType().findFieldByName(TABLE_ENTRY); + Descriptor tableEntrySchema = tableEntryField.getMessageType(); + + for (Object value : table.values()) { + @SuppressWarnings("unchecked") BMap recordOrMap = (BMap) value; + // Create a nested message for each entry + Builder nestedMessageBuilder = DynamicMessage.newBuilder(tableEntrySchema); + + switch (constrainedType.getTag()) { + case TypeTags.RECORD_TYPE_TAG: { + generateMessageForRecordType(nestedMessageBuilder, recordOrMap); + break; + } + + case TypeTags.MAP_TAG: { + generateMessageForMapType(nestedMessageBuilder, recordOrMap); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); + } + messageBuilder.addRepeatedField(tableEntryField, nestedMessageBuilder.build()); } return messageBuilder; } From 75bd7f406747275ea5cfc4772dc1d01911e5f0b7 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Tue, 28 Jun 2022 12:39:23 +0530 Subject: [PATCH 2/6] Add support to ballerina tuple --- ballerina/tests/errors_test.bal | 77 ++++- ballerina/tests/map_type_test.bal | 19 ++ .../tests/multi_dimentional_array_test.bal | 14 + ballerina/tests/record_type_test.bal | 61 +++- ballerina/tests/table_type_test.bal | 1 - ballerina/tests/tuple_type_test.bal | 138 ++++++++ ballerina/tests/union_type_test.bal | 29 ++ .../io/ballerina/stdlib/serdes/Constants.java | 36 +- .../ballerina/stdlib/serdes/Deserializer.java | 157 ++++++++- .../stdlib/serdes/SchemaGenerator.java | 308 ++++++++++++++++-- .../ballerina/stdlib/serdes/Serializer.java | 161 ++++++++- .../io/ballerina/stdlib/serdes/Utils.java | 20 +- 12 files changed, 949 insertions(+), 72 deletions(-) create mode 100644 ballerina/tests/tuple_type_test.bal diff --git a/ballerina/tests/errors_test.bal b/ballerina/tests/errors_test.bal index a3513d5..ab75c94 100644 --- a/ballerina/tests/errors_test.bal +++ b/ballerina/tests/errors_test.bal @@ -98,7 +98,7 @@ type RecordWithNonReferencedMapField record { @test:Config {} public isolated function testRecordWithNonReferencedMapFieldError() returns error? { - string expectedErrorMsg = "Record field of map type only supported with reference map type"; + string expectedErrorMsg = "Record field with map type only supported with reference map type"; Proto3Schema|error ser = new(RecordWithNonReferencedMapField); @@ -113,7 +113,7 @@ type RecordWithNonReferencedTableField record { @test:Config {} public isolated function testRecordWithNonReferencedTableFieldError() returns error? { - string expectedErrorMsg = "Record field of table type only supported with reference table type"; + string expectedErrorMsg = "Record field with table type only supported with reference table type"; Proto3Schema|error ser = new(RecordWithNonReferencedTableField); @@ -122,6 +122,22 @@ public isolated function testRecordWithNonReferencedTableFieldError() returns er test:assertEquals(err.message(), expectedErrorMsg); } +type RecordWithNonReferencedArrayTuple record { + [int, int][] field1; +}; + +@test:Config {} +public isolated function testRecordNonReferencedArrayOfTuplesError() returns error? { + string expectedErrorMsg = "Record field with array of tuple type member only supported " + + "with reference tuple type"; + + Proto3Schema|error ser = new (RecordWithNonReferencedArrayTuple); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + type TableA table>; type TableB map; type UnionOfTables TableA|TableA; @@ -149,3 +165,60 @@ public isolated function testTableArrayUnionMemberNotYetSupporteError() returns Error err = ser; test:assertEquals(err.message(), expectedErrorMsg); } + +type RecordWithMapArrayField record { + AgeMap[] ages; + MapRecord[] mapRecords; +}; + +@test:Config {} +public isolated function testRecordWithMapArrayNotYetSupportedError() returns error? { + string expectedErrorMsg = "Serdes not yet support array of maps as record field"; + + Proto3Schema|error ser = new(RecordWithMapArrayField); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + +type UnionWithNonReferenceTupleMembers [int, byte]|[string, decimal]; + +@test:Config {} +public isolated function testUnionWithNonReferencedTupleTypeError() returns error? { + string expectedErrorMsg = "Union with tuple type member only supported with reference tuple type"; + + Proto3Schema|error ser = new(UnionWithNonReferenceTupleMembers); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + +type UnionWithNonReferencedArrayOfTupleMembers [int, byte][]|[string, decimal][]; + +@test:Config {} +public isolated function testUnionWithNonReferencedArrayOfTuplesError() returns error? { + string expectedErrorMsg = "Union with array of tuple type member only " + + "supported with reference tuple type"; + + Proto3Schema|error ser = new (UnionWithNonReferencedArrayOfTupleMembers); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} + +type TupleWithNonReferenceArrayOfTuple [[int,int][], [boolean, float][]]; + +@test:Config {} +public isolated function testTupleWithNonReferenceArrayOfTuplesTypeError() returns error? { + string expectedErrorMsg = "Tuple element with array of tuple type member only supported " + + "with reference tuple type"; + + Proto3Schema|error ser = new (TupleWithNonReferenceArrayOfTuple); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} \ No newline at end of file diff --git a/ballerina/tests/map_type_test.bal b/ballerina/tests/map_type_test.bal index 8d1a7a3..d825e93 100644 --- a/ballerina/tests/map_type_test.bal +++ b/ballerina/tests/map_type_test.bal @@ -39,6 +39,8 @@ type RecordWithMapField record { AgeMap ages; }; +type MapWithTuple map; + @test:Config{} public isolated function testMapInt() returns error? { @@ -234,3 +236,20 @@ public isolated function testMapFieldinRecord() returns error? { test:assertEquals(decode, data); } + +@test:Config{} +public isolated function testMapWithTupleElement() returns error? { + MapWithTuple data = { + "first": ["serdes", 1.2], + "second": ["module", 2.4] + }; + + Proto3Schema ser = check new(MapWithTuple); + byte[] encode = check ser.serialize(data); + + Proto3Schema des = check new(MapWithTuple); + MapWithTuple decode = check des.deserialize(encode); + + test:assertEquals(decode, data); +} + diff --git a/ballerina/tests/multi_dimentional_array_test.bal b/ballerina/tests/multi_dimentional_array_test.bal index d0e1ff0..131cee6 100644 --- a/ballerina/tests/multi_dimentional_array_test.bal +++ b/ballerina/tests/multi_dimentional_array_test.bal @@ -43,6 +43,8 @@ type AgeMap map; type AgeMapArray AgeMap[]; type AgeMap2DArray AgeMapArray[]; +type ArrayOfTuples TupleWithUnion[][]; + @test:Config {} public isolated function testInt2DArray() returns error? { Int2DArray data = [ @@ -195,3 +197,15 @@ public isolated function test2DArrayofMaps() returns error? { AgeMap2DArray decoded = check des.deserialize(encoded); test:assertEquals(decoded, data); } + +@test:Config {} +public isolated function testArrayOfTuples() returns error? { + ArrayOfTuples value = [[["serdes", 1.2d]],[["module", 3.4]]]; + + Proto3Schema ser = check new (ArrayOfTuples); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (ArrayOfTuples); + ArrayOfTuples decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} diff --git a/ballerina/tests/record_type_test.bal b/ballerina/tests/record_type_test.bal index 3e80e75..ac18d23 100644 --- a/ballerina/tests/record_type_test.bal +++ b/ballerina/tests/record_type_test.bal @@ -108,6 +108,23 @@ type Nested3 record { Nested1? nested; }; +type UnionField1 int|int[]|string; +type UnionField2 decimal|int[]; +type RecordWithUnionArrayField record { + UnionField1[] feild1; + UnionField2[] feild2; +}; + +type RecordWithTuple record { + TupleWithUnion field1; + PrimitiveTuple field2; +}; + +type RecordWithArrayOfTuple record { + TupleWithUnion[][] field1; + PrimitiveTuple[] field2; +}; + @test:Config {} public isolated function testRecordWithPrimitives() returns error? { Employee jhon = { @@ -230,6 +247,18 @@ public isolated function testRecordWithUnionFields() returns error? { test:assertEquals(decoded, rec); } +@test:Config {} +public isolated function testRecordWithUnionArrayField() returns error? { + RecordWithUnionArrayField rec = {feild1: ["serdes"], feild2: [2.3e10]}; + + Proto3Schema ser = check new (RecordWithUnionArrayField); + byte[] encoded = check ser.serialize(rec); + + Proto3Schema des = check new (RecordWithUnionArrayField); + RecordWithUnionArrayField decoded = check des.deserialize(encoded); + test:assertEquals(decoded, rec); +} + @test:Config {} public function testRecordWithMultidimentionalArrays() returns error? { Proto3Schema ser = check new Proto3Schema(RecordWithMultidimentionalArrays); @@ -289,7 +318,6 @@ public function testNestedRecordWithCyclicReference() returns error? { }; Proto3Schema ser = check new (Nested1); - check ser.generateProtoFile("Nested.proto"); byte[] encode = check ser.serialize(data); Proto3Schema des = check new (Nested1); @@ -297,3 +325,34 @@ public function testNestedRecordWithCyclicReference() returns error? { test:assertEquals(decoded, data); } + +@test:Config {} +public isolated function testRecordWithTupleField() returns error? { + RecordWithTuple value = { + field1: ["serdes", 4.5], + field2: [100, 30000, 0.0, false, "serdes", 4.999999999d] + }; + + Proto3Schema ser = check new (RecordWithTuple); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (RecordWithTuple); + RecordWithTuple decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testRecordWithArrayOfTuple() returns error? { + RecordWithArrayOfTuple value = { + field1: [[["serdes", 4.5]]], + field2: [[100, 30000, 0.0, false, "serdes", 4.999999999d]] + }; + + Proto3Schema ser = check new (RecordWithArrayOfTuple); + check ser.generateProtoFile("A.proto"); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (RecordWithArrayOfTuple); + RecordWithArrayOfTuple decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} \ No newline at end of file diff --git a/ballerina/tests/table_type_test.bal b/ballerina/tests/table_type_test.bal index 0174d32..3a70405 100644 --- a/ballerina/tests/table_type_test.bal +++ b/ballerina/tests/table_type_test.bal @@ -147,7 +147,6 @@ public isolated function testTableAsMapConstraint() returns error? { }; Proto3Schema ser = check new(MapWithTableConstraint); - check ser.generateProtoFile("MapWithTableConstraint.proto"); byte[] encoded = check ser.serialize(data); Proto3Schema des = check new(MapWithTableConstraint); diff --git a/ballerina/tests/tuple_type_test.bal b/ballerina/tests/tuple_type_test.bal new file mode 100644 index 0000000..8c0a77e --- /dev/null +++ b/ballerina/tests/tuple_type_test.bal @@ -0,0 +1,138 @@ +// Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + +// WSO2 Inc. 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. + +import ballerina/test; + +type PrimitiveTuple [byte, int, float, boolean, string ,decimal]; +type TupleWithUnion [byte|string, decimal|boolean]; +type UnionTupleElement byte|string; +type TupleWithArray [string[], boolean[][], int[][][], UnionTupleElement[]]; +type TupleWithRecord [Student, Teacher]; +type TupleWithMap [map, map]; +type TupleWithTable [table>, table]; +type TupleOfTuples [PrimitiveTuple, TupleWithUnion]; +type TupleWithTupleArrays [PrimitiveTuple[], TupleWithUnion[][]]; + +@test:Config {} +public isolated function testTupleWithPrimitive() returns error? { + PrimitiveTuple value = [254, 100000, 1.2, true, "serdes", 3.2e-5]; + + Proto3Schema ser = check new (PrimitiveTuple); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (PrimitiveTuple); + PrimitiveTuple decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithUnionElements() returns error? { + TupleWithUnion value = ["serdes", 3.2e-5]; + + Proto3Schema ser = check new (TupleWithUnion); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithUnion); + TupleWithUnion decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithArrayElements() returns error? { + TupleWithArray value = [["serdes"], [[true, false], [false]], [[[1]]], ["serdes"]]; + + Proto3Schema ser = check new (TupleWithArray); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithArray); + TupleWithArray decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithRecordElements() returns error? { + TupleWithRecord value = [{name: "Linus Torvalds", courseId: 123, fees: 3.4e10}, {name: "Andrew S. Tanenbaum", courseId: 123, salary: 3.4e10 * 10}]; + + Proto3Schema ser = check new (TupleWithRecord); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithRecord); + TupleWithRecord decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithMapElements() returns error? { + TupleWithMap value = [ + {"a": 10, "b": 20, c: 30}, + { + "Linux": {name: "Linus Torvalds", courseId: 123, fees: 3.4e10}, + "Minix": {name: "Andrew S. Tanenbaum", courseId: 123, fees: 3.4e10} + } + ]; + + Proto3Schema ser = check new (TupleWithMap); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithMap); + TupleWithMap decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithTableElements() returns error? { + TupleWithTable value = [ + table [ + {"a": 10}, + {"b": 20, c: 30} + ], + table [ + {name: "Linus Torvalds", courseId: 123, fees: 3.4e10}, + {name: "Andrew S. Tanenbaum", courseId: 123, fees: 3.4e10} + ] + ]; + + Proto3Schema ser = check new (TupleWithTable); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithTable); + TupleWithTable decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithTupleElements() returns error? { + TupleOfTuples value = [[254, 100000, 1.2, true, "serdes", 3.2e-5], ["serdes", 3.4e10]]; + + Proto3Schema ser = check new (TupleOfTuples); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleOfTuples); + TupleOfTuples decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} + +@test:Config {} +public isolated function testTupleWithArrayOfTupleElements() returns error? { + TupleWithTupleArrays value = [[[254, 100000, 1.2, true, "serdes", 3.2e-5]], [[["serdes", 3.4e10]]]]; + + Proto3Schema ser = check new (TupleWithTupleArrays); + byte[] encoded = check ser.serialize(value); + + Proto3Schema des = check new (TupleWithTupleArrays); + TupleWithTupleArrays decoded = check des.deserialize(encoded); + test:assertEquals(decoded, value); +} diff --git a/ballerina/tests/union_type_test.bal b/ballerina/tests/union_type_test.bal index f0d99f4..8b8e12a 100644 --- a/ballerina/tests/union_type_test.bal +++ b/ballerina/tests/union_type_test.bal @@ -49,6 +49,11 @@ type UnionWithRecord int|string[]|UnionMember|(); type CompleUnion UnionOfPrimitiveAndArrays|UnionOfUnionArray|UnionWithRecord; type UnionWithArrayOfMaps MapString[]|MapInt[]; +type TupleA [int, string]; +type TupleB [boolean, decimal]; +type UnionOfTuples TupleA | TupleB; +type UnionOfTupleArrays TupleA[] | TupleB[]; + @test:Config {} public isolated function testPrimitiveUnion() returns error? { PrimitiveUnion nums = 3.9d; @@ -146,3 +151,27 @@ public isolated function testComplexUnion() returns error? { CompleUnion decoded = check des.deserialize(encoded); test:assertEquals(decoded, member); } + +@test:Config {} +public isolated function testUnionOfTuples() returns error? { + UnionOfTuples data = [10, "serdes"]; + + Proto3Schema ser = check new (UnionOfTuples); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new (UnionOfTuples); + UnionOfTuples decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} + +@test:Config {} +public isolated function testUnionOfTupleArrays() returns error? { + UnionOfTupleArrays data = [[10, "serdes"]]; + + Proto3Schema ser = check new (UnionOfTupleArrays); + byte[] encoded = check ser.serialize(data); + + Proto3Schema des = check new (UnionOfTupleArrays); + UnionOfTupleArrays decoded = check des.deserialize(encoded); + test:assertEquals(decoded, data); +} \ No newline at end of file diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java index aadf9fe..d7f5481 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java @@ -38,7 +38,7 @@ private Constants() {} // Constants related to java type public static final String INTEGER = "Integer"; - + // Constants related to protobuf schema public static final String SCHEMA_NAME = "schema"; public static final String UNION_BUILDER_NAME = "UnionBuilder"; @@ -60,6 +60,8 @@ private Constants() {} public static final String VALUE_NAME = "value"; public static final String TABLE_BUILDER = "TableBuilder"; public static final String TABLE_ENTRY = "TableEntry"; + public static final String TUPLE_BUILDER = "TupleBuilder"; + public static final String TUPLE_FIELD_NAME = "element"; public static final String SEPARATOR = "_"; public static final String TYPE_SEPARATOR = "___"; @@ -82,13 +84,31 @@ private Constants() {} public static final String FAILED_WRITE_FILE = "Failed to write proto file: "; public static final String MAP_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support map type as union member"; public static final String TABLE_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support table type as union member"; - public static final String ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of maps as" - + " union member"; - public static final String ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of tables as " - + "union member"; - public static final String RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field of map type " + public static final String ARRAY_OF_MAP_AS_UNION_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of maps" + + " as union member"; + public static final String ARRAY_OF_MAP_AS_RECORD_FIELD_NOT_YET_SUPPORTED = "Serdes not yet support array of maps" + + " as record field"; + public static final String ARRAY_OF_MAP_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED = "Serdes not yet support array of maps" + + " as tuple element"; + public static final String ARRAY_OF_TABLE_AS_UNION_MEMBER_NOT_YET_SUPPORTED = "Serdes not yet support array of" + + " tables as union member"; + public static final String ARRAY_OF_TABLE_AS_RECORD_FIELD_NOT_YET_SUPPORTED = "Serdes not yet support array of" + + " tables as record field"; + public static final String ARRAY_OF_TABLE_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED = "Serdes not yet support array of" + + " tables as tuple element"; + public static final String RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with map type " + "only supported with reference map type"; - public static final String RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field of table type " - + "only supported with reference table type"; + public static final String RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with table " + + "type only supported with reference table type"; + public static final String RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with tuple " + + "type only supported with reference table type"; + public static final String RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with" + + " array of tuple type member only supported with reference tuple type"; + public static final String UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Union with tuple type" + + " member only supported with reference tuple type"; + public static final String UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Union with " + + "array of tuple type member only supported with reference tuple type"; + public static final String TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Tuple element " + + "with array of tuple type member only supported with reference tuple type"; public static final BString BALLERINA_TYPEDESC_ATTRIBUTE_NAME = StringUtils.fromString("dataType"); } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java b/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java index 70fb69f..ef574f7 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Deserializer.java @@ -29,6 +29,7 @@ import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TableType; +import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.StringUtils; @@ -62,6 +63,7 @@ import static io.ballerina.stdlib.serdes.Constants.SCHEMA_NAME; import static io.ballerina.stdlib.serdes.Constants.SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; +import static io.ballerina.stdlib.serdes.Constants.TUPLE_BUILDER; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UNSUPPORTED_DATA_TYPE; import static io.ballerina.stdlib.serdes.Constants.VALUE; @@ -139,6 +141,10 @@ private static Object dynamicMessageToBallerinaType(DynamicMessage dynamicMessag return getTableTypeValueFromMessage(dynamicMessage, (TableType) referredType); } + case TypeTags.TUPLE_TAG: { + return getTupleTypeValueFromMessage(dynamicMessage, (TupleType) referredType); + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -188,8 +194,8 @@ private static Object getUnionTypeValueFromMessage(DynamicMessage dynamicMessage return null; } - // Handle array values if (fieldDescriptor.isRepeated()) { + // Handle array values String fieldName = fieldDescriptor.getName(); String[] tokens = fieldName.split(TYPE_SEPARATOR); String ballerinaTypeName = tokens[0]; @@ -197,11 +203,23 @@ private static Object getUnionTypeValueFromMessage(DynamicMessage dynamicMessage ArrayType arrayType = getBallerinaArrayTypeFromUnion((UnionType) type, ballerinaTypeName, dimention); return getArrayTypeValueFromMessage(value, arrayType.getElementType(), messageDescriptor, dimention, ballerinaTypeName); - } else if (value instanceof ByteString && fieldDescriptor.getName().contains(ARRAY_FIELD_NAME)) { + } + + if (value instanceof ByteString && fieldDescriptor.getName().contains(ARRAY_FIELD_NAME)) { // Handle byte array values ByteString byteString = (ByteString) value; return ValueCreator.createArrayValue(byteString.toByteArray()); - } else if (value instanceof DynamicMessage && !fieldDescriptor.getMessageType().getName() + } + + if (value instanceof DynamicMessage && + fieldDescriptor.getMessageType().getName().contains(TUPLE_BUILDER)) { + // Handle tuple values + String ballerinaTypeName = fieldDescriptor.getMessageType().getName().split(TYPE_SEPARATOR)[0]; + TupleType tupleType = getBallerinaTupleTypeFromUnion((UnionType) type, ballerinaTypeName); + return getTupleTypeValueFromMessage((DynamicMessage) value, tupleType); + } + + if (value instanceof DynamicMessage && !fieldDescriptor.getMessageType().getName() .contains(DECIMAL_VALUE)) { // Handle record values String fieldName = fieldDescriptor.getName(); @@ -209,10 +227,10 @@ private static Object getUnionTypeValueFromMessage(DynamicMessage dynamicMessage String ballerinaType = tokens[0]; RecordType recordType = getBallerinaRecordTypeFromUnion((UnionType) type, ballerinaType); return getRecordTypeValueFromMessage((DynamicMessage) value, recordType); - } else { - // Handle primitive values - return getPrimitiveTypeValueFromMessage(value); } + + // Handle primitive values + return getPrimitiveTypeValueFromMessage(value); } throw createSerdesError(UNSUPPORTED_DATA_TYPE + type.getName(), SERDES_ERROR); @@ -299,8 +317,15 @@ private static Object getArrayTypeValueFromMessage(Object value, Type elementTyp case TypeTags.TABLE_TAG: { TableType tableType = (TableType) referredElementType; - Object map = getTableTypeValueFromMessage((DynamicMessage) element, tableType); - bArray.append(map); + Object table = getTableTypeValueFromMessage((DynamicMessage) element, tableType); + bArray.append(table); + break; + } + + case TypeTags.TUPLE_TAG: { + TupleType tupleType = (TupleType) referredElementType; + Object tuple = getTupleTypeValueFromMessage((DynamicMessage) element, tupleType); + bArray.append(tuple); break; } @@ -349,8 +374,8 @@ private static Object getRecordTypeValueFromMessage(DynamicMessage dynamicMessag ArrayType arrayType = (ArrayType) referredEntryFieldType; Descriptor recordSchema = fieldDescriptor.getContainingType(); - String ballerinaTypeName = Utils.getElementTypeOfBallerinaArray(arrayType); - int dimention = Utils.getDimensions(arrayType); + String ballerinaTypeName = Utils.getElementTypeNameOfBallerinaArray(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); ballerinaValue = getArrayTypeValueFromMessage(value, arrayType.getElementType(), recordSchema, dimention, ballerinaTypeName); @@ -378,6 +403,13 @@ private static Object getRecordTypeValueFromMessage(DynamicMessage dynamicMessag break; } + case TypeTags.TUPLE_TAG: { + Object tupleMessage = dynamicMessage.getField(fieldDescriptor); + ballerinaValue = getTupleTypeValueFromMessage((DynamicMessage) tupleMessage, + (TupleType) referredEntryFieldType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredEntryFieldType.getName(), SERDES_ERROR); } @@ -430,8 +462,8 @@ private static Object getMapTypeValueFromMessage(DynamicMessage dynamicMessage, ArrayType arrayType = (ArrayType) referredConstrainedType; Descriptor recordSchema = valueFieldDescriptor.getContainingType(); - String ballerinaTypeName = Utils.getElementTypeOfBallerinaArray(arrayType); - int dimention = Utils.getDimensions(arrayType); + String ballerinaTypeName = Utils.getElementTypeNameOfBallerinaArray(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); ballerinaValue = getArrayTypeValueFromMessage(value, arrayType.getElementType(), recordSchema, dimention, ballerinaTypeName); @@ -456,6 +488,12 @@ private static Object getMapTypeValueFromMessage(DynamicMessage dynamicMessage, break; } + case TypeTags.TUPLE_TAG: { + ballerinaValue = getTupleTypeValueFromMessage((DynamicMessage) value, + (TupleType) referredConstrainedType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); } @@ -498,6 +536,97 @@ private static Object getTableTypeValueFromMessage(DynamicMessage dynamicMessage return table; } + private static Object getTupleTypeValueFromMessage(DynamicMessage dynamicMessage, TupleType tupleType) { + BArray tuple = ValueCreator.createTupleValue(tupleType); + for (Map.Entry tupleField : dynamicMessage.getAllFields().entrySet()) { + int tupleElementIndex = tupleField.getKey().getNumber() - 1; + Type tupleElementType = tupleType.getTupleTypes().get(tupleElementIndex); + Type referredElementType = TypeUtils.getReferredType(tupleElementType); + Object tupleFieldValue = tupleField.getValue(); + Object ballerinaValue; + + switch (referredElementType.getTag()) { + case TypeTags.INT_TAG: + case TypeTags.BYTE_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.STRING_TAG: + case TypeTags.BOOLEAN_TAG: { + ballerinaValue = getPrimitiveTypeValueFromMessage(tupleFieldValue); + break; + } + + case TypeTags.DECIMAL_TAG: { + ballerinaValue = getDecimalPrimitiveTypeValueFromMessage((DynamicMessage) tupleFieldValue); + break; + } + + case TypeTags.UNION_TAG: { + ballerinaValue = getUnionTypeValueFromMessage((DynamicMessage) tupleFieldValue, + referredElementType); + break; + } + + case TypeTags.ARRAY_TAG: { + ArrayType arrayType = (ArrayType) referredElementType; + Descriptor recordSchema = tupleField.getKey().getContainingType(); + + String ballerinaTypeName = Utils.getElementTypeNameOfBallerinaArray(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); + + ballerinaValue = getArrayTypeValueFromMessage(tupleFieldValue, arrayType.getElementType(), + recordSchema, dimention, ballerinaTypeName); + break; + } + + case TypeTags.RECORD_TYPE_TAG: { + ballerinaValue = getRecordTypeValueFromMessage((DynamicMessage) tupleFieldValue, + (RecordType) referredElementType); + break; + } + + case TypeTags.MAP_TAG: { + ballerinaValue = getMapTypeValueFromMessage((DynamicMessage) tupleFieldValue, + (MapType) referredElementType); + break; + } + + case TypeTags.TABLE_TAG: { + ballerinaValue = getTableTypeValueFromMessage((DynamicMessage) tupleFieldValue, + (TableType) referredElementType); + break; + } + + case TypeTags.TUPLE_TAG: { + ballerinaValue = getTupleTypeValueFromMessage((DynamicMessage) tupleFieldValue, + (TupleType) referredElementType); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); + + } + tuple.add(tupleElementIndex, ballerinaValue); + } + return tuple; + } + + private static TupleType getBallerinaTupleTypeFromUnion(UnionType unionType, String targetBallerinaTypeName) { + TupleType targetTupleType = null; + + for (Type memberType : unionType.getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + if (memberType.getTag() == TypeTags.TUPLE_TAG) { + String tupleTypeName = memberType.getName(); + if (tupleTypeName.equals(targetBallerinaTypeName)) { + targetTupleType = (TupleType) memberType; + break; + } + } + } + return targetTupleType; + } + private static RecordType getBallerinaRecordTypeFromUnion(UnionType unionType, String targetBallerinaTypeName) { RecordType targetRecordType = null; @@ -521,8 +650,8 @@ private static ArrayType getBallerinaArrayTypeFromUnion(UnionType unionType, Str for (Type memberType : unionType.getMemberTypes()) { memberType = TypeUtils.getReferredType(memberType); if (memberType.getTag() == TypeTags.ARRAY_TAG) { - String arrayBasicType = Utils.getElementTypeOfBallerinaArray((ArrayType) memberType); - int arrayDimention = Utils.getDimensions((ArrayType) memberType); + String arrayBasicType = Utils.getElementTypeNameOfBallerinaArray((ArrayType) memberType); + int arrayDimention = Utils.getArrayDimensions((ArrayType) memberType); if (arrayDimention == dimention && arrayBasicType.equals(targetBallerinaTypeName)) { targetArrayType = (ArrayType) memberType; break; diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java index beca2ea..7b9c3d2 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java @@ -24,6 +24,7 @@ import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TableType; +import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.utils.TypeUtils; @@ -48,12 +49,17 @@ import static com.google.protobuf.Descriptors.DescriptorValidationException; import static io.ballerina.stdlib.serdes.Constants.ARRAY_BUILDER_NAME; import static io.ballerina.stdlib.serdes.Constants.ARRAY_FIELD_NAME; -import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED; -import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_AS_RECORD_FIELD_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_MAP_AS_UNION_MEMBER_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_TABLE_AS_RECORD_FIELD_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_TABLE_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.ARRAY_OF_TABLE_AS_UNION_MEMBER_NOT_YET_SUPPORTED; import static io.ballerina.stdlib.serdes.Constants.ATOMIC_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.BOOL; import static io.ballerina.stdlib.serdes.Constants.BYTES; import static io.ballerina.stdlib.serdes.Constants.DECIMAL_VALUE; +import static io.ballerina.stdlib.serdes.Constants.EMPTY_STRING; import static io.ballerina.stdlib.serdes.Constants.FAILED_WRITE_FILE; import static io.ballerina.stdlib.serdes.Constants.KEY_NAME; import static io.ballerina.stdlib.serdes.Constants.MAP_BUILDER; @@ -67,6 +73,8 @@ import static io.ballerina.stdlib.serdes.Constants.PROTO3; import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; +import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; +import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.REPEATED_LABEL; import static io.ballerina.stdlib.serdes.Constants.SCALE; import static io.ballerina.stdlib.serdes.Constants.SCHEMA_GENERATION_FAILURE; @@ -75,10 +83,16 @@ import static io.ballerina.stdlib.serdes.Constants.STRING; import static io.ballerina.stdlib.serdes.Constants.TABLE_BUILDER; import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; +import static io.ballerina.stdlib.serdes.Constants.TABLE_MEMBER_NOT_YET_SUPPORTED; +import static io.ballerina.stdlib.serdes.Constants.TUPLE_BUILDER; +import static io.ballerina.stdlib.serdes.Constants.TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; +import static io.ballerina.stdlib.serdes.Constants.TUPLE_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UINT32; import static io.ballerina.stdlib.serdes.Constants.UNION_BUILDER_NAME; import static io.ballerina.stdlib.serdes.Constants.UNION_FIELD_NAME; +import static io.ballerina.stdlib.serdes.Constants.UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; +import static io.ballerina.stdlib.serdes.Constants.UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.UNSUPPORTED_DATA_TYPE; import static io.ballerina.stdlib.serdes.Constants.VALUE; import static io.ballerina.stdlib.serdes.Constants.VALUE_NAME; @@ -189,12 +203,20 @@ private static ProtobufMessageBuilder buildProtobufMessageFromBallerinaTypedesc( case TypeTags.TABLE_TAG: { TableType tableType = (TableType) referredType; - messageName = Constants.TABLE_BUILDER; + messageName = TABLE_BUILDER; messageBuilder = new ProtobufMessageBuilder(messageName); generateMessageDefinitionForTableType(messageBuilder, tableType); break; } + case TypeTags.TUPLE_TAG: { + TupleType tupleType = (TupleType) referredType; + messageName = TUPLE_BUILDER; + messageBuilder = new ProtobufMessageBuilder(messageName); + generateMessageDefinitionForTupleType(messageBuilder, tupleType); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -234,8 +256,8 @@ private static Map.Entry mapUnionMemberToMapEntry(Type type) { String typeName = referredType.getName(); if (referredType.getTag() == TypeTags.ARRAY_TAG) { - int dimention = Utils.getDimensions((ArrayType) referredType); - typeName = Utils.getElementTypeOfBallerinaArray((ArrayType) referredType); + int dimention = Utils.getArrayDimensions((ArrayType) referredType); + typeName = Utils.getElementTypeNameOfBallerinaArray((ArrayType) referredType); String key = typeName + TYPE_SEPARATOR + ARRAY_FIELD_NAME + SEPARATOR + dimention + TYPE_SEPARATOR + UNION_FIELD_NAME; @@ -253,6 +275,15 @@ private static Map.Entry mapUnionMemberToMapEntry(Type type) { return Map.entry(NULL_FIELD_NAME, type); } + if (referredType.getTag() == TypeTags.TUPLE_TAG) { + if (!typeName.equals(EMPTY_STRING)) { + String key = typeName + TYPE_SEPARATOR + UNION_FIELD_NAME; + return Map.entry(key, type); + } else { + throw createSerdesError(UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + } + } + if (referredType.getTag() == TypeTags.MAP_TAG) { // TODO: support map member throw createSerdesError(MAP_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); @@ -260,7 +291,7 @@ private static Map.Entry mapUnionMemberToMapEntry(Type type) { if (referredType.getTag() == TypeTags.TABLE_TAG) { // TODO: support table member - throw createSerdesError(Constants.TABLE_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + throw createSerdesError(TABLE_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); } throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); @@ -315,11 +346,11 @@ private static void generateMessageDefinitionForUnionType(ProtobufMessageBuilder case TypeTags.ARRAY_TAG: { ArrayType arrayType = (ArrayType) referredMemberType; - int dimention = Utils.getDimensions(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); fieldName = ARRAY_FIELD_NAME + SEPARATOR + dimention; boolean isUnionMember = true; boolean isRecordField = false; - generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, dimention, fieldNumber, + generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, fieldNumber, dimention, isUnionMember, isRecordField); break; } @@ -347,6 +378,20 @@ private static void generateMessageDefinitionForUnionType(ProtobufMessageBuilder // TODO: support map member // TODO: support table member + case TypeTags.TUPLE_TAG: { + TupleType tupleType = (TupleType) referredMemberType; + String nestedMessageName = tupleType.getName() + TYPE_SEPARATOR + TUPLE_BUILDER; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName); + generateMessageDefinitionForTupleType(nestedMessageBuilder, tupleType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + fieldName = tupleType.getName() + TYPE_SEPARATOR + UNION_FIELD_NAME; + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, fieldNumber); + messageBuilder.addField(messageField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredMemberType.getName(), SERDES_ERROR); } @@ -357,19 +402,27 @@ private static void generateMessageDefinitionForUnionType(ProtobufMessageBuilder private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder messageBuilder, ArrayType arrayType, String fieldName, int fieldNumber, int dimention, boolean isRecordField) { - generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, dimention, fieldNumber, false, + generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, fieldNumber, dimention, false, isRecordField); } private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder messageBuilder, ArrayType arrayType, String fieldName, int fieldNumber) { - generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, -1, fieldNumber, false, false); + generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, fieldNumber, -1, false, false); } private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder messageBuilder, - ArrayType arrayType, String fieldName, int dimension, - int fieldNumber, boolean isUnionMember, + ArrayType arrayType, String fieldName, int fieldNumber, + int dimension, boolean isUnionMember, boolean isRecordField) { + generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, fieldNumber, dimension, + isUnionMember, isRecordField, false); + } + + private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder messageBuilder, + ArrayType arrayType, String fieldName, int fieldNumber, + int dimension, boolean isUnionMember, + boolean isRecordField, boolean isTupleElement) { Type elementType = arrayType.getElementType(); Type referredElementType = TypeUtils.getReferredType(elementType); @@ -417,9 +470,12 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder String nestedMessageName = UNION_BUILDER_NAME; if (isUnionMember) { - String ballerinaType = Utils.getElementTypeOfBallerinaArray(arrayType); + String ballerinaType = Utils.getElementTypeNameOfBallerinaArray(arrayType); nestedMessageName = ballerinaType + TYPE_SEPARATOR + nestedMessageName; fieldName = ballerinaType + TYPE_SEPARATOR + fieldName + TYPE_SEPARATOR + UNION_FIELD_NAME; + } else if (isRecordField || isTupleElement) { + String ballerinaType = Utils.getElementTypeNameOfBallerinaArray(arrayType); + nestedMessageName = ballerinaType + TYPE_SEPARATOR + nestedMessageName; } ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, @@ -438,12 +494,12 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder String nestedMessageName = ARRAY_BUILDER_NAME; if (isUnionMember) { - String ballerinaType = Utils.getElementTypeOfBallerinaArray(nestedArrayType); + String ballerinaType = Utils.getElementTypeNameOfBallerinaArray(nestedArrayType); nestedMessageName = ARRAY_BUILDER_NAME + SEPARATOR + (dimension - 1); nestedMessageName = ballerinaType + TYPE_SEPARATOR + nestedMessageName; fieldName = ballerinaType + TYPE_SEPARATOR + fieldName + TYPE_SEPARATOR + UNION_FIELD_NAME; - } else if (isRecordField) { - String ballerinaType = Utils.getElementTypeOfBallerinaArray(nestedArrayType); + } else if (isRecordField || isTupleElement) { + String ballerinaType = Utils.getElementTypeNameOfBallerinaArray(nestedArrayType); nestedMessageName = ARRAY_BUILDER_NAME + SEPARATOR + (dimension - 1); nestedMessageName = ballerinaType + TYPE_SEPARATOR + nestedMessageName; } @@ -484,13 +540,18 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder case TypeTags.MAP_TAG: { MapType mapType = (MapType) referredElementType; - + String nestedMessageName = MAP_BUILDER; if (isUnionMember) { - // TODO: support array of map union member - throw createSerdesError(ARRAY_OF_MAP_AS_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + // TODO: support array of map as union member + throw createSerdesError(ARRAY_OF_MAP_AS_UNION_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + } else if (isRecordField) { + // TODO: support array of map as record field + throw createSerdesError(ARRAY_OF_MAP_AS_RECORD_FIELD_NOT_YET_SUPPORTED, SERDES_ERROR); + } else if (isTupleElement) { + // TODO: support array of map as tuple element + throw createSerdesError(ARRAY_OF_MAP_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED, SERDES_ERROR); } - String nestedMessageName = MAP_BUILDER; ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName); generateMessageDefinitionForMapType(nestedMessageBuilder, mapType); messageBuilder.addNestedMessage(nestedMessageBuilder); @@ -506,7 +567,13 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder if (isUnionMember) { // TODO: support array of table union member - throw createSerdesError(ARRAY_OF_TABLE_AS_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + throw createSerdesError(ARRAY_OF_TABLE_AS_UNION_MEMBER_NOT_YET_SUPPORTED, SERDES_ERROR); + } else if (isRecordField) { + // TODO: support array of table as record field + throw createSerdesError(ARRAY_OF_TABLE_AS_RECORD_FIELD_NOT_YET_SUPPORTED, SERDES_ERROR); + } else if (isTupleElement) { + // TODO: support array of table as tuple element + throw createSerdesError(ARRAY_OF_TABLE_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED, SERDES_ERROR); } String nestedMessageName = TABLE_BUILDER; @@ -520,6 +587,44 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder break; } + case TypeTags.TUPLE_TAG: { + TupleType tupleType = (TupleType) referredElementType; + String nestedMessageName = TUPLE_BUILDER; + + if (isUnionMember) { + String ballerinaType = referredElementType.getName(); + if (ballerinaType.equals(EMPTY_STRING)) { + throw createSerdesError(UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, + SERDES_ERROR); + } + nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; + fieldName = ballerinaType + TYPE_SEPARATOR + fieldName + TYPE_SEPARATOR + UNION_FIELD_NAME; + } else if (isRecordField) { + String ballerinaType = referredElementType.getName(); + if (ballerinaType.equals(EMPTY_STRING)) { + throw createSerdesError(RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, + SERDES_ERROR); + } + nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; + } else if (isTupleElement) { + String ballerinaType = referredElementType.getName(); + if (ballerinaType.equals(EMPTY_STRING)) { + throw createSerdesError(TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, + SERDES_ERROR); + } + nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; + } + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName); + generateMessageDefinitionForTupleType(nestedMessageBuilder, tupleType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(REPEATED_LABEL, + nestedMessageName, fieldName, fieldNumber); + messageBuilder.addField(messageField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); } @@ -578,7 +683,7 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde case TypeTags.ARRAY_TAG: { ArrayType arrayType = (ArrayType) referredFieldEntryType; - int dimention = Utils.getDimensions(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); boolean isRecordField = true; generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldEntryName, fieldNumber, dimention, isRecordField); @@ -638,6 +743,23 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde break; } + case TypeTags.TUPLE_TAG: { + if (fieldEntryType.getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { + throw createSerdesError(RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + } + + String nestedMessageName = fieldEntryType.getName() + TYPE_SEPARATOR + TUPLE_BUILDER; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForTupleType(nestedMessageBuilder, (TupleType) referredFieldEntryType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldEntryName, fieldNumber); + messageBuilder.addField(messageField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredFieldEntryType.getName(), SERDES_ERROR); } @@ -699,7 +821,7 @@ private static void generateMessageDefinitionForMapType(ProtobufMessageBuilder m case TypeTags.ARRAY_TAG: { ArrayType arrayType = (ArrayType) referredConstrainedType; - int dimention = Utils.getDimensions(arrayType); + int dimention = Utils.getArrayDimensions(arrayType); boolean isRecordField = true; generateMessageDefinitionForArrayType(mapEntryBuilder, arrayType, VALUE_NAME, valueFieldNumber, dimention, isRecordField); @@ -755,6 +877,21 @@ private static void generateMessageDefinitionForMapType(ProtobufMessageBuilder m break; } + case TypeTags.TUPLE_TAG: { + TupleType tupleType = (TupleType) constrainedType; + String nestedMessageName = TUPLE_BUILDER; + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + mapEntryBuilder); + generateMessageDefinitionForTupleType(nestedMessageBuilder, tupleType); + mapEntryBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder valueField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, VALUE_NAME, valueFieldNumber); + mapEntryBuilder.addField(valueField); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredConstrainedType.getName(), SERDES_ERROR); } @@ -807,4 +944,129 @@ private static void generateMessageDefinitionForTableType(ProtobufMessageBuilder } } + + private static void generateMessageDefinitionForTupleType(ProtobufMessageBuilder messageBuilder, + TupleType tupleType) { + int elementFieldNumber = 1; + for (Type tupleElementType : tupleType.getTupleTypes()) { + Type referredTupleElementType = TypeUtils.getReferredType(tupleElementType); + String fieldName = TUPLE_FIELD_NAME + SEPARATOR + elementFieldNumber; + switch (referredTupleElementType.getTag()) { + case TypeTags.INT_TAG: + case TypeTags.BYTE_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.STRING_TAG: + case TypeTags.BOOLEAN_TAG: { + String protoType = DataTypeMapper.mapBallerinaTypeToProtoType(referredTupleElementType.getTag()); + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + protoType, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.DECIMAL_TAG: { + String protoType = DataTypeMapper.mapBallerinaTypeToProtoType(referredTupleElementType.getTag()); + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(protoType); + generateMessageDefinitionForPrimitiveDecimal(nestedMessageBuilder); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + protoType, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.UNION_TAG: { + String nestedMessageName = fieldName + TYPE_SEPARATOR + UNION_BUILDER_NAME; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForUnionType(nestedMessageBuilder, (UnionType) referredTupleElementType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.ARRAY_TAG: { + ArrayType arrayType = (ArrayType) referredTupleElementType; + int dimention = Utils.getArrayDimensions(arrayType); + boolean isRecordField = false; + boolean isUnionMember = false; + boolean isTupleElement = true; + generateMessageDefinitionForArrayType(messageBuilder, arrayType, fieldName, elementFieldNumber, + dimention, isUnionMember, isRecordField, isTupleElement); + break; + } + + case TypeTags.RECORD_TYPE_TAG: { + RecordType nestedRecordType = (RecordType) referredTupleElementType; + String nestedMessageName = nestedRecordType.getName(); + boolean hasMessageDefinition = messageBuilder.hasMessageDefinitionInMessageTree(nestedMessageName); + + // Check for cyclic reference in ballerina record + if (!hasMessageDefinition) { + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForRecordType(nestedMessageBuilder, nestedRecordType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + } + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.MAP_TAG: { + MapType nestedMapType = (MapType) referredTupleElementType; + String nestedMessageName = fieldName + SEPARATOR + MAP_BUILDER; + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForMapType(nestedMessageBuilder, nestedMapType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.TABLE_TAG: { + TableType tableType = (TableType) referredTupleElementType; + String nestedMessageName = fieldName + SEPARATOR + TABLE_BUILDER; + + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName, + messageBuilder); + generateMessageDefinitionForTableType(nestedMessageBuilder, tableType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + case TypeTags.TUPLE_TAG: { + TupleType nestedTupleType = (TupleType) referredTupleElementType; + String nestedMessageName = fieldName + TYPE_SEPARATOR + TUPLE_BUILDER; + ProtobufMessageBuilder nestedMessageBuilder = new ProtobufMessageBuilder(nestedMessageName); + generateMessageDefinitionForTupleType(nestedMessageBuilder, nestedTupleType); + messageBuilder.addNestedMessage(nestedMessageBuilder); + + ProtobufMessageFieldBuilder messageField = new ProtobufMessageFieldBuilder(OPTIONAL_LABEL, + nestedMessageName, fieldName, elementFieldNumber); + messageBuilder.addField(messageField); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredTupleElementType.getName(), SERDES_ERROR); + + } + elementFieldNumber++; + } + } } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java index 71cccbf..c29e4bf 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java @@ -29,6 +29,7 @@ import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.TableType; +import io.ballerina.runtime.api.types.TupleType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; @@ -62,6 +63,7 @@ import static io.ballerina.stdlib.serdes.Constants.SERIALIZATION_ERROR_MESSAGE; import static io.ballerina.stdlib.serdes.Constants.STRING; import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; +import static io.ballerina.stdlib.serdes.Constants.TUPLE_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.TYPE_MISMATCH_ERROR_MESSAGE; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UNION_FIELD_NAME; @@ -144,6 +146,11 @@ private static Builder buildDynamicMessageFromType(Object anydata, Descriptor me return generateMessageForTableType(messageBuilder, table); } + case TypeTags.TUPLE_TAG: { + BArray tuple = (BArray) anydata; + return generateMessageForTupleType(messageBuilder, tuple); + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredType.getName(), SERDES_ERROR); } @@ -217,23 +224,35 @@ private static Builder generateMessageForUnionType(Builder messageBuilder, Objec return generateMessageForPrimitiveType(messageBuilder, fieldDescriptor, anydata, ballerinaType); } - // Handle ballerina array + if (anydata instanceof BArray) { BArray bArray = (BArray) anydata; - ballerinaType = bArray.getElementType().getName(); - int dimention = 1; - - if (ballerinaType.equals(EMPTY_STRING)) { - // Get the base type of the ballerina multidimensional array - ballerinaType = Utils.getElementTypeOfBallerinaArray((ArrayType) bArray.getElementType()); - dimention += Utils.getDimensions((ArrayType) bArray.getElementType()); - } + // Handle ballerina tuple + if (bArray.getType().getTag() == TypeTags.TUPLE_TAG) { + String fieldName = bArray.getType().getName() + TYPE_SEPARATOR + UNION_FIELD_NAME; + FieldDescriptor tupleFieldDescriptor = messageDescriptor.findFieldByName(fieldName); + Descriptor tupleSchema = tupleFieldDescriptor.getMessageType(); + Builder tupleMessageBuilder = DynamicMessage.newBuilder(tupleSchema); + generateMessageForTupleType(tupleMessageBuilder, bArray); + messageBuilder.setField(tupleFieldDescriptor, tupleMessageBuilder.build()); + return messageBuilder; + } else { + // Handle ballerina array + ballerinaType = bArray.getElementType().getName(); + int dimention = 1; + + if (ballerinaType.equals(EMPTY_STRING)) { + // Get the base type of the ballerina multidimensional array + ballerinaType = Utils.getElementTypeNameOfBallerinaArray((ArrayType) bArray.getElementType()); + dimention += Utils.getArrayDimensions((ArrayType) bArray.getElementType()); + } - String fieldName = ballerinaType + TYPE_SEPARATOR + ARRAY_FIELD_NAME + SEPARATOR + dimention - + TYPE_SEPARATOR + UNION_FIELD_NAME; + String fieldName = ballerinaType + TYPE_SEPARATOR + ARRAY_FIELD_NAME + SEPARATOR + dimention + + TYPE_SEPARATOR + UNION_FIELD_NAME; - FieldDescriptor fieldDescriptor = messageDescriptor.findFieldByName(fieldName); - return generateMessageForArrayType(messageBuilder, fieldDescriptor, bArray); + FieldDescriptor fieldDescriptor = messageDescriptor.findFieldByName(fieldName); + return generateMessageForArrayType(messageBuilder, fieldDescriptor, bArray); + } } @@ -347,6 +366,15 @@ private static Builder generateMessageForArrayType(Builder messageBuilder, Field break; } + case TypeTags.TUPLE_TAG: { + Descriptor nestedSchema = fieldDescriptor.getMessageType(); + Builder nestedMessageBuilder = DynamicMessage.newBuilder(nestedSchema); + BArray tuple = (BArray) element; + DynamicMessage tupleMessage = generateMessageForTupleType(nestedMessageBuilder, tuple).build(); + messageBuilder.addRepeatedField(fieldDescriptor, tupleMessage); + break; + } + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); } @@ -426,6 +454,15 @@ private static Builder generateMessageForRecordType(Builder messageBuilder, BMap break; } + case TypeTags.TUPLE_TAG: { + BArray tuple = (BArray) recordFieldValue; + Builder tableBuilder = DynamicMessage.newBuilder(fieldDescriptor.getMessageType()); + DynamicMessage nestedMessage = generateMessageForTupleType(tableBuilder, tuple).build(); + messageBuilder.setField(fieldDescriptor, nestedMessage); + break; + } + + default: throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredRecordFieldType.getName(), SERDES_ERROR); } @@ -520,6 +557,15 @@ private static Builder generateMessageForMapType(Builder messageBuilder, BMap table) { Type constrainedType = ((TableType) TypeUtils.getType(table)).getConstrainedType(); Type referredConstrainedType = TypeUtils.getReferredType(constrainedType); @@ -558,4 +603,92 @@ private static Builder generateMessageForTableType(Builder messageBuilder, BTabl } return messageBuilder; } + + + private static Builder generateMessageForTupleType(Builder messageBuilder, BArray tuple) { + int elementFieldNumber = 1; + + for (Object value : tuple.getValues()) { + String elementFieldName = TUPLE_FIELD_NAME + SEPARATOR + elementFieldNumber; + FieldDescriptor tupleElementFieldDescriptor = messageBuilder.getDescriptorForType() + .findFieldByName(elementFieldName); + Type elementType = ((TupleType) tuple.getType()).getTupleTypes().get(elementFieldNumber - 1); + Type referredElementType = TypeUtils.getReferredType(elementType); + + switch (referredElementType.getTag()) { + case TypeTags.INT_TAG: + case TypeTags.BYTE_TAG: + case TypeTags.FLOAT_TAG: + case TypeTags.STRING_TAG: + case TypeTags.BOOLEAN_TAG: { + generateMessageForPrimitiveType(messageBuilder, tupleElementFieldDescriptor, value, + referredElementType.getName()); + break; + } + + case TypeTags.DECIMAL_TAG: { + Descriptor decimalSchema = tupleElementFieldDescriptor.getMessageType(); + Builder decimalMessageBuilder = DynamicMessage.newBuilder(decimalSchema); + DynamicMessage decimalMessage = generateMessageForPrimitiveDecimalType(decimalMessageBuilder, value, + decimalSchema).build(); + messageBuilder.setField(tupleElementFieldDescriptor, decimalMessage); + break; + } + + case TypeTags.UNION_TAG: { + Descriptor unionSchema = tupleElementFieldDescriptor.getMessageType(); + Builder nestedMessageBuilder = DynamicMessage.newBuilder(unionSchema); + DynamicMessage nestedUnionMessage = generateMessageForUnionType(nestedMessageBuilder, + value).build(); + messageBuilder.setField(tupleElementFieldDescriptor, nestedUnionMessage); + break; + } + + case TypeTags.ARRAY_TAG: { + generateMessageForArrayType(messageBuilder, tupleElementFieldDescriptor, (BArray) value); + break; + } + + case TypeTags.RECORD_TYPE_TAG: { + @SuppressWarnings("unchecked") BMap record = (BMap) value; + Builder recordBuilder = DynamicMessage.newBuilder(tupleElementFieldDescriptor.getMessageType()); + DynamicMessage nestedRecordMessage = generateMessageForRecordType(recordBuilder, record).build(); + messageBuilder.setField(tupleElementFieldDescriptor, nestedRecordMessage); + break; + } + + case TypeTags.MAP_TAG: { + @SuppressWarnings("unchecked") BMap nestedMap = (BMap) value; + Builder mapBuilder = DynamicMessage.newBuilder(tupleElementFieldDescriptor.getMessageType()); + DynamicMessage nestedMapMessage = generateMessageForMapType(mapBuilder, nestedMap).build(); + messageBuilder.setField(tupleElementFieldDescriptor, nestedMapMessage); + break; + } + + case TypeTags.TABLE_TAG: { + BTable table = (BTable) value; + Builder tableBuilder = DynamicMessage.newBuilder(tupleElementFieldDescriptor.getMessageType()); + DynamicMessage nestedMapMessage = generateMessageForTableType(tableBuilder, table).build(); + messageBuilder.setField(tupleElementFieldDescriptor, nestedMapMessage); + break; + } + + case TypeTags.TUPLE_TAG: { + BArray nestedTuple = (BArray) value; + Builder nestedTupleBuilder = DynamicMessage.newBuilder( + tupleElementFieldDescriptor.getMessageType()); + DynamicMessage nestedTupleMessage = generateMessageForTupleType(nestedTupleBuilder, + nestedTuple).build(); + messageBuilder.setField(tupleElementFieldDescriptor, nestedTupleMessage); + break; + } + + default: + throw createSerdesError(UNSUPPORTED_DATA_TYPE + referredElementType.getName(), SERDES_ERROR); + + } + elementFieldNumber++; + } + return messageBuilder; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java index 6b543b6..9fa8f79 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java @@ -20,8 +20,10 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BError; @@ -51,13 +53,13 @@ public static BError createSerdesError(String message, String typeId) { } // Get the dimention of given array type - public static int getDimensions(ArrayType array) { + public static int getArrayDimensions(ArrayType array) { int dimension = 1; - String messageName = array.getElementType().getName(); + Type basicElementType = array.getElementType(); - while (messageName.equals(Constants.EMPTY_STRING)) { + while (basicElementType.getTag() == TypeTags.ARRAY_TAG) { array = (ArrayType) array.getElementType(); - messageName = array.getElementType().getName(); + basicElementType = array.getElementType(); dimension++; } @@ -65,14 +67,14 @@ public static int getDimensions(ArrayType array) { } // Get the basic ballerina type of the given array - public static String getElementTypeOfBallerinaArray(ArrayType array) { - String messageName = array.getElementType().getName(); + public static String getElementTypeNameOfBallerinaArray(ArrayType array) { + Type basicElementType = array.getElementType(); - while (messageName.equals(Constants.EMPTY_STRING)) { + while (basicElementType.getTag() == TypeTags.ARRAY_TAG) { array = (ArrayType) array.getElementType(); - messageName = array.getElementType().getName(); + basicElementType = array.getElementType(); } - return messageName; + return basicElementType.getName(); } // Create protobuf message name for the given ballerina primitive type (string -> StringValue) From 7f7e21e7d288df71caf472b6303aaa5f735cffee Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 29 Jun 2022 16:16:06 +0530 Subject: [PATCH 3/6] Change error message to suggest reference type --- ballerina/tests/errors_test.bal | 24 ++++++++++--------- ballerina/tests/map_type_test.bal | 1 - ballerina/tests/record_type_test.bal | 3 +-- ballerina/tests/union_type_test.bal | 2 +- .../io/ballerina/stdlib/serdes/Constants.java | 14 ----------- .../stdlib/serdes/SchemaGenerator.java | 24 ++++++------------- .../io/ballerina/stdlib/serdes/Utils.java | 4 ++++ 7 files changed, 26 insertions(+), 46 deletions(-) diff --git a/ballerina/tests/errors_test.bal b/ballerina/tests/errors_test.bal index ab75c94..8ecc4e0 100644 --- a/ballerina/tests/errors_test.bal +++ b/ballerina/tests/errors_test.bal @@ -96,9 +96,14 @@ type RecordWithNonReferencedMapField record { map ages; }; +public isolated function getErrorMessageForNonReferenceTypes(string typeName) returns string { + return "Type `" + typeName + "` not supported, use a reference type instead: " + + "`type MyType " + typeName + ";`"; +} + @test:Config {} public isolated function testRecordWithNonReferencedMapFieldError() returns error? { - string expectedErrorMsg = "Record field with map type only supported with reference map type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("map"); Proto3Schema|error ser = new(RecordWithNonReferencedMapField); @@ -113,7 +118,7 @@ type RecordWithNonReferencedTableField record { @test:Config {} public isolated function testRecordWithNonReferencedTableFieldError() returns error? { - string expectedErrorMsg = "Record field with table type only supported with reference table type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("table>"); Proto3Schema|error ser = new(RecordWithNonReferencedTableField); @@ -128,8 +133,7 @@ type RecordWithNonReferencedArrayTuple record { @test:Config {} public isolated function testRecordNonReferencedArrayOfTuplesError() returns error? { - string expectedErrorMsg = "Record field with array of tuple type member only supported " - + "with reference tuple type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("[int,int]"); Proto3Schema|error ser = new (RecordWithNonReferencedArrayTuple); @@ -186,7 +190,7 @@ type UnionWithNonReferenceTupleMembers [int, byte]|[string, decimal]; @test:Config {} public isolated function testUnionWithNonReferencedTupleTypeError() returns error? { - string expectedErrorMsg = "Union with tuple type member only supported with reference tuple type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("[int,byte]"); Proto3Schema|error ser = new(UnionWithNonReferenceTupleMembers); @@ -199,8 +203,7 @@ type UnionWithNonReferencedArrayOfTupleMembers [int, byte][]|[string, decimal][] @test:Config {} public isolated function testUnionWithNonReferencedArrayOfTuplesError() returns error? { - string expectedErrorMsg = "Union with array of tuple type member only " - + "supported with reference tuple type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("[int,byte]"); Proto3Schema|error ser = new (UnionWithNonReferencedArrayOfTupleMembers); @@ -209,16 +212,15 @@ public isolated function testUnionWithNonReferencedArrayOfTuplesError() returns test:assertEquals(err.message(), expectedErrorMsg); } -type TupleWithNonReferenceArrayOfTuple [[int,int][], [boolean, float][]]; +type TupleWithNonReferenceArrayOfTuple [[int, int][], [boolean, float][]]; @test:Config {} public isolated function testTupleWithNonReferenceArrayOfTuplesTypeError() returns error? { - string expectedErrorMsg = "Tuple element with array of tuple type member only supported " - + "with reference tuple type"; + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("[int,int]"); Proto3Schema|error ser = new (TupleWithNonReferenceArrayOfTuple); test:assertTrue(ser is Error); Error err = ser; test:assertEquals(err.message(), expectedErrorMsg); -} \ No newline at end of file +} diff --git a/ballerina/tests/map_type_test.bal b/ballerina/tests/map_type_test.bal index d825e93..f42f94f 100644 --- a/ballerina/tests/map_type_test.bal +++ b/ballerina/tests/map_type_test.bal @@ -252,4 +252,3 @@ public isolated function testMapWithTupleElement() returns error? { test:assertEquals(decode, data); } - diff --git a/ballerina/tests/record_type_test.bal b/ballerina/tests/record_type_test.bal index ac18d23..c8fd191 100644 --- a/ballerina/tests/record_type_test.bal +++ b/ballerina/tests/record_type_test.bal @@ -349,10 +349,9 @@ public isolated function testRecordWithArrayOfTuple() returns error? { }; Proto3Schema ser = check new (RecordWithArrayOfTuple); - check ser.generateProtoFile("A.proto"); byte[] encoded = check ser.serialize(value); Proto3Schema des = check new (RecordWithArrayOfTuple); RecordWithArrayOfTuple decoded = check des.deserialize(encoded); test:assertEquals(decoded, value); -} \ No newline at end of file +} diff --git a/ballerina/tests/union_type_test.bal b/ballerina/tests/union_type_test.bal index 8b8e12a..30efd47 100644 --- a/ballerina/tests/union_type_test.bal +++ b/ballerina/tests/union_type_test.bal @@ -174,4 +174,4 @@ public isolated function testUnionOfTupleArrays() returns error? { Proto3Schema des = check new (UnionOfTupleArrays); UnionOfTupleArrays decoded = check des.deserialize(encoded); test:assertEquals(decoded, data); -} \ No newline at end of file +} diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java index d7f5481..b567569 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java @@ -96,19 +96,5 @@ private Constants() {} + " tables as record field"; public static final String ARRAY_OF_TABLE_AS_TUPLE_ELEMENT_NOT_YET_SUPPORTED = "Serdes not yet support array of" + " tables as tuple element"; - public static final String RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with map type " - + "only supported with reference map type"; - public static final String RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with table " - + "type only supported with reference table type"; - public static final String RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with tuple " - + "type only supported with reference table type"; - public static final String RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Record field with" - + " array of tuple type member only supported with reference tuple type"; - public static final String UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Union with tuple type" - + " member only supported with reference tuple type"; - public static final String UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Union with " - + "array of tuple type member only supported with reference tuple type"; - public static final String TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE = "Tuple element " - + "with array of tuple type member only supported with reference tuple type"; public static final BString BALLERINA_TYPEDESC_ATTRIBUTE_NAME = StringUtils.fromString("dataType"); } diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java index 7b9c3d2..3670a0f 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java @@ -71,10 +71,6 @@ import static io.ballerina.stdlib.serdes.Constants.OPTIONAL_LABEL; import static io.ballerina.stdlib.serdes.Constants.PRECISION; import static io.ballerina.stdlib.serdes.Constants.PROTO3; -import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; -import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; -import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; -import static io.ballerina.stdlib.serdes.Constants.RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.REPEATED_LABEL; import static io.ballerina.stdlib.serdes.Constants.SCALE; import static io.ballerina.stdlib.serdes.Constants.SCHEMA_GENERATION_FAILURE; @@ -85,14 +81,11 @@ import static io.ballerina.stdlib.serdes.Constants.TABLE_ENTRY; import static io.ballerina.stdlib.serdes.Constants.TABLE_MEMBER_NOT_YET_SUPPORTED; import static io.ballerina.stdlib.serdes.Constants.TUPLE_BUILDER; -import static io.ballerina.stdlib.serdes.Constants.TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.TUPLE_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.TYPE_SEPARATOR; import static io.ballerina.stdlib.serdes.Constants.UINT32; import static io.ballerina.stdlib.serdes.Constants.UNION_BUILDER_NAME; import static io.ballerina.stdlib.serdes.Constants.UNION_FIELD_NAME; -import static io.ballerina.stdlib.serdes.Constants.UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; -import static io.ballerina.stdlib.serdes.Constants.UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE; import static io.ballerina.stdlib.serdes.Constants.UNSUPPORTED_DATA_TYPE; import static io.ballerina.stdlib.serdes.Constants.VALUE; import static io.ballerina.stdlib.serdes.Constants.VALUE_NAME; @@ -280,7 +273,7 @@ private static Map.Entry mapUnionMemberToMapEntry(Type type) { String key = typeName + TYPE_SEPARATOR + UNION_FIELD_NAME; return Map.entry(key, type); } else { - throw createSerdesError(UNION_MEMBER_WITH_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(type), SERDES_ERROR); } } @@ -594,23 +587,20 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder if (isUnionMember) { String ballerinaType = referredElementType.getName(); if (ballerinaType.equals(EMPTY_STRING)) { - throw createSerdesError(UNION_MEMBER_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, - SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(tupleType), SERDES_ERROR); } nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; fieldName = ballerinaType + TYPE_SEPARATOR + fieldName + TYPE_SEPARATOR + UNION_FIELD_NAME; } else if (isRecordField) { String ballerinaType = referredElementType.getName(); if (ballerinaType.equals(EMPTY_STRING)) { - throw createSerdesError(RECORD_FIELD_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, - SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(tupleType), SERDES_ERROR); } nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; } else if (isTupleElement) { String ballerinaType = referredElementType.getName(); if (ballerinaType.equals(EMPTY_STRING)) { - throw createSerdesError(TUPLE_ELEMENT_WITH_ARRAY_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, - SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(tupleType), SERDES_ERROR); } nestedMessageName = ballerinaType + TYPE_SEPARATOR + TUPLE_BUILDER; } @@ -711,7 +701,7 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde case TypeTags.MAP_TAG: { if (fieldEntryType.getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { - throw createSerdesError(RECORD_FIELD_OF_MAP_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(fieldEntryType), SERDES_ERROR); } String nestedMessageName = fieldEntryType.getName() + TYPE_SEPARATOR + MAP_BUILDER; @@ -728,7 +718,7 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde case TypeTags.TABLE_TAG: { if (fieldEntryType.getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { - throw createSerdesError(RECORD_FIELD_OF_TABLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(fieldEntryType), SERDES_ERROR); } String nestedMessageName = fieldEntryType.getName() + TYPE_SEPARATOR + TABLE_BUILDER; @@ -745,7 +735,7 @@ private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilde case TypeTags.TUPLE_TAG: { if (fieldEntryType.getTag() != TypeTags.TYPE_REFERENCED_TYPE_TAG) { - throw createSerdesError(RECORD_FIELD_OF_TUPLE_ONLY_SUPPORTED_WITH_REFERENCE_TYPE, SERDES_ERROR); + throw createSerdesError(Utils.typeNotSupportedErrorMessage(fieldEntryType), SERDES_ERROR); } String nestedMessageName = fieldEntryType.getName() + TYPE_SEPARATOR + TUPLE_BUILDER; diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java index 9fa8f79..c3e22c9 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java @@ -52,6 +52,10 @@ public static BError createSerdesError(String message, String typeId) { return ErrorCreator.createError(getModule(), typeId, StringUtils.fromString(message), null, null); } + public static String typeNotSupportedErrorMessage(Type type) { + return "Type `" + type + "` not supported, use a reference type instead: " + "`type MyType " + type + ";`"; + } + // Get the dimention of given array type public static int getArrayDimensions(ArrayType array) { int dimension = 1; From e555894408aec525c1281f3a521e84b749486f00 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Thu, 30 Jun 2022 10:52:53 +0530 Subject: [PATCH 4/6] Add error message for record field with non referenced record type --- ballerina/tests/errors_test.bal | 26 ++++++++++++++++--- .../io/ballerina/stdlib/serdes/Constants.java | 1 + .../stdlib/serdes/SchemaGenerator.java | 5 ++++ .../io/ballerina/stdlib/serdes/Utils.java | 7 +++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/ballerina/tests/errors_test.bal b/ballerina/tests/errors_test.bal index 8ecc4e0..017f119 100644 --- a/ballerina/tests/errors_test.bal +++ b/ballerina/tests/errors_test.bal @@ -92,15 +92,15 @@ public isolated function testMapArrayUnionMemberNotYetSupporteError() returns er test:assertEquals(err.message(), expectedErrorMsg); } -type RecordWithNonReferencedMapField record { - map ages; -}; - public isolated function getErrorMessageForNonReferenceTypes(string typeName) returns string { return "Type `" + typeName + "` not supported, use a reference type instead: " + "`type MyType " + typeName + ";`"; } +type RecordWithNonReferencedMapField record { + map ages; +}; + @test:Config {} public isolated function testRecordWithNonReferencedMapFieldError() returns error? { string expectedErrorMsg = getErrorMessageForNonReferenceTypes("map"); @@ -224,3 +224,21 @@ public isolated function testTupleWithNonReferenceArrayOfTuplesTypeError() retur Error err = ser; test:assertEquals(err.message(), expectedErrorMsg); } + +type RecordWithNonReferencedRecordField record { + record { + string name; + int age; + } person; +}; + +@test:Config {} +public isolated function testRecordWithNonReferencedRecordFieldError() returns error? { + string expectedErrorMsg = getErrorMessageForNonReferenceTypes("record {| string name; int age; anydata...; |}"); + + Proto3Schema|error ser = new(RecordWithNonReferencedRecordField); + + test:assertTrue(ser is Error); + Error err = ser; + test:assertEquals(err.message(), expectedErrorMsg); +} diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java index b567569..67abb05 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Constants.java @@ -66,6 +66,7 @@ private Constants() {} public static final String SEPARATOR = "_"; public static final String TYPE_SEPARATOR = "___"; public static final String SPACE = " "; + public static final String CURLY_BRACE = "{"; // Constants related to protobuf labels and types public static final String PROTO3 = "proto3"; diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java index 3670a0f..ff6055f 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/SchemaGenerator.java @@ -58,6 +58,7 @@ import static io.ballerina.stdlib.serdes.Constants.ATOMIC_FIELD_NAME; import static io.ballerina.stdlib.serdes.Constants.BOOL; import static io.ballerina.stdlib.serdes.Constants.BYTES; +import static io.ballerina.stdlib.serdes.Constants.CURLY_BRACE; import static io.ballerina.stdlib.serdes.Constants.DECIMAL_VALUE; import static io.ballerina.stdlib.serdes.Constants.EMPTY_STRING; import static io.ballerina.stdlib.serdes.Constants.FAILED_WRITE_FILE; @@ -622,6 +623,10 @@ private static void generateMessageDefinitionForArrayType(ProtobufMessageBuilder private static void generateMessageDefinitionForRecordType(ProtobufMessageBuilder messageBuilder, RecordType recordType) { + if (recordType.getName().contains(CURLY_BRACE)) { + throw createSerdesError(Utils.typeNotSupportedErrorMessage(recordType), SERDES_ERROR); + } + Map recordFields = recordType.getFields(); int fieldNumber = 1; diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java index c3e22c9..61ce7d4 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Utils.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BError; @@ -56,6 +57,12 @@ public static String typeNotSupportedErrorMessage(Type type) { return "Type `" + type + "` not supported, use a reference type instead: " + "`type MyType " + type + ";`"; } + public static String typeNotSupportedErrorMessage(RecordType type) { + String recordTypeWithoutModulePrefix = type.toString().split(":")[1]; + return "Type `" + recordTypeWithoutModulePrefix + "` not supported, use a reference type instead: " + + "`type MyType " + recordTypeWithoutModulePrefix + ";`"; + } + // Get the dimention of given array type public static int getArrayDimensions(ArrayType array) { int dimension = 1; From c278ef497ea3a2383e65c09002df0fd965ae6aa8 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Thu, 30 Jun 2022 11:02:25 +0530 Subject: [PATCH 5/6] Update changelog.md --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 45daea0..e268c19 100644 --- a/changelog.md +++ b/changelog.md @@ -10,4 +10,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [[#3012] Add support to decimal, records and arrays types](https://github.com/ballerina-platform/ballerina-standard-library/issues/3012#issue-1274825828) - [[#3013] Add support for record with cyclic references](https://github.com/ballerina-platform/ballerina-standard-library/issues/3013#issue-1274864592) -- [[#3032] Add support to ballerina map type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3032#issue-1283701869) \ No newline at end of file +- [[#3032] Add support to ballerina map type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3032#issue-1283701869) +- [[#3043] Add support to ballerina table type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3043#issue-1288139316) +- [[#3046] Add support to ballerina tuple type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3046#issue-1289604760) \ No newline at end of file From a6684ab5c0b428b21ed3bb961f60dc2cd998a5b8 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Thu, 30 Jun 2022 14:44:04 +0530 Subject: [PATCH 6/6] Update review suggestions --- changelog.md | 10 +++++----- .../java/io/ballerina/stdlib/serdes/Serializer.java | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index e268c19..fe0f09d 100644 --- a/changelog.md +++ b/changelog.md @@ -8,8 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- [[#3012] Add support to decimal, records and arrays types](https://github.com/ballerina-platform/ballerina-standard-library/issues/3012#issue-1274825828) -- [[#3013] Add support for record with cyclic references](https://github.com/ballerina-platform/ballerina-standard-library/issues/3013#issue-1274864592) -- [[#3032] Add support to ballerina map type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3032#issue-1283701869) -- [[#3043] Add support to ballerina table type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3043#issue-1288139316) -- [[#3046] Add support to ballerina tuple type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3046#issue-1289604760) \ No newline at end of file +- [[#3012] Add support to decimal, records and arrays types](https://github.com/ballerina-platform/ballerina-standard-library/issues/3012) +- [[#3013] Add support for record with cyclic references](https://github.com/ballerina-platform/ballerina-standard-library/issues/3013) +- [[#3032] Add support to ballerina map type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3032) +- [[#3043] Add support to ballerina table type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3043) +- [[#3046] Add support to ballerina tuple type](https://github.com/ballerina-platform/ballerina-standard-library/issues/3046) \ No newline at end of file diff --git a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java index c29e4bf..a52e998 100644 --- a/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java +++ b/native/src/main/java/io/ballerina/stdlib/serdes/Serializer.java @@ -224,7 +224,6 @@ private static Builder generateMessageForUnionType(Builder messageBuilder, Objec return generateMessageForPrimitiveType(messageBuilder, fieldDescriptor, anydata, ballerinaType); } - if (anydata instanceof BArray) { BArray bArray = (BArray) anydata; // Handle ballerina tuple @@ -254,8 +253,7 @@ private static Builder generateMessageForUnionType(Builder messageBuilder, Objec return generateMessageForArrayType(messageBuilder, fieldDescriptor, bArray); } } - - + if (anydata instanceof BMap) { @SuppressWarnings("unchecked") BMap ballerinaMapOrRecord = (BMap) anydata; // Handle ballerina record