diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 787c91f..92b702b 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -19,4 +19,4 @@ path = "../native/build/libs/data.xmldata-native-1.1.0-SNAPSHOT.jar" groupId = "io.ballerina.stdlib" artifactId = "constraint-native" version = "1.6.0" -path = "./lib/constraint-native-1.6.0-20241121-170800-002e6d6.jar" +path = "./lib/constraint-native-1.6.0-20241122-133100-98689e2.jar" diff --git a/ballerina/tests/test_finite_types.bal b/ballerina/tests/test_finite_types.bal new file mode 100644 index 0000000..62bf7e4 --- /dev/null +++ b/ballerina/tests/test_finite_types.bal @@ -0,0 +1,214 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. 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; + +enum EnumA { + A = "A", + B, + C = "C2" +} + +type FiniteType true|"A"|1|2; + +@test:Config +function testFiniteTypes() returns error? { + record {| + EnumA a; + "A"|"B"|"C" b; + record {| + "A"|"B"|"C" c; + EnumA d; + |} nested; + |} r = check parseAsType(xml `ABBB`); + test:assertEquals(r, {a: "A", b: "B", nested: {c: "B", d: "B"}}); +} + +@test:Config +function testFiniteTypesWithNestedRecords() returns error? { + record {| + EnumA a; + FiniteType b; + record {| + FiniteType c; + FiniteType e; + EnumA d; + |} nested; + |} r = check parseAsType(xml `C212Atrue`); + test:assertEquals(r, {a: "C2", b: 1, nested: {c: 2, d: "A", e: true}}); +} + +@test:Config +function testFiniteTypeArrays() returns error? { + record {| + EnumA[] a; + ("A"|"B"|"C")[] b; + record {| + ("A"|"B"|"C")[] c; + EnumA[] d; + |} nested; + |} r = check parseAsType(xml `AABBBBBB`); + test:assertEquals(r, {a: ["A", "A"], b: ["B", "B"], nested: {c: ["B", "B"], d: ["B", "B"]}}); +} + +@test:Config +function testFiniteTypeArrays2() returns error? { + record {| + EnumA[] a; + FiniteType[] b; + record {| + FiniteType[] c; + FiniteType[] e; + EnumA[] d; + |} nested; + |} r = check parseAsType(xml `C2C21122AAtruetrue`); + test:assertEquals(r, {a: ["C2", "C2"], b: [1, 1], nested: {c: [2, 2], d: ["A", "A"], e: [true, true]}}); +} + +@test:Config +function testFiniteTypesWithXmlString() returns error? { + record {| + EnumA a; + "A"|"B"|"C" b; + record {| + "A"|"B"|"C" c; + EnumA d; + |} nested; + |} r = check parseString(string `ABBB`); + test:assertEquals(r, {a: "A", b: "B", nested: {c: "B", d: "B"}}); +} + +@test:Config +function testFiniteTypesWithNestedRecordsWithXmlString() returns error? { + record {| + EnumA a; + FiniteType b; + record {| + FiniteType c; + FiniteType e; + EnumA d; + |} nested; + |} r = check parseString(string `C212Atrue`); + test:assertEquals(r, {a: "C2", b: 1, nested: {c: 2, d: "A", e: true}}); +} + +@test:Config +function testFiniteTypeArraysWithXmlString() returns error? { + record {| + EnumA[] a; + ("A"|"B"|"C")[] b; + record {| + ("A"|"B"|"C")[] c; + EnumA[] d; + |} nested; + |} r = check parseString(string `AABBBBBB`); + test:assertEquals(r, {a: ["A", "A"], b: ["B", "B"], nested: {c: ["B", "B"], d: ["B", "B"]}}); +} + +@test:Config +function testFiniteTypeArraysWithXmlString2() returns error? { + record {| + EnumA[] a; + FiniteType[] b; + record {| + FiniteType[] c; + FiniteType[] e; + EnumA[] d; + |} nested; + |} r = check parseString(string `C2C21122AAtruetrue`); + test:assertEquals(r, {a: ["C2", "C2"], b: [1, 1], nested: {c: [2, 2], d: ["A", "A"], e: [true, true]}}); +} +type NestedRec record {| + @Name { + value: "c2" + } + FiniteType c; + FiniteType e; + @Name { + value: "d2" + } + EnumA d; +|}; + +@test:Config +function testFiniteTypesWithNameAnnotations() returns error? { + record {| + EnumA a; + FiniteType b; + NestedRec nested; + |} r = check parseAsType(xml `C212Atrue`); + test:assertEquals(r, {a: "C2", b: 1, nested: {c: 2, d: "A", e: true}}); +} + +type FiniteValue 100f; + +@test:Config +function testRecordFieldWithSingleFiniteType() returns error? { + record {| + EnumA a; + "A" b; + record {| + FiniteValue c; + EnumA d; + |} nested; + |} r = check parseAsType(xml `AA100.0B`); + test:assertEquals(r, {a: "A", b: "A", nested: {c: 100f, d: "B"}}); +} + +@test:Config +function testRecordFieldWithSingleFiniteType2() returns error? { + record {| + 100f a; + 200.1d b; + 100d c; + 200.1f d; + 100f e; + 200.1d f; + 100d g; + 200.1f h; + |} r = check parseAsType(xml `100200.1100200.1100.0200.1100.0200.1`); + test:assertEquals(r, {a: 100f, b: 200.1d, c: 100d, d: 200.1f, e: 100f, f: 200.1d, g: 100d, h: 200.1f}); +} + +@test:Config +function testRecordFieldWithSingleFiniteType3() returns error? { + record {| + 100f a; + |}|Error r = parseAsType(xml `100.01`); + test:assertTrue(r is Error); + test:assertEquals((r).message(), "'string' value '100.01' cannot be converted to '100.0f'"); + + record {| + 100d a; + |}|Error r2 = parseAsType(xml `100.01`); + test:assertTrue(r2 is Error); + test:assertEquals((r2).message(), "'string' value '100.01' cannot be converted to '100d'"); +} + +@test:Config +function testRecordFieldWithSingleFiniteType4() returns error? { + record {| + 200.1d|100f a; + 200.1d|100d b; + 100d|100f|200.1f c; + 200.1f|200.1d|100f d; + 200.1d|200.1f|100f e; + 100d|100d|200.1d f; + 100d|200.1d|200.1d g; + 200.1f|200.1d|200.1d h; + |} r = check parseAsType(xml `100200.1100200.1100.0200.1100.0200.1`); + test:assertEquals(r, {a: 100f, b: 200.1d, c: 100d, d: 200.1f, e: 100f, f: 200.1d, g: 100d, h: 200.1f}); +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/FromString.java b/native/src/main/java/io/ballerina/lib/data/xmldata/FromString.java index 57d3a2f..914e96c 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/FromString.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/FromString.java @@ -22,17 +22,20 @@ import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.FiniteType; import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.types.ReferenceType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -96,6 +99,8 @@ public static Object fromStringWithType(BString string, Type expType) { return stringToUnion(string, JSON_TYPE_WITH_BASIC_TYPES); case TypeTags.TYPE_REFERENCED_TYPE_TAG: return fromStringWithType(string, ((ReferenceType) expType).getReferredType()); + case TypeTags.FINITE_TYPE_TAG: + return stringToFiniteType(value, (FiniteType) expType); default: return returnError(value, expType.toString()); } @@ -104,6 +109,38 @@ public static Object fromStringWithType(BString string, Type expType) { } } + private static Object stringToFiniteType(String value, FiniteType finiteType) { + return finiteType.getValueSpace().stream() + .filter(finiteValue -> !(convertToSingletonValue(value, finiteValue) instanceof BError)) + .findFirst() + .orElseGet(() -> returnError(value, finiteType.toString())); + } + + private static Object convertToSingletonValue(String str, Object singletonValue) { + String singletonStr = String.valueOf(singletonValue); + Type type = TypeUtils.getType(singletonValue); + + if (singletonValue instanceof BDecimal decimalValue) { + BigDecimal bigDecimal = decimalValue.decimalValue(); + if (bigDecimal.compareTo(new BigDecimal(str)) == 0) { + return fromStringWithType(StringUtils.fromString(str), type); + } + return returnError(str, singletonStr); + } + + if (singletonValue instanceof Double doubleValue) { + if (doubleValue.compareTo(Double.valueOf(str)) == 0) { + return fromStringWithType(StringUtils.fromString(str), type); + } + return returnError(str, singletonStr); + } + + if (str.equals(singletonStr)) { + return fromStringWithType(StringUtils.fromString(str), type); + } + return returnError(str, singletonStr); + } + private static Long stringToInt(String value) throws NumberFormatException { return Long.parseLong(value); } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java index 5615006..efca262 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java @@ -375,7 +375,7 @@ public static boolean isSupportedType(Type type) { switch (type.getTag()) { case TypeTags.NULL_TAG, TypeTags.INT_TAG, TypeTags.BYTE_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.STRING_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.MAP_TAG, - TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { + TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG, TypeTags.FINITE_TYPE_TAG -> { return true; } case TypeTags.ARRAY_TAG -> { diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java index 19e186c..43e9ec8 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java @@ -448,7 +448,7 @@ private Object convertStringToRestExpType(BString value, Type expType) { return convertStringToRestExpType(value, ((ArrayType) expType).getElementType()); } case TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.STRING_TAG, - TypeTags.BOOLEAN_TAG, TypeTags.UNION_TAG -> { + TypeTags.BOOLEAN_TAG, TypeTags.UNION_TAG, TypeTags.FINITE_TYPE_TAG -> { return convertStringToExpType(value, expType); } case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {