diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02bc091..03f4500 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,4 +4,5 @@ # See: https://help.github.com/articles/about-codeowners/ # These owners will be the default owners for everything in the repo. -* @gimantha hasithaa +* @gimantha @SasinduDilshara + diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index f8f63ed..a5ae2f1 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -3340,13 +3340,13 @@ function testUnsupportedTypeNegative() { `; record {| record {|string a;|}|record {|string b;|} A; - |}|error err2 = parseAsType(xmlVal2); - test:assertEquals((err2).message(), "unsupported input type"); + |}|error val = parseAsType(xmlVal2); + test:assertEquals(val, {A: {a: "1"}}); record {| record {|string a;|}? A; - |}|error err3 = parseAsType(xmlVal2); - test:assertEquals((err3).message(), "unsupported input type"); + |}|error val2 = parseAsType(xmlVal2); + test:assertEquals(val2, {A: {a: "1"}}); } @Namespace { diff --git a/ballerina/tests/parse_string_union_test.bal b/ballerina/tests/parse_string_union_test.bal new file mode 100644 index 0000000..923ebe5 --- /dev/null +++ b/ballerina/tests/parse_string_union_test.bal @@ -0,0 +1,574 @@ +import ballerina/test; + +string s1 = "42"; + +type S11 record { + int[]|string \#content; +}; + +type S12 record {int[] \#content;}|record {int \#content;}; + +type S13 record {| + int[]|int...; +|}; + +type S14 record { + boolean|int \#content; +}; + +@test:Config +function testParseStringUnionTypes1() { + S11|error a11 = parseString(s1); + test:assertEquals(a11, {"#content": "42"}); + + S12|error a12 = parseString(s1); + test:assertEquals(a12, {"#content":42}); + + // https://github.com/ballerina-platform/ballerina-library/issues/6929 + // S13|error a13 = parseString(s1); + // test:assertEquals(a13, {"#content":42}); + + S14|error a14 = parseString(s1); + test:assertEquals(a14, {"#content": 42}); +} + +string s2 = string `Sample Text`; + +type S21 record { + int[]|string \#content; + int|boolean|string a1; +}; + +type S22 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {int \#content; int a1;}; + +type S23 record{int \#content;}|record {string \#content; int|decimal a1;}; + +// type S24 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {|float|string|int...;|}; + +type S25 record{int \#content;}|record {string \#content; int|decimal a1; int|decimal a2;}; + +type S26 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {|boolean?|string?...;|}; + +@test:Config +function testParseStringUnionTypes2() { + S21|error a21 = parseString(s2); + test:assertEquals(a21, {"#content":"Sample Text","a1":2024}); + + S22|error a22 = parseString(s2); + test:assertTrue(a22 is Error); + test:assertEquals(( a22).message(), "source value cannot be converted into 'ballerina/data.xmldata:1:S22'"); + + // https://github.com/ballerina-platform/ballerina-library/issues/6925 + // S23|error a23 = parseString(s2); + // test:assertEquals(a23, {"#content":"Sample Text","a1":2024, "a2": 3.14}); + + // https://github.com/ballerina-platform/ballerina-library/issues/6925 + // S24|error a24 = parseString(s2); + // test:assertEquals(a24, {"a1":2024, "a2": 3.14}); + + S25|error a25 = parseString(s2); + test:assertEquals(a25, {"#content":"Sample Text","a1":2024, "a2": 3.14}); + + S26|error a26 = parseString(s2); + test:assertEquals(a26, {"#content":"Sample Text"}); +} + +string s3 = string `100`; + +type S31 record { + int[]|string B; +}; + +type S31P2 record { + @Name { + value: "B" + } + string|int[] b; +}; + +type S32 record { + @Name { + value: "B" + } + boolean|record{int \#content;}|int[] b; +}; + +type S33 record{string|int[] b1;}|record {|int|string B;|}; + +type S34 record { + record{int \#content; int b1;}|record{int \#content;}[]|record{int \#content;} B; +}; + +type S35 record {| + record{|string...;|}...; +|}; + +@test:Config +function testParseStringUnionTypes3() { + S31|error a31 = parseString(s3); + test:assertEquals(a31, {"B":"100"}); + + S31P2|error a31p2 = parseString(s3); + test:assertEquals(a31p2, {"b":"100"}); + + S32|error a32 = parseString(s3); + test:assertEquals(a32, {"b":{"#content":100}}); + + S33|error a33 = parseString(s3); + test:assertEquals(a33, {"B":100}); + + S34|error a34 = parseString(s3); + test:assertEquals(a34, {"B":[{"#content":100}]}); + + // https://github.com/ballerina-platform/ballerina-library/issues/6929 + // S35|error a35 = parseString(s3); + // test:assertEquals(a35, {"B":{"#content":"100"}}); +} + +string s4 = string `Nested Content`; + +type S41 record {| + int|record{|string \#content; int[]|int b1; record{}|boolean b2;|}|record{|string \#content; int[]|int b1; record{}|string b2;|}|record{} B; +|}; + +type S41P2 record {| + int|record{|boolean \#content; int[]|int b1; record{}|boolean b2;|}|record{|string \#content; int[]|int b1; record{}|string b2;|}|record{} B; +|}; + +type S42 record {| + int|record{|int...;|}|record{string \#content; int[]|int b1;}|record{} B; +|}; + +type S43P2 record {|int[]|record{|string \#content;|} B;|}; +type S43 record{|int[]|record{|int \#content; float|decimal|int...;|} B;|}|S43P2; + +@test:Config +function testParseStringUnionTypes4() { + // https://github.com/ballerina-platform/ballerina-library/issues?q=is%3Aopen+is%3Aissue+author%3ASasinduDilshara+label%3Amodule%2Fdata.xmldata + // S41|error a41 = parseString(s4); + // test:assertEquals(a41, {"B":{"#content":"Nested Content","b1":99,"b2":"45.67"}}); + + S41P2|error a41p2 = parseString(s4); + test:assertEquals(a41p2, {"B":{"#content":"Nested Content","b1":99,"b2":"45.67"}}); + + S42|error a42 = parseString(s4); + test:assertEquals(a42, {"B":{"#content":"Nested Content","b1":99}}); + + S43|error a43 = parseString(s4); + test:assertEquals(a43, {"B":{"#content":"Nested Content"}}); +} + +string s5 = string `123456`; + +type Ref record {|int...;|}; +type RefArr Ref[]; + +type S51 record {RefArr|int[] B;}; +type S52 record {int[]|RefArr B;}; +type S53 record {Ref|int[] B;}; +type S54 record {|Ref|int[]...;|}; +type S55 record {|Ref[]|int[]...;|}; +type S56 record {|(Ref|int)[]...;|}; +type S57 record {|(Ref|int)[] B;|}; +type S58 record {|(int|Ref)[]...;|}; + +@test:Config +function testParseStringUnionTypes5() { + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S51|error a51 = parseString(s5); + // test:assertEquals(a51, {"B":[{"#content":123},{"#content":456}]}); + + S52|error a52 = parseString(s5); + test:assertEquals(a52, {"B":[123,456]}); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S53|error a53 = parseString(s5); + // test:assertEquals(a53, {); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S54|error a54 = parseString(s5); + // test:assertEquals(a54, {}); + + // https://github.com/ballerina-platform/ballerina-library/issues?q=is%3Aopen+is%3Aissue+author%3ASasinduDilshara+label%3Amodule%2Fdata.xmldata + // S55|error a55 = parseString(s5); + // test:assertEquals(a55, {"B":[123,456]}); + + S56|error a56 = parseString(s5); + test:assertEquals(a56, {"B":[{"#content":123},{"#content":456}]}); + + S57|error a57 = parseString(s5); + test:assertEquals(a57, {"B":[{"#content":123},{"#content":456}]}); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S58|error a58 = parseString(s5); + // test:assertEquals(a58, {"B":[{"#content":123},{"#content":456}]}); +} + +string s6 = string `ToyotaYamaha`; + +type S61 record {string C; record {|string \#content;|}[]|string B;}; +type S62 record {record {|boolean|record{(record{}|int[])[]|string \#content;}|string \#content;|}|string B; string C;}; +type S63 record {|record {|string \#content;|}[]|record {|string \#content;|}...;|}; +type S64 record {|record {|boolean|record{}|record{}[]|string \#content;|}[]|string B;|}; + +@test:Config +function testParseStringUnionTypes6() { + S61|error a61 = parseString(s6); + test:assertEquals(a61, {"C":"Yamaha","B":[{"#content":"Toyota"}]}); + + S62|error a62 = parseString(s6); + test:assertEquals(a62, {"B":"Toyota","C":"Yamaha"}); + + S63|error a63 = parseString(s6); + test:assertEquals(a63, {"B":[{"#content":"Toyota"}],"C":[{"#content":"Yamaha"}]}); + + S64|error a64 = parseString(s6); + test:assertEquals(a64, {"B":[{"#content":"Toyota"}]}); +} + +string s7 = string `BrickWaterAir`; + +type S71 record {record{[int, int] a;}|record {|string...;|}[]|string[] B;}; +type S72 record {string[]|record {|string...;|}[] B;}; +type S73 record {record {|string \@content;|}|string[] B;}; +type S74 record {|record {|string...;|}|string[]...;|}; +type S75 record {|record {|string...;|}[]|string[]...;|}; +type S76 record {|(record {|string...;|}|string)[]...;|}; +type S77 record {|(record {|string...;|}|string)[] B;|}; +type S78 record {|(string|record {|string...;|})[]...;|}; + +@test:Config +function testParseStringUnionTypes7() { + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S71|error a71 = parseString(s7); + // test:assertEquals(a71, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":{"c1":"gas","c2":"transparent","#content":"Air"}}); + + S72|error a72 = parseString(s7); + test:assertEquals(a72, {"B":["Brick","Water"],"C":{"c1":"gas","c2":"transparent","#content":"Air"}}); + + S73|error a73 = parseString(s7); + test:assertTrue(a73 is Error); + test:assertEquals((a73).message(), "field 'B' cannot be converted into the type '(data.xmldata:record {| string @content; |}|string[])'"); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S74|error a74 = parseString(s7); + // test:assertEquals(a74, {"B":{"#content":"Water"},"C":{"#content":"Air"}); + + // https://github.com/ballerina-platform/ballerina-library/issues?q=is%3Aopen+is%3Aissue+author%3ASasinduDilshara+label%3Amodule%2Fdata.xmldata + // S75|error a75 = parseString(s7); + // test:assertEquals(a75, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":[{"#content":"Air"}]}); + + // // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S76|error a76 = parseString(s7); + // test:assertEquals(a76, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":[{"#content":"Air"}]}); + + // // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S77|error a77 = parseString(s7); + // test:assertEquals(a77, {"B":[{"#content":"Brick"},{"#content":"Water"}]}); + + // // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // S78|error a78 = parseString(s7); + // test:assertEquals(a78, {"B":{"#content":"Water"},"C":{"#content":"Air"}}); +} + +string s8 = string ` + + First + + + Second + Third + + + Fourth + Fifth + + `; + +type S81P2 record{string \#content;}[]; +type S81P1 record{string \#content;}; +type S81 record {record{|(S81P1|S81P2)[] C?;|}[] B;}; +type S81Part2 record {record{(S81P1|S81P2)[] C?;}[] B;}; +type S82P2 record{string \#content;}[][]; +type S82P1 record{string \#content;}[]; +type S82 record {record{|S82P1|S82P2 C?;|}[] B;}; +type S82Part2 record {record{S82P1|S82P2 C?;}[] B;}; +type S83P2 record{string \#content;}[]; +type S83P1 record{string \#content;}; +type S83 record {|record{|(S83P1|S83P2)[] C?;|}...;|}; +type S84P2 record{string \#content;}[]; +type S84P1 record{string \#content;}; +type S84 record {|record{|(S84P1|S84P2)[] C?;|}[]...;|}; +type S85 record{|record{}...;|}|record{|record{}[]...;|}; +type S86 record{|record{}[]...;|}|record{|record{}...;|}; + +@test:Config +function testParseStringUnionTypes8() { + S81|error a81 = parseString(s8); + test:assertEquals(a81, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + S81Part2|error a81p2 = parseString(s8); + test:assertEquals(a81p2, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]}, + {"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); + + S82|error a82 = parseString(s8); + test:assertEquals(a82, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + S82Part2|error a82p2 = parseString(s8); + test:assertEquals(a82p2, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]}, + {"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); + + S83|error a83 = parseString(s8); + test:assertEquals(a83, {B: {}}); + + S84|error a84 = parseString(s8); + test:assertEquals(a84, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + S85|error a85 = parseString(s8); + test:assertEquals(a85, {"B":{"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}}); + + S86|error a86 = parseString(s8); + test:assertEquals(a86, {"B":[{"C":{"c1":"inner1","c2":"value","#content":"First"}},{"C":[{"c1":"inner2","c2":"value","#content":"Second"}, + {"c1":"inner3","c2":"value","#content":"Third"}]},{"D":{"d1":"inner4","d2":"value","#content":"Fourth"}, + "E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); +} + +string s9 = string ` + + 100 + 200 + 300 + + + 400 + 500 + 600 + + `; + +type S91P1 record{string[]|record{string \#content;}[] C;}; +type S91 record {S91P1[] B;}; +type S92P1 record{(string|record{string \#content;})[] C;}; +type S92 record {S92P1[] B;}; +type S93P1 record{record{string \#content;}[]|string[] C;}; +type S93 record {S93P1[] B;}; +type S94P1 record{|(record{string \#content;}|string)[]...;|}; +type S94 record {S94P1[] B;}; + +@test:Config +function testParseStringUnionTypes9() { + S91|error a91 = parseString(s9); + test:assertEquals(a91, {"B":[{"C":["100","200","300"]},{"C":["400","500","600"]}]}); + + S92|error a92 = parseString(s9); + test:assertEquals(a92, {"B":[{"C":["100","200","300"]},{"C":["400","500","600"]}]}); + + S93|error a93 = parseString(s9); + test:assertEquals(a93, {"B":[{"C":[{"#content":"100"}, + {"#content":"200"},{"#content":"300"}]}, + {"C":[{"#content":"400"},{"#content":"500"}, + {"#content":"600"}]}]}); + + S94|error a94 = parseString(s9); + test:assertEquals(a94, {"B":[{"C":[{"#content":"100"}, + {"#content":"200"},{"#content":"300"}]}, + {"C":[{"#content":"400"},{"#content":"500"}, + {"#content":"600"}]}]}); +} + +string s10 = string ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value4 + + `; + +type S101P1 record{string[]|record{string \#content;}[] C;}; +type S101 record {S101P1[] B;}; +type S102P1 record{(string|record{string \#content;})[] C;}; +type S102 record {S102P1[] B;}; +type S103P1 record{record{string \#content;}[]|string[] C;}; +type S103 record {S103P1[] B;}; +type S104P1 record{(record{string \#content;}|string)[] C;}; +type S104 record {S104P1[] B;}; + +@test:Config +function testParseStringUnionTypes10() { + S101|error a101 = parseString(s10); + test:assertEquals(a101, {"B":[{"C":["Deep Value1","Deep Value2"]},{"C":["Deep Value3","Deep Value4"]}]}); + + S102|error a102 = parseString(s10); + test:assertEquals(a102, {"B":[{"C":["Deep Value1","Deep Value2"]},{"C":["Deep Value3","Deep Value4"]}]}); + + S103|error a103 = parseString(s10); + test:assertEquals(a103, {"B":[{"C":[{"#content":"Deep Value1"},{"#content":"Deep Value2"}]}, + {"C":[{"#content":"Deep Value3"},{"#content":"Deep Value4"}]}]}); + + S104|error a104 = parseString(s10); + test:assertEquals(a104, {"B":[{"C":[{"#content":"Deep Value1"},{"#content":"Deep Value2"}]}, + {"C":[{"#content":"Deep Value3"},{"#content":"Deep Value4"}]}]}); +} + +string s11 = string ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value2 + + `; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type S111 record { + string a1; + (T111|T112)[] B; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type S112 record {| + string a1; + (T111|T112)[]...; +|}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type T111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (U111|U112)[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type T112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (U111|U112)[] C; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type U111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type U112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@test:Config +function testParseStringUnionTypes11() { + S111|error a111 = parseString(s11); + test:assertEquals(a111, {"a1":"outer","B":[{"b1":"middle","C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]}, + {"b1":"middle","C":[{"c1":"inner","#content":"Deep Value3"}, + {"c1":"inner","#content":"Deep Value2"}]}]}); + + S112|error a112 = parseString(s11); + test:assertEquals(a112, {"a1":"outer","B":[{"b1":"middle","C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]}, + {"b1":"middle","C":[{"c1":"inner","#content":"Deep Value3"}, + {"c1":"inner","#content":"Deep Value2"}]}]}); +} + +string s12 = string ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value4 + + `; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type S121 record { + string a1; + (T121|T122)[] B; +}; + +type S122 record {| + string a1; + (T121|T122)[]...; +|}; + +@Namespace { + prefix: "ns222", + uri: "http://example.com/ns2" +} +type T121 record { + string b1; + U121[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type T122 record { + string b1; + U121[] C; +}; + +@Namespace { + prefix: "ns3", + uri: "http://example.com/ns3" +} +type U121 record { + string c1; + string \#content; +}; + +@test:Config +function testParseStringUnionTypes12() { + S121|error a121 = parseString(s12); + test:assertEquals(a121, {"a1":"outer","B":[{"b1":"middle", "C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]},{"b1":"middle", + "C":[{"c1":"inner","#content":"Deep Value3"},{"c1":"inner","#content":"Deep Value4"}]}]}); + S122|error a122 = parseString(s12); + test:assertEquals(a122, {"a1":"outer","B":[{"b1":"middle", "C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]},{"b1":"middle", + "C":[{"c1":"inner","#content":"Deep Value3"},{"c1":"inner","#content":"Deep Value4"}]}]}); +} diff --git a/ballerina/tests/parse_type_union_test.bal b/ballerina/tests/parse_type_union_test.bal new file mode 100644 index 0000000..a81b33e --- /dev/null +++ b/ballerina/tests/parse_type_union_test.bal @@ -0,0 +1,572 @@ +import ballerina/test; + +xml x1 = xml `42`; + +type A11 record { + int[]|string \#content; +}; + +type A12 record {int[] \#content;}|record {int \#content;}; + +type A13 record {| + int[]|string...; +|}; + +type A14 record { + boolean|int \#content; +}; + +@test:Config +function testTraverseUnionTypes1() { + A11|error a11 = parseAsType(x1); + test:assertEquals(a11, {"#content": "42"}); + + A12|error a12 = parseAsType(x1); + test:assertEquals(a12, {"#content":42}); + + A13|error a13 = parseAsType(x1); + test:assertEquals(a13, {"#content":"42"}); + + A14|error a14 = parseAsType(x1); + test:assertEquals(a14, {"#content": 42}); +} + +xml x2 = xml `Sample Text`; + +type A21 record { + int[]|string \#content; + int|boolean|string a1; +}; + +type A22 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {int \#content; int a1;}; + +type A23 record{int \#content;}|record {string \#content; int|decimal a1;}; + +// type A24 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {|float|string|int...;|}; + +type A25 record{int \#content;}|record {string \#content; int|decimal a1; int|decimal a2;}; + +type A26 record {int[] \#content;}|record {|int[]...;|}|record {int \#content; int a2;}|record {|boolean?|string?...;|}; + +@test:Config +function testTraverseUnionTypes2() { + A21|error a21 = parseAsType(x2); + test:assertEquals(a21, {"#content":"Sample Text","a1":2024}); + + A22|error a22 = parseAsType(x2); + test:assertTrue(a22 is Error); + test:assertEquals(( a22).message(), "source value cannot be converted into 'ballerina/data.xmldata:1:A22'"); + + // https://github.com/ballerina-platform/ballerina-library/issues/6925 + // A23|error a23 = parseAsType(x2); + // test:assertEquals(a23, {"#content":"Sample Text","a1":2024, "a2": 3.14}); + + // https://github.com/ballerina-platform/ballerina-library/issues/6925 + // A24|error a24 = parseAsType(x2); + // test:assertEquals(a24, {"a1":2024, "a2": 3.14}); + + A25|error a25 = parseAsType(x2); + test:assertEquals(a25, {"#content":"Sample Text","a1":2024, "a2": 3.14}); + + A26|error a26 = parseAsType(x2); + test:assertEquals(a26, {"#content":"Sample Text"}); +} + +xml x3 = xml `100`; + +type A31 record { + int[]|string B; +}; + +type A31P2 record { + @Name { + value: "B" + } + string|int[] b; +}; + +type A32 record { + @Name { + value: "B" + } + boolean|record{int \#content;}|int[] b; +}; + +type A33 record{string|int[] b1;}|record {|int|string B;|}; + +type A34 record { + record{int \#content; int b1;}|record{int \#content;}[]|record{int \#content;} B; +}; + +type A35 record {| + record{|boolean|string...;|}...; +|}; + +@test:Config +function testTraverseUnionTypes3() { + A31|error a31 = parseAsType(x3); + test:assertEquals(a31, {"B":[100]}); + + A31P2|error a31p2 = parseAsType(x3); + test:assertEquals(a31p2, {"b":"100"}); + + A32|error a32 = parseAsType(x3); + test:assertEquals(a32, {"b":{"#content":100}}); + + A33|error a33 = parseAsType(x3); + test:assertEquals(a33, {"B":100}); + + A34|error a34 = parseAsType(x3); + test:assertEquals(a34, {"B":[{"#content":100}]}); + + A35|error a35 = parseAsType(x3); + test:assertEquals(a35, {"B":{"#content":"100"}}); +} + +xml x4 = xml `Nested Content`; + +type A41 record {| + int|record{|string \#content; int[]|int b1; record{}|boolean b2;|}|record{|string \#content; int[]|int b1; record{}|string b2;|}|record{} B; +|}; + +type A41P2 record {| + int|record{|boolean \#content; int[]|int b1; record{}|boolean b2;|}|record{|string \#content; int[]|int b1; record{}|string b2;|}|record{} B; +|}; + +type A42 record {| + int|record{|int...;|}|record{string \#content; int[]|int b1;}|record{} B; +|}; + +type A43P2 record {|int[]|record{|string \#content;|} B;|}; +type A43 record{|int[]|record{|int \#content; float|decimal|int...;|} B;|}|A43P2; + +@test:Config +function testTraverseUnionTypes4() { + // https://github.com/ballerina-platform/ballerina-library/issues/6925 + // A41|error a41 = parseAsType(x4); + // test:assertEquals(a41, {"B":{"#content":"Nested Content","b1":99,"b2":"45.67"}}); + + A41P2|error a41p2 = parseAsType(x4); + test:assertEquals(a41p2, {"B":{"#content":"Nested Content","b1":99,"b2":"45.67"}}); + + A42|error a42 = parseAsType(x4); + test:assertEquals(a42, {"B":{"#content":"Nested Content","b1":99}}); + + A43|error a43 = parseAsType(x4); + test:assertEquals(a43, {"B":{"#content":"Nested Content"}}); +} + +xml x5 = xml `123456`; + +type Ref2 record {|int...;|}; +type RefArr2 Ref2[]; + +type A51 record {RefArr2|int[] B;}; +type A52 record {int[]|RefArr2 B;}; +type A53 record {Ref2|int[] B;}; +type A54 record {|Ref2|int[]...;|}; +type A55 record {|Ref2[]|int[]...;|}; +type A56 record {|(Ref2|int)[]...;|}; +type A57 record {|(Ref2|int)[] B;|}; +type A58 record {|(int|Ref2)[]...;|}; + +@test:Config +function testTraverseUnionTypes5() { + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A51|error a51 = parseAsType(x5); + // test:assertEquals(a51, {"B":[{"#content":123},{"#content":456}]}); + + A52|error a52 = parseAsType(x5); + test:assertEquals(a52, {"B":[123,456]}); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A53|error a53 = parseAsType(x5); + // test:assertEquals(a53, {); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A54|error a54 = parseAsType(x5); + // test:assertEquals(a54, {}); + + // https://github.com/ballerina-platform/ballerina-library/issues?q=is%3Aopen+is%3Aissue+author%3ASasinduDilshara+label%3Amodule%2Fdata.xmldata + // A55|error a55 = parseAsType(x5); + // test:assertEquals(a55, {"B":[123,456]}); + + A56|error a56 = parseAsType(x5); + test:assertEquals(a56, {"B":[{"#content":123},{"#content":456}]}); + + A57|error a57 = parseAsType(x5); + test:assertEquals(a57, {"B":[{"#content":123},{"#content":456}]}); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A58|error a58 = parseAsType(x5); + // test:assertEquals(a58, {"B":[{"#content":123},{"#content":456}]}); +} + +xml x6 = xml `ToyotaYamaha`; + +type A61 record {string C; record {|string \#content;|}[]|string B;}; +type A62 record {record {|boolean|record{(record{}|int[])[]|string \#content;}|string \#content;|}|string B; string C;}; +type A63 record {|record {|string \#content;|}[]|record {|string \#content;|}...;|}; +type A64 record {|record {|boolean|record{}|record{}[]|string \#content;|}[]|string B;|}; + +@test:Config +function testTraverseUnionTypes6() { + A61|error a61 = parseAsType(x6); + test:assertEquals(a61, {"C":"Yamaha","B":[{"#content":"Toyota"}]}); + + A62|error a62 = parseAsType(x6); + test:assertEquals(a62, {"B":"Toyota","C":"Yamaha"}); + + A63|error a63 = parseAsType(x6); + test:assertEquals(a63, {"B":[{"#content":"Toyota"}],"C":[{"#content":"Yamaha"}]}); + + A64|error a64 = parseAsType(x6); + test:assertEquals(a64, {"B":[{"#content":"Toyota"}]}); +} + +xml x7 = xml `BrickWaterAir`; + +type A71 record {record{[int, int] a;}|record {|string...;|}[]|string[] B;}; +type A72 record {string[]|record {|string...;|}[] B;}; +type A73 record {record {|string \@content;|}|string[] B;}; +type A74 record {|record {|string...;|}|string[]...;|}; +type A75 record {|record {|string...;|}[]|string[]...;|}; +type A76 record {|(record {|string...;|}|string)[]...;|}; +type A77 record {|(record {|string...;|}|string)[] B;|}; +type A78 record {|(string|record {|string...;|})[]...;|}; + +@test:Config +function testTraverseUnionTypes7() { + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A71|error a71 = parseAsType(x7); + // test:assertEquals(a71, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":{"c1":"gas","c2":"transparent","#content":"Air"}}); + + A72|error a72 = parseAsType(x7); + test:assertEquals(a72, {"B":["Brick","Water"],"C":{"c1":"gas","c2":"transparent","#content":"Air"}}); + + A73|error a73 = parseAsType(x7); + test:assertTrue(a73 is Error); + test:assertEquals((a73).message(), "field 'B' cannot be converted into the type '(data.xmldata:record {| string @content; |}|string[])'"); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A74|error a74 = parseAsType(x7); + // test:assertEquals(a74, {"B":{"#content":"Water"},"C":{"#content":"Air"}); + + // https://github.com/ballerina-platform/ballerina-library/issues?q=is%3Aopen+is%3Aissue+author%3ASasinduDilshara+label%3Amodule%2Fdata.xmldata + // A75|error a75 = parseAsType(x7); + // test:assertEquals(a75, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":[{"#content":"Air"}]}); + + // // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A76|error a76 = parseAsType(x7); + // test:assertEquals(a76, {"B":[{"#content":"Brick"},{"#content":"Water"}],"C":[{"#content":"Air"}]}); + + // // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A77|error a77 = parseAsType(x7); + // test:assertEquals(a77, {"B":[{"#content":"Brick"},{"#content":"Water"}]}); + + // //bug https://github.com/ballerina-platform/ballerina-library/issues/6907 + // A78|error a78 = parseAsType(x7); + // test:assertEquals(a78, {"B":{"#content":"Water"},"C":{"#content":"Air"}}); +} + +xml x8 = xml ` + + First + + + Second + Third + + + Fourth + Fifth + + `; + +type A81P2 record{string \#content;}[]; +type A81P1 record{string \#content;}; +type A81 record {record{|(A81P1|A81P2)[] C?;|}[] B;}; +type A81Part2 record {record{(A81P1|A81P2)[] C?;}[] B;}; +type A82P2 record{string \#content;}[][]; +type A82P1 record{string \#content;}[]; +type A82 record {record{|A82P1|A82P2 C?;|}[] B;}; +type A82Part2 record {record{A82P1|A82P2 C?;}[] B;}; +type A83P2 record{string \#content;}[]; +type A83P1 record{string \#content;}; +type A83 record {|record{|(A83P1|A83P2)[] C?;|}...;|}; +type A84P2 record{string \#content;}[]; +type A84P1 record{string \#content;}; +type A84 record {|record{|(A84P1|A84P2)[] C?;|}[]...;|}; +type A85 record{|record{}...;|}|record{|record{}[]...;|}; +type A86 record{|record{}[]...;|}|record{|record{}...;|}; + +@test:Config +function testTraverseUnionTypes8() { + A81|error a81 = parseAsType(x8); + test:assertEquals(a81, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + A81Part2|error a81p2 = parseAsType(x8); + test:assertEquals(a81p2, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]}, + {"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); + + A82|error a82 = parseAsType(x8); + test:assertEquals(a82, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + A82Part2|error a82p2 = parseAsType(x8); + test:assertEquals(a82p2, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]}, + {"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); + + A83|error a83 = parseAsType(x8); + test:assertEquals(a83, {B: {}}); + + A84|error a84 = parseAsType(x8); + test:assertEquals(a84, {"B":[{"C":[{"#content":"First"}]},{"C":[{"#content":"Second"},{"#content":"Third"}]},{}]}); + + A85|error a85 = parseAsType(x8); + test:assertEquals(a85, {"B":{"D":{"d1":"inner4","d2":"value","#content":"Fourth"},"E":{"e1":"inner5","e2":"value","#content":"Fifth"}}}); + + A86|error a86 = parseAsType(x8); + test:assertEquals(a86, {"B":[{"C":{"c1":"inner1","c2":"value","#content":"First"}},{"C":[{"c1":"inner2","c2":"value","#content":"Second"}, + {"c1":"inner3","c2":"value","#content":"Third"}]},{"D":{"d1":"inner4","d2":"value","#content":"Fourth"}, + "E":{"e1":"inner5","e2":"value","#content":"Fifth"}}]}); +} + +xml x9 = xml ` + + 100 + 200 + 300 + + + 400 + 500 + 600 + + `; + +type A91P1 record{string[]|record{string \#content;}[] C;}; +type A91 record {A91P1[] B;}; +type A92P1 record{(string|record{string \#content;})[] C;}; +type A92 record {A92P1[] B;}; +type A93P1 record{record{string \#content;}[]|string[] C;}; +type A93 record {A93P1[] B;}; +type A94P1 record{|(record{string \#content;}|string)[]...;|}; +type A94 record {A94P1[] B;}; + +@test:Config +function testTraverseUnionTypes9() { + A91|error a91 = parseAsType(x9); + test:assertEquals(a91, {"B":[{"C":["100","200","300"]},{"C":["400","500","600"]}]}); + + A92|error a92 = parseAsType(x9); + test:assertEquals(a92, {"B":[{"C":["100","200","300"]},{"C":["400","500","600"]}]}); + + A93|error a93 = parseAsType(x9); + test:assertEquals(a93, {"B":[{"C":[{"#content":"100"}, + {"#content":"200"},{"#content":"300"}]}, + {"C":[{"#content":"400"},{"#content":"500"}, + {"#content":"600"}]}]}); + + A94|error a94 = parseAsType(x9); + test:assertEquals(a94, {"B":[{"C":[{"#content":"100"}, + {"#content":"200"},{"#content":"300"}]}, + {"C":[{"#content":"400"},{"#content":"500"}, + {"#content":"600"}]}]}); +} + +xml x10 = xml ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value4 + + `; + +type A101P1 record{string[]|record{string \#content;}[] C;}; +type A101 record {A101P1[] B;}; +type A102P1 record{(string|record{string \#content;})[] C;}; +type A102 record {A102P1[] B;}; +type A103P1 record{record{string \#content;}[]|string[] C;}; +type A103 record {A103P1[] B;}; +type A104P1 record{(record{string \#content;}|string)[] C;}; +type A104 record {A104P1[] B;}; + +@test:Config +function testTraverseUnionTypes10() { + A101|error a101 = parseAsType(x10); + test:assertEquals(a101, {"B":[{"C":["Deep Value1","Deep Value2"]},{"C":["Deep Value3","Deep Value4"]}]}); + + A102|error a102 = parseAsType(x10); + test:assertEquals(a102, {"B":[{"C":["Deep Value1","Deep Value2"]},{"C":["Deep Value3","Deep Value4"]}]}); + + A103|error a103 = parseAsType(x10); + test:assertEquals(a103, {"B":[{"C":[{"#content":"Deep Value1"},{"#content":"Deep Value2"}]}, + {"C":[{"#content":"Deep Value3"},{"#content":"Deep Value4"}]}]}); + + A104|error a104 = parseAsType(x10); + test:assertEquals(a104, {"B":[{"C":[{"#content":"Deep Value1"},{"#content":"Deep Value2"}]}, + {"C":[{"#content":"Deep Value3"},{"#content":"Deep Value4"}]}]}); +} + +xml x11 = xml ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value2 + + `; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type A111 record { + string a1; + (B111|B112)[] B; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type A112 record {| + string a1; + (B111|B112)[]...; +|}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type B111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (C111|C112)[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type B112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (C111|C112)[] C; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type C111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type C112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@test:Config +function testTraverseUnionTypes11() { + A111|error a111 = parseAsType(x11); + test:assertEquals(a111, {"a1":"outer","B":[{"b1":"middle","C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]}, + {"b1":"middle","C":[{"c1":"inner","#content":"Deep Value3"}, + {"c1":"inner","#content":"Deep Value2"}]}]}); + + A112|error a112 = parseAsType(x11); + test:assertEquals(a112, {"a1":"outer","B":[{"b1":"middle","C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]}, + {"b1":"middle","C":[{"c1":"inner","#content":"Deep Value3"}, + {"c1":"inner","#content":"Deep Value2"}]}]}); +} + +xml x12 = xml ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value4 + + `; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type A121 record { + string a1; + (B121|B122)[] B; +}; + +type A122 record {| + string a1; + (B121|B122)[]...; +|}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type B121 record { + string b1; + C121[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type B122 record { + string b1; + C121[] C; +}; + +@Namespace { + prefix: "ns3", + uri: "http://example.com/ns3" +} +type C121 record { + string c1; + string \#content; +}; + +@test:Config +function testTraverseUnionTypes12() { + A121|error a121 = parseAsType(x12); + test:assertEquals(a121, {"a1":"outer","B":[{"b1":"middle", "C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]},{"b1":"middle", + "C":[{"c1":"inner","#content":"Deep Value3"},{"c1":"inner","#content":"Deep Value4"}]}]}); + A122|error a122 = parseAsType(x12); + test:assertEquals(a122, {"a1":"outer","B":[{"b1":"middle", "C":[{"c1":"inner","#content":"Deep Value1"}, + {"c1":"inner","#content":"Deep Value2"}]},{"b1":"middle", + "C":[{"c1":"inner","#content":"Deep Value3"},{"c1":"inner","#content":"Deep Value4"}]}]}); +} diff --git a/ballerina/tests/union_error_tests.bal b/ballerina/tests/union_error_tests.bal new file mode 100644 index 0000000..ff7a5de --- /dev/null +++ b/ballerina/tests/union_error_tests.bal @@ -0,0 +1,149 @@ +import ballerina/test; + +xml xe1 = xml `text`; +xml xe2 = xml `text`; +xml xe3 = xml `texttext`; +xml xe4 = xml ` + texttexttext + texttexttext + `; +xml xe5 = xml ` + + Deep Value1 + Deep Value2 + + + Deep Value3 + Deep Value2 + + `; + +type E1 record { + boolean|()|int|decimal|record {} \#content; +}; + +type E2 record { + record {boolean|()|int|decimal|record {} \#content;} B; +}; + +type E22 record { + record {boolean|()|int|decimal|record {} \#content;}|record {record {int \#content;} \#content;} B; +}; + +type E3 record { + (record {int \#content;}|record {boolean \#content;})[] B; +}; + +type E32 record { + record {int \#content;}[]|record {boolean \#content;}[] B; +}; + +type E4P1 record {boolean \#content;}; +type E4P12 record {int \#content;}|E4P1; + +type E4 record { + record{E4P12[] C;}[] B; +}; + +type E4P2 record {boolean \#content;}[]; +type E4P22 record {int \#content;}[]|E4P2; + +type E42 record { + record{E4P22 C;}[] B; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type E5 record { + string a1; + (F111|F112)[] B; +}; + +@Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" +} +type F111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (G111|G112)[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns2" +} +type F112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string b1; + (G111|G112)[] C; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns1" +} +type G111 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@Namespace { + prefix: "ns2", + uri: "http://example.com/ns1" +} +type G112 record { + @Namespace { + prefix: "ns1", + uri: "http://example.com/ns1" + } + string c1; + string \#content; +}; + +@test:Config +function testErrorUnion() { + E1|error e1 = parseAsType(xe1); + test:assertTrue(e1 is error); + test:assertEquals((e1).message(), "field '#content' cannot be converted into the type '(boolean|int|decimal|data.xmldata:record {| anydata...; |})?'"); + + E2|error e2 = parseAsType(xe2); + test:assertTrue(e2 is error); + test:assertEquals((e2).message(), "field '#content' cannot be converted into the type '(boolean|int|decimal|data.xmldata:record {| anydata...; |})?'"); + + E22|error e22 = parseAsType(xe2); + test:assertTrue(e22 is error); + test:assertEquals((e22).message(), "field 'B' cannot be converted into the type '(data.xmldata:record {| (boolean|int|decimal|record {| anydata...; |})? #content; anydata...; |}|data.xmldata:record {| record {| int #content; anydata...; |} #content; anydata...; |})'"); + + E3|error e3 = parseAsType(xe3); + test:assertTrue(e3 is error); + test:assertEquals((e3).message(), "field 'B' cannot be converted into the type '(data.xmldata:record {| int #content; anydata...; |}|data.xmldata:record {| boolean #content; anydata...; |})[]'"); + + E32|error e32 = parseAsType(xe3); + test:assertTrue(e32 is error); + test:assertEquals((e32).message(), "field 'B' cannot be converted into the type '(data.xmldata:record {| int #content; anydata...; |}[]|data.xmldata:record {| boolean #content; anydata...; |}[])'"); + + E4|error e4 = parseAsType(xe4); + test:assertTrue(e4 is error); + test:assertEquals((e4).message(), "field 'C' cannot be converted into the type 'data.xmldata:E4P12[]'"); + + E42|error e42 = parseAsType(xe4); + test:assertTrue(e42 is error); + test:assertEquals((e42).message(), "field 'C' cannot be converted into the type 'ballerina/data.xmldata:1:E4P22'"); + + E5|error e5 = parseAsType(xe5); + test:assertTrue(e5 is error); + test:assertEquals((e5).message(), "field 'B' cannot be converted into the type '(data.xmldata:F111|data.xmldata:F112)[]'"); +} diff --git a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTest.java index 81c1040..ef12cf5 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTest.java @@ -55,78 +55,6 @@ public void testDuplicateFieldNegative2() { "invalid field: duplicate field found"); } - @Test - public void testUnsupportedUnionTypeNegative1() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_3").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 2); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - } - - @Test - public void testUnsupportedUnionTypeNegative2() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_4").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 2); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - } - - @Test - public void testUnsupportedTypeNegative1() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_5").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 4); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - } - - @Test - public void testUnsupportedTypeNegative2() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_7").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 8); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(4).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - Assert.assertEquals(errorDiagnosticsList.get(5).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(6).diagnosticInfo().messageFormat(), - "unsupported type: the record field does not support the expected type"); - Assert.assertEquals(errorDiagnosticsList.get(7).diagnosticInfo().messageFormat(), - "unsupported union type: union type does not support multiple non-primitive record types"); - } - @Test public void testChildRecordWithNameAnnotNegative() { DiagnosticResult diagnosticResult = @@ -197,32 +125,4 @@ public void testCompilerPluginWithAProjectWithSubModule() { Assert.assertEquals(warningDiagnosticsList.get(0).diagnosticInfo().messageFormat(), "invalid annotation attachment: child record does not allow name annotation"); } - - @Test - public void testComplexUnionTypeCaseWhenUserDefinedModulePrefix() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_11").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 1); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "invalid type: expected a record type"); - } - - @Test - public void testComplexUnionTypeCaseWhenUserDefinedModulePrefix2() { - DiagnosticResult diagnosticResult = - CompilerPluginTestUtils.loadPackage("sample_package_12").getCompilation().diagnosticResult(); - List errorDiagnosticsList = diagnosticResult.diagnostics().stream() - .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) - .collect(Collectors.toList()); - Assert.assertEquals(errorDiagnosticsList.size(), 3); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), - "invalid type: expected a record type"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), - "invalid type: expected a record type"); - Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), - "invalid type: expected a record type"); - } } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/Ballerina.toml deleted file mode 100644 index 0d3fd35..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/Ballerina.toml +++ /dev/null @@ -1,4 +0,0 @@ -[package] -org = "xmldata_test" -name = "sample_11" -version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/main.bal deleted file mode 100644 index 48ff508..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/main.bal +++ /dev/null @@ -1,8 +0,0 @@ -import ballerina/data.xmldata as xd; - -type UnionType record {|int a;|}|record {|string b;|}; - -public function main() returns error? { - string str = string `{"a": 1, "b": "str"}`; - UnionType _ = check xd:parseString(str); -} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/Ballerina.toml deleted file mode 100644 index 93df2bb..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/Ballerina.toml +++ /dev/null @@ -1,4 +0,0 @@ -[package] -org = "xmldata_test" -name = "sample_12" -version = "0.1.0" diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_1.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_1.bal deleted file mode 100644 index 4cced6f..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_1.bal +++ /dev/null @@ -1,6 +0,0 @@ -import ballerina/data.xmldata as xd; - -public function testFunc() returns error? { - string str = string `{"a": 1, "b": "str"}`; - UnionType _ = check xd:parseString(str); -} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_2.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_2.bal deleted file mode 100644 index 710f15b..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_2.bal +++ /dev/null @@ -1,6 +0,0 @@ -import ballerina/data.xmldata as xd2; - -public function testFunc2() returns error? { - string str = string `{"a": 1, "b": "str"}`; - UnionType _ = check xd2:parseString(str); -} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_3.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_3.bal deleted file mode 100644 index ab8ee06..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/file_3.bal +++ /dev/null @@ -1,6 +0,0 @@ -import ballerina/data.xmldata; - -public function testFunc3() returns error? { - string str = string `{"a": 1, "b": "str"}`; - UnionType _ = check xmldata:parseString(str); -} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/types.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/types.bal deleted file mode 100644 index 4383f4d..0000000 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/types.bal +++ /dev/null @@ -1 +0,0 @@ -type UnionType record {|int a;|}|record {|string b;|}; diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataRecordFieldValidator.java index 170ed26..d0da173 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataRecordFieldValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataRecordFieldValidator.java @@ -179,15 +179,18 @@ private void validateExpectedType(TypeSymbol typeSymbol, Optional loca case TYPE_REFERENCE -> validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), location, ctx); case UNION -> { - int nonErrorTypeCount = 0; + int recordCount = 0; for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) { - if (getReferredTypeSymbol(memberTSymbol).typeKind() == TypeDescKind.ERROR) { + TypeDescKind typeDescKind = getReferredTypeSymbol(memberTSymbol).typeKind(); + if (typeDescKind == TypeDescKind.ERROR) { continue; } - nonErrorTypeCount++; - validateExpectedType(memberTSymbol, location, ctx); + if (typeDescKind == TypeDescKind.RECORD) { + validateExpectedType(memberTSymbol, location, ctx); + recordCount++; + } } - if (nonErrorTypeCount > 1) { + if (recordCount == 0) { reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE); } } @@ -211,11 +214,12 @@ private boolean isNotValidExpectedType(TypeSymbol typeSymbol) { } case UNION -> { for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) { - if (getReferredTypeSymbol(memberTSymbol).typeKind() == TypeDescKind.ERROR) { + TypeSymbol referredTypeSymbol = getReferredTypeSymbol(memberTSymbol); + if (referredTypeSymbol.typeKind() == TypeDescKind.ERROR) { continue; } - if (isNotValidExpectedType(memberTSymbol)) { + if (!(referredTypeSymbol.typeKind() == TypeDescKind.RECORD)) { return true; } } @@ -323,7 +327,6 @@ private void processRecordFieldsType(RecordTypeSymbol recordTypeSymbol, SyntaxNo private void validateRecordFieldType(TypeSymbol typeSymbol, Optional location, SyntaxNodeAnalysisContext ctx) { switch (typeSymbol.typeKind()) { - case UNION -> validateUnionType((UnionTypeSymbol) typeSymbol, location, ctx); case NIL, TUPLE -> reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.UNSUPPORTED_TYPE); case ARRAY -> validateRecordFieldType(((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(), location, ctx); case TYPE_REFERENCE -> @@ -331,37 +334,6 @@ private void validateRecordFieldType(TypeSymbol typeSymbol, Optional l } } - private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional location, - SyntaxNodeAnalysisContext ctx) { - int nonPrimitiveMemberCount = 0; - boolean isNilPresent = false; - List memberTypeSymbols = unionTypeSymbol.memberTypeDescriptors(); - for (TypeSymbol memberTypeSymbol : memberTypeSymbols) { - if (isPrimitiveType(memberTypeSymbol)) { - continue; - } - - if (memberTypeSymbol.typeKind() == TypeDescKind.NIL) { - isNilPresent = true; - } - nonPrimitiveMemberCount++; - } - - if (nonPrimitiveMemberCount > 1 || (memberTypeSymbols.size() > 1 && isNilPresent)) { - reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.UNSUPPORTED_UNION_TYPE); - } - } - - private boolean isPrimitiveType(TypeSymbol typeSymbol) { - TypeDescKind kind = typeSymbol.typeKind(); - if (kind == TypeDescKind.TYPE_REFERENCE) { - kind = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); - } - - return kind == TypeDescKind.INT || kind == TypeDescKind.FLOAT || kind == TypeDescKind.DECIMAL - || kind == TypeDescKind.STRING || kind == TypeDescKind.BOOLEAN || kind == TypeDescKind.BYTE; - } - private boolean isAnnotFromXmldata(AnnotationSymbol annotationSymbol) { Optional moduleSymbol = annotationSymbol.getModule(); if (moduleSymbol.isEmpty()) { 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 5e59ab9..0a8cd06 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 @@ -34,6 +34,7 @@ import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.ReferenceType; +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; @@ -45,6 +46,8 @@ import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.stdlib.constraint.Constraints; +import java.io.IOException; +import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -239,6 +242,7 @@ public static Object convertStringToExpType(BString value, Type expType) { result = FromString.fromStringWithType(value, PredefinedTypes.TYPE_JSON); case TypeTags.ARRAY_TAG -> result = convertStringToExpType(value, ((ArrayType) refferedType).getElementType()); + case TypeTags.UNION_TAG -> result = convertStringToUnionExpType(value, expType); default -> result = FromString.fromStringWithType(value, expType); } @@ -248,18 +252,60 @@ public static Object convertStringToExpType(BString value, Type expType) { return result; } - public static void validateRequiredFields(XmlAnalyzerData analyzerData) { - for (Field field : analyzerData.fieldHierarchy.peek().getMembers().values()) { + private static Object convertStringToUnionExpType(BString value, Type expType) { + for (Type memberType : ((UnionType) expType).getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + try { + return convertStringToExpType(value, memberType); + } catch (Exception ex) { + // ignore + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, expType); + } + + public static void validateRequiredFields(XmlAnalyzerData analyzerData, BMap currentMapValue) { + Map fields = analyzerData.fieldHierarchy.peek().getMembers(); + for (QualifiedName key : fields.keySet()) { + // Validate required array size + Field field = fields.get(key); + String fieldName = field.getFieldName(); + Type fieldType = TypeUtils.getReferredType(field.getFieldType()); + if (fieldType.getTag() == TypeTags.ARRAY_TAG) { + ArrayType arrayType = (ArrayType) fieldType; + if (arrayType.getSize() != -1 + && arrayType.getSize() != ((BArray) currentMapValue.get( + StringUtils.fromString(fieldName))).getLength()) { + throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); + } + continue; + } + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, field.getFieldName()); + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, fieldName); + } + } + + Map attributes = analyzerData.attributeHierarchy.peek().getMembers(); + for (QualifiedName key : attributes.keySet()) { + Field field = attributes.get(key); + if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) { + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, field.getFieldName()); } } + } - for (Field attribute : analyzerData.attributeHierarchy.peek().getMembers().values()) { - if (!SymbolFlags.isFlagOn(attribute.getFlags(), SymbolFlags.OPTIONAL)) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, attribute.getFieldName()); + public static boolean isArrayValueAssignable(Type type) { + int typeTag = type.getTag(); + if (typeTag == TypeTags.UNION_TAG) { + for (Type memberType : ((UnionType) type).getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + if (isArrayValueAssignable(memberType.getTag())) { + return true; + } } } + return isArrayValueAssignable(typeTag); } public static boolean isArrayValueAssignable(int typeTag) { @@ -279,12 +325,13 @@ public static BArray createArrayValue(Type type) { default -> throw new IllegalStateException("Unexpected value: " + type.getTag()); }; } + public static MapType getMapTypeFromConstraintType(Type constraintType) { return switch (constraintType.getTag()) { case TypeTags.MAP_TAG -> (MapType) constraintType; case TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.STRING_TAG, TypeTags.BOOLEAN_TAG, TypeTags.BYTE_TAG, - TypeTags.DECIMAL_TAG, TypeTags.JSON_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.OBJECT_TYPE_TAG, - TypeTags.XML_TAG, TypeTags.NULL_TAG -> TypeCreator.createMapType(constraintType); + TypeTags.DECIMAL_TAG, TypeTags.JSON_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.OBJECT_TYPE_TAG, + TypeTags.XML_TAG, TypeTags.NULL_TAG -> TypeCreator.createMapType(constraintType); case TypeTags.ARRAY_TAG -> TypeCreator.createMapType(((ArrayType) constraintType).getElementType()); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> getMapTypeFromConstraintType(TypeUtils.getReferredType(constraintType)); @@ -315,8 +362,8 @@ public static boolean isAnydataOrJson(int typeTag) { 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.BOOLEAN_TAG, TypeTags.STRING_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.MAP_TAG, + TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { return true; } case TypeTags.ARRAY_TAG -> { @@ -334,17 +381,11 @@ public static boolean isSupportedType(Type type) { private static boolean isSupportedUnionType(UnionType type) { for (Type memberType : type.getMemberTypes()) { - switch (memberType.getTag()) { - case TypeTags.RECORD_TYPE_TAG, TypeTags.OBJECT_TYPE_TAG, TypeTags.MAP_TAG, TypeTags.JSON_TAG, - TypeTags.ANYDATA_TAG, TypeTags.XML_TAG -> { - return false; - } - case TypeTags.UNION_TAG -> { - return !isSupportedUnionType(type); - } + if (isSupportedType(memberType)) { + return true; } } - return true; + return false; } public static void updateOptions(BMap options, XmlAnalyzerData analyzerData) { @@ -402,7 +443,7 @@ public static Object getModifiedRecord(BMap input, BString text if (describingType.getTag() == TypeTags.RECORD_TYPE_TAG && describingType.getFlags() != Constants.DEFAULT_TYPE_FLAG) { BArray jsonArray = ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); - BMap recordField = addFields(input, type.getDescribingType()); + BMap recordField = addFields(input, type.getDescribingType()); BMap processedRecord = processParentAnnotation(type.getDescribingType(), recordField); BString rootTagName = processedRecord.getKeys()[0]; jsonArray.append(processedRecord.get(rootTagName)); @@ -457,7 +498,7 @@ private static BMap addFields(BMap input, Type BMap recordValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); Map fields = ((RecordType) type).getFields(); BMap annotations = ((RecordType) type).getAnnotations(); - for (Map.Entry entry: input.entrySet()) { + for (Map.Entry entry : input.entrySet()) { String key = entry.getKey().getValue(); Object value = entry.getValue(); if (fields.containsKey(key)) { @@ -476,7 +517,8 @@ private static void processRecordField(Type fieldType, BMap ann switch (fieldType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> processRecord(key, annotations, recordValue, value, (RecordType) fieldType); - case TypeTags.ARRAY_TAG -> processArray(fieldType, annotations, recordValue, entry); + case TypeTags.ARRAY_TAG -> processArray(TypeUtils.getReferredType(((ArrayType) fieldType) + .getElementType()), annotations, recordValue, entry); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> { Type referredType = TypeUtils.getReferredType(fieldType); if (referredType.getTag() != TypeTags.RECORD_TYPE_TAG) { @@ -506,7 +548,7 @@ private static void processTypeReferenceType(Type fieldType, BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); Type referredType = TypeUtils.getReferredType(fieldType); if (!doesNamespaceDefinedInField) { BMap subRecordAnnotations = ((RecordType) referredType).getAnnotations(); @@ -534,7 +576,7 @@ private static void addNamespaceToSubRecord(String key, BMap na return; } - for (Map.Entry nsAnnotEntry: ((BMap) value).entrySet()) { + for (Map.Entry nsAnnotEntry : ((BMap) value).entrySet()) { subRecord.put(nsAnnotEntry.getKey(), nsAnnotEntry.getValue()); } } @@ -597,7 +639,7 @@ private static BMap getFieldNamespaceAndNameAnnotations(String @SuppressWarnings("unchecked") private static void processRecord(String key, BMap parentAnnotations, BMap record, Object value, RecordType childType) { - BMap parentRecordAnnotations = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + BMap parentRecordAnnotations = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); BMap annotation = childType.getAnnotations(); if (parentAnnotations.size() > 0) { annotation.merge(getFieldNamespaceAndNameAnnotations(key, parentAnnotations), true); @@ -622,7 +664,7 @@ private static void addPrimitiveValue(QName qName, BMap annotat BString key = qName.getPrefix().isBlank() ? localPart : StringUtils.fromString(qName.getPrefix() + ":" + localPart); BString annotationKey = StringUtils.fromString(Constants.FIELD - + (localPart.getValue().replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0"))); + + (localPart.getValue().replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0"))); BMap currentValue; if (record.containsKey(key)) { currentValue = (BMap) record.get(key); @@ -640,12 +682,11 @@ private static void addPrimitiveValue(QName qName, BMap annotat } @SuppressWarnings("unchecked") - private static void processArray(Type childType, BMap annotations, + private static void processArray(Type elementType, BMap annotations, BMap record, Map.Entry entry) { - Type elementType = TypeUtils.getReferredType(((ArrayType) childType).getElementType()); - BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); String keyName = entry.getKey().getValue(); - if (annotations.size() > 0) { + if (!annotations.isEmpty()) { keyName = getKeyNameFromAnnotation(annotations, keyName); processSubRecordAnnotation(annotations, annotationRecord); } @@ -753,7 +794,7 @@ private static BString processAnnotation(BMap annotation, Strin return StringUtils.fromString(key); } - private static void processSubRecordAnnotation(BMap annotation, BMap subRecord) { + private static void processSubRecordAnnotation(BMap annotation, BMap subRecord) { BString[] keys = annotation.getKeys(); for (BString value : keys) { if (isNamespaceAnnotationKey(value.getValue())) { @@ -799,11 +840,11 @@ private static String processNameAnnotation(BMap annotation, St @SuppressWarnings("unchecked") private static String processNamespaceAnnotation(BMap annotation, String key, BString value, - BMap subRecord) { + BMap subRecord) { BMap namespaceAnnotation = (BMap) annotation.get(value); BString uri = (BString) namespaceAnnotation.get(Constants.URI); BString prefix = (BString) namespaceAnnotation.get(Constants.PREFIX); - if (prefix == null) { + if (prefix == null) { subRecord.put(StringUtils.fromString(ATTRIBUTE_PREFIX + "xmlns"), uri); } else { subRecord.put(StringUtils.fromString(ATTRIBUTE_PREFIX + "xmlns:" + prefix), uri); @@ -814,7 +855,7 @@ private static String processNamespaceAnnotation(BMap annotatio @SuppressWarnings("unchecked") private static QName processFieldNamespaceAnnotation(BMap annotation, String key, BString value, - BMap subRecord, boolean isAttributeField) { + BMap subRecord, boolean isAttributeField) { BMap namespaceAnnotation = (BMap) annotation.get(value); BString uri = (BString) namespaceAnnotation.get(Constants.URI); BString prefix = (BString) namespaceAnnotation.get(Constants.PREFIX); @@ -831,7 +872,7 @@ private static QName processFieldNamespaceAnnotation(BMap annot private static String addAttributeToRecord(BString prefix, BString uri, String key, BMap subRecord) { - if (prefix == null) { + if (prefix == null) { subRecord.put(StringUtils.fromString(ATTRIBUTE_PREFIX + "xmlns"), uri); return key; } @@ -910,25 +951,80 @@ public static boolean isEqualQualifiedName(QualifiedName firstQName, QualifiedNa public static boolean isSimpleType(Type type) { return switch (type.getTag()) { case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG, TypeTags.MAP_TAG, TypeTags.OBJECT_TYPE_TAG, - TypeTags.RECORD_TYPE_TAG, TypeTags.XML_TAG -> false; + TypeTags.RECORD_TYPE_TAG, TypeTags.XML_TAG -> false; case TypeTags.ARRAY_TAG -> isSimpleType(((ArrayType) type).getElementType()); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> isSimpleType(((ReferenceType) type).getReferredType()); default -> true; }; } + public static String generateStringFromXmlReader(Reader reader) throws IOException { + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[1024]; + int numCharsRead; + while ((numCharsRead = reader.read(buffer)) != -1) { + builder.append(buffer, 0, numCharsRead); + } + return builder.toString(); + } + + public static boolean isContainsUnionType(Type expType) { + if (expType == null) { + return false; + } + expType = TypeUtils.getReferredType(expType); + if (expType.getTag() == TypeTags.UNION_TAG) { + for (Type memberType : ((UnionType) expType).getMemberTypes()) { + if (!isSimpleType(memberType)) { + return true; + } + } + } + + if (expType.getTag() == TypeTags.ARRAY_TAG) { + Type memberType = TypeUtils.getReferredType(((ArrayType) expType).getElementType()); + return isContainsUnionType(memberType); + } + + if (expType.getTag() == TypeTags.MAP_TAG) { + Type memberType = TypeUtils.getReferredType(((MapType) expType).getConstrainedType()); + return isContainsUnionType(memberType); + } + + if (expType.getTag() == TypeTags.TUPLE_TAG) { + TupleType tupleType = (TupleType) expType; + for (Type type : tupleType.getTupleTypes()) { + if (isContainsUnionType(type)) { + return true; + } + } + return isContainsUnionType(tupleType.getRestType()); + } + + if (expType.getTag() == TypeTags.RECORD_TYPE_TAG) { + RecordType recordType = (RecordType) expType; + for (Field field : recordType.getFields().values()) { + if (isContainsUnionType(field.getFieldType())) { + return true; + } + } + return isContainsUnionType(recordType.getRestFieldType()); + } + return false; + } + /** * Holds data required for the traversing. * * @since 0.1.0 */ public static class XmlAnalyzerData { - public final Stack nodesStack = new Stack<>(); - public final Stack> fieldHierarchy = new Stack<>(); - public final Stack> visitedFieldHierarchy = new Stack<>(); - public final Stack> attributeHierarchy = new Stack<>(); - public final Stack restTypes = new Stack<>(); - public final Stack> arrayIndexes = new Stack<>(); + public Stack nodesStack = new Stack<>(); + public Stack> fieldHierarchy = new Stack<>(); + public Stack> visitedFieldHierarchy = new Stack<>(); + public Stack> attributeHierarchy = new Stack<>(); + public Stack restTypes = new Stack<>(); + public Stack> arrayIndexes = new Stack<>(); public RecordType rootRecord; public Field currentField; public QualifiedName rootElement; @@ -936,5 +1032,41 @@ public static class XmlAnalyzerData { public String textFieldName; public boolean allowDataProjection; public boolean useSemanticEquality; + + @SuppressWarnings("unchecked") + public static XmlAnalyzerData copy(XmlAnalyzerData analyzerData) { + XmlAnalyzerData data = new XmlAnalyzerData(); + data.nodesStack = (Stack) analyzerData.nodesStack.clone(); + data.fieldHierarchy = (Stack>) analyzerData.fieldHierarchy.clone(); + data.visitedFieldHierarchy = (Stack>) analyzerData.visitedFieldHierarchy.clone(); + data.attributeHierarchy = (Stack>) analyzerData.attributeHierarchy.clone(); + data.restTypes = (Stack) analyzerData.restTypes.clone(); + data.arrayIndexes = (Stack>) analyzerData.arrayIndexes.clone(); + data.rootRecord = analyzerData.rootRecord; + data.currentField = analyzerData.currentField; + data.rootElement = analyzerData.rootElement; + data.attributePrefix = analyzerData.attributePrefix; + data.textFieldName = analyzerData.textFieldName; + data.allowDataProjection = analyzerData.allowDataProjection; + data.useSemanticEquality = analyzerData.useSemanticEquality; + + return data; + } + + public void resetFrom(XmlAnalyzerData analyzerData) { + this.nodesStack = analyzerData.nodesStack; + this.fieldHierarchy = analyzerData.fieldHierarchy; + this.visitedFieldHierarchy = analyzerData.visitedFieldHierarchy; + this.attributeHierarchy = analyzerData.attributeHierarchy; + this.restTypes = analyzerData.restTypes; + this.arrayIndexes = analyzerData.arrayIndexes; + this.rootRecord = analyzerData.rootRecord; + this.currentField = analyzerData.currentField; + this.rootElement = analyzerData.rootElement; + this.attributePrefix = analyzerData.attributePrefix; + this.textFieldName = analyzerData.textFieldName; + this.allowDataProjection = analyzerData.allowDataProjection; + this.useSemanticEquality = analyzerData.useSemanticEquality; + } } } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticErrorCode.java index 0d90190..7f09304 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticErrorCode.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticErrorCode.java @@ -41,7 +41,9 @@ public enum DiagnosticErrorCode { UNSUPPORTED_TYPE("XML_ERROR_014", "unsupported.type"), STREAM_BROKEN("XML_ERROR_015", "stream.broken"), XML_PARSE_ERROR("XML_ERROR_016", "xml.parse.error"), - UNDEFINED_FIELD("XML_ERROR_0017", "undefined.field"); + UNDEFINED_FIELD("XML_ERROR_0017", "undefined.field"), + CANNOT_CONVERT_SOURCE_INTO_EXP_TYPE("XML_ERROR_0018", "cannot.convert.source.into.expected.type"), + FIELD_CANNOT_CAST_INTO_TYPE("XML_ERROR_0019", "field.cannot.convert.into.type"); String diagnosticId; String messageKey; 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 98a5a72..113f1b8 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 @@ -35,11 +35,13 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.utils.XmlUtils; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.runtime.api.values.BXml; import java.io.Reader; import java.util.ArrayList; @@ -107,6 +109,12 @@ public static Object parse(Reader reader, BMap options, BTypede public static Object parse(Reader reader, BMap options, Type type) { try { XmlParserData xmlParserData = new XmlParserData(); + if (DataUtils.isContainsUnionType(type)) { + String xmlStr = DataUtils.generateStringFromXmlReader(reader); + BXml xmlValue = XmlUtils.parse(xmlStr); + return XmlTraversal.traverse(xmlValue, options, type); + } + updateOptions(options, xmlParserData); XmlParser xmlParser = new XmlParser(reader); return xmlParser.parse(type, xmlParserData); @@ -324,11 +332,10 @@ private void readText(XMLStreamReader xmlStreamReader, case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType, bText, xmlParserData); case TypeTags.ARRAY_TAG -> - addTextToCurrentNodeIfExpTypeIsArray((ArrayType) fieldType, bFieldName, bText, xmlParserData); - case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> - convertTextAndUpdateCurrentNode(xmlParserData.currentNode, - (BMap) xmlParserData.nodesStack.pop(), - bFieldName, bText, fieldType, xmlParserData); + addTextToCurrentNodeIfExpTypeIsArray((ArrayType) fieldType, bFieldName, bText, xmlParserData); + case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> convertTextAndUpdateCurrentNode(xmlParserData.currentNode, + (BMap) xmlParserData.nodesStack.pop(), + bFieldName, bText, fieldType, xmlParserData); default -> xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType)); } } @@ -344,7 +351,7 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, if (currentElement == null && !currentNode.isEmpty()) { // Add text to the #content field currentNode.put(StringUtils.fromString(Constants.CONTENT), result); } else if (parent.get(currentFieldName) instanceof BArray bArray) { - bArray.add(bArray.getLength() - 1, result); + bArray.add(bArray.getLength() - 1, result); } else { parent.put(currentFieldName, result); } @@ -357,10 +364,9 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, @SuppressWarnings("unchecked") private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString bFieldName, BString bText, XmlParserData xmlParserData) { - Type referredType = TypeUtils.getReferredType(fieldType.getElementType()); - int elementTypeTag = referredType.getTag(); - switch (elementTypeTag) { - case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) referredType, + Type elementType = TypeUtils.getReferredType(fieldType.getElementType()); + switch (elementType.getTag()) { + case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) elementType, bText, xmlParserData); case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> { BArray tempArr = (BArray) ((BMap) xmlParserData.nodesStack.peek()).get(bFieldName); @@ -429,7 +435,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 -> { return convertStringToExpType(value, expType); } case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> { @@ -557,8 +563,8 @@ private void initializeNextValueBasedOnExpectedType(String fieldName, Type field case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> - initializeNextValueBasedOnExpectedType(fieldName, TypeUtils.getReferredType(fieldType), temp, - currentNode, xmlParserData); + initializeNextValueBasedOnExpectedType(fieldName, TypeUtils.getReferredType(fieldType), temp, + currentNode, xmlParserData); } } @@ -574,8 +580,8 @@ private void updateNextArrayMember(XMLStreamReader xmlStreamReader, XmlParserDat case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> - updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, - TypeUtils.getReferredType(type)); + updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, + TypeUtils.getReferredType(type)); } } @@ -777,7 +783,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x handleAttributesRest(xmlStreamReader, restType, next, useSemanticEquality); Object temp = xmlParserData.currentNode.get( - StringUtils.fromString(lastElement.getLocalPart())); + StringUtils.fromString(lastElement.getLocalPart())); BMap mapValue = xmlParserData.currentNode; if (temp == null) { xmlParserData.currentNode.put(currentFieldName, next); @@ -861,7 +867,7 @@ private boolean endElementRest(XMLStreamReader xmlStreamReader, XmlParserData xm if (xmlParserData.siblings.contains(elemQName)) { // TODO: This place behaviour is strange need to check and fix it, Properly. popMappingTypeStacks(xmlParserData); -// xmlParserData.attributeHierarchy.pop(); + // xmlParserData.attributeHierarchy.pop(); xmlParserData.arrayIndexes.pop(); } xmlParserData.restFieldsPoints.pop(); @@ -899,8 +905,8 @@ private void readTextRest(XMLStreamReader xmlStreamReader, @SuppressWarnings("unchecked") private void convertTextRestAndUpdateCurrentNodeForRestType(BMap currentNode, - BString currentFieldName, - BString bText, Type restType, BString textFieldName) { + BString currentFieldName, + BString bText, Type restType, BString textFieldName) { Object currentElement = currentNode.get(currentFieldName); Object result = convertStringToRestExpType(bText, restType); diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java index 21a0f0e..0d1776f 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java @@ -33,6 +33,7 @@ import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.types.XmlNodeType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; @@ -83,29 +84,57 @@ static class XmlTree { private Object currentNode; public Object traverseXml(BXml xml, BMap options, Type type) { + XmlAnalyzerData analyzerData = new XmlAnalyzerData(); + DataUtils.updateOptions(options, analyzerData); + return traverseXml(xml, analyzerData, type); + } + + public Object traverseXml(BXml xml, XmlAnalyzerData analyzerData, Type type) { Type referredType = TypeUtils.getReferredType(type); switch (referredType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> { - XmlAnalyzerData analyzerData = new XmlAnalyzerData(); - DataUtils.updateOptions(options, analyzerData); - RecordType recordType = (RecordType) referredType; - currentNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); - BXml nextXml = validateRootElement(xml, recordType, analyzerData); - Object resultRecordValue = traverseXml(nextXml, recordType, analyzerData); - DataUtils.validateRequiredFields(analyzerData); - return resultRecordValue; + return traverseXmlWithRecordAsExpectedType(xml, analyzerData, (RecordType) referredType); } case TypeTags.MAP_TAG -> { - MapType mapType = (MapType) referredType; - RecordType anonRecType = TypeCreator.createRecordType(Constants.ANON_TYPE, mapType.getPackage(), 0, - new HashMap<>(), mapType.getConstrainedType(), false, 0); - return traverseXml(xml, options, anonRecType); + return traverseXmlWithMapAsExpectedType(xml, (MapType) referredType, analyzerData); } - default -> { - return DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, Constants.RECORD_OR_MAP, - type.getName()); + case TypeTags.UNION_TAG -> { + return traverseXmlToUnion(xml, analyzerData, (UnionType) referredType); + } + default -> throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, Constants.RECORD_OR_MAP, type); + } + } + + private Object traverseXmlWithRecordAsExpectedType(BXml xml, + XmlAnalyzerData analyzerData, RecordType recordType) { + currentNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); + BXml nextXml = validateRootElement(xml, recordType, analyzerData); + Object resultRecordValue = traverseXml(nextXml, recordType, analyzerData); + DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); + return resultRecordValue; + } + + private Object traverseXmlWithMapAsExpectedType(BXml xml, MapType mapType, XmlAnalyzerData analyzerData) { + RecordType anonRecType = TypeCreator.createRecordType(Constants.ANON_TYPE, mapType.getPackage(), 0, + new HashMap<>(), mapType.getConstrainedType(), false, 0); + return traverseXml(xml, analyzerData, anonRecType); + } + + private Object traverseXmlToUnion(BXml xml, XmlAnalyzerData options, UnionType unionType) { + XmlAnalyzerData clonedAnalyzerData = XmlAnalyzerData.copy(options); + for (Type memberType: unionType.getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + try { + if (memberType.getTag() == TypeTags.ERROR_TAG) { + continue; + } + return traverseXml(xml, options, memberType); + } catch (Exception ex) { + options.resetFrom(clonedAnalyzerData); + // ignore } } + throw DiagnosticLog.error(DiagnosticErrorCode.CANNOT_CONVERT_SOURCE_INTO_EXP_TYPE, unionType); } private Object traverseXml(BXml xml, Type type, XmlAnalyzerData analyzerData) { @@ -144,8 +173,30 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { BString fieldName = StringUtils.fromString(currentField.getFieldName()); Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); - Object convertedValue = DataUtils.convertStringToExpType(StringUtils.fromString(text), fieldType); + Object convertedValue = null; Object value = mapValue.get(fieldName); + if (fieldType.getTag() == TypeTags.UNION_TAG) { + XmlAnalyzerData clonedAnalyzerData = XmlAnalyzerData.copy(analyzerData); + for (Type memberType: ((UnionType) fieldType).getMemberTypes()) { + try { + if (!(value instanceof BArray) && memberType.getTag() == TypeTags.ARRAY_TAG) { + continue; + } + convertedValue = DataUtils.convertStringToExpType(StringUtils.fromString(text), memberType); + fieldType = memberType; + break; + } catch (Exception ex) { + analyzerData.resetFrom(clonedAnalyzerData); + // ignore + } + } + if (convertedValue == null) { + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, fieldName, fieldType); + } + } else { + convertedValue = DataUtils.convertStringToExpType(StringUtils.fromString(text), fieldType); + } + if (value instanceof BArray) { if (fieldName.getValue().equals(textFieldName)) { mapValue.put(fieldName, convertedValue); @@ -165,6 +216,9 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { } ((BArray) value).add(currentIndex, convertedValue); } else { + if (fieldType.getTag() == TypeTags.ARRAY_TAG) { + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, fieldName, fieldType); + } mapValue.put(fieldName, convertedValue); } } @@ -177,12 +231,16 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { if (analyzerData.visitedFieldHierarchy.peek().contains(elementQName)) { currentField = analyzerData.visitedFieldHierarchy.peek().get(elementQName); Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); - if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) { + if (!DataUtils.isArrayValueAssignable(fieldType)) { throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, currentField.getFieldName()); } } else { - currentField = fieldsMap.remove(elementQName); + currentField = fieldsMap.get(elementQName); + if (currentField != null + && TypeUtils.getReferredType(currentField.getFieldType()).getTag() != TypeTags.ARRAY_TAG) { + fieldsMap.remove(elementQName); + } } analyzerData.currentField = currentField; @@ -235,10 +293,32 @@ private void convertToFieldType(BXmlItem xmlItem, Field currentField, String fie case TypeTags.TYPE_REFERENCED_TYPE_TAG -> convertToFieldType(xmlItem, currentField, fieldName, TypeUtils.getReferredType(currentFieldType), mapValue, analyzerData); + case TypeTags.UNION_TAG -> convertFieldTypeToUnion(xmlItem, currentField, fieldName, + currentFieldType, mapValue, analyzerData); default -> traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); } } + private void convertFieldTypeToUnion(BXmlItem xmlItem, Field currentField, String fieldName, + Type currentFieldType, BMap mapValue, XmlAnalyzerData analyzerData) { + XmlAnalyzerData clonedAnalyzerData = XmlAnalyzerData.copy(analyzerData); + for (Type memberType: ((UnionType) currentFieldType).getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + try { + if (memberType.getTag() == TypeTags.ERROR_TAG) { + continue; + } + convertToFieldType(xmlItem, currentField, fieldName, memberType, mapValue, analyzerData); + return; + } catch (Exception ex) { + analyzerData.resetFrom(clonedAnalyzerData); + mapValue.put(StringUtils.fromString(fieldName), null); + // ignore + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, fieldName, currentFieldType); + } + private void convertToArrayType(BXmlItem xmlItem, Field field, BMap mapValue, BString bCurrentFieldName, ArrayType arrayType, XmlAnalyzerData analyzerData) { Object temp = mapValue.get(bCurrentFieldName); @@ -276,10 +356,31 @@ private void convertToArrayMemberType(BXmlItem xmlItem, String fieldName, ArrayT case TypeTags.TYPE_REFERENCED_TYPE_TAG -> convertToArrayMemberType(xmlItem, fieldName, fieldType, TypeUtils.getReferredType(elementType), mapValue, analyzerData); + case TypeTags.UNION_TAG -> convertToUnionMemberType(xmlItem, fieldName, fieldType, + elementType, mapValue, analyzerData); default -> traverseXml(xmlItem.getChildrenSeq(), fieldType, analyzerData); } } + private void convertToUnionMemberType(BXmlItem xmlItem, String fieldName, ArrayType fieldType, + Type elementType, BMap mapValue, XmlAnalyzerData analyzerData) { + XmlAnalyzerData clonedAnalyzerData = XmlAnalyzerData.copy(analyzerData); + for (Type memberType: ((UnionType) elementType).getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + if (memberType.getTag() == TypeTags.ERROR_TAG) { + continue; + } + try { + convertToArrayMemberType(xmlItem, fieldName, fieldType, memberType, mapValue, analyzerData); + return; + } catch (Exception ex) { + analyzerData.resetFrom(clonedAnalyzerData); + // ignore + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, fieldName, fieldType); + } + private void convertToRecordType(BXmlItem xmlItem, Type currentFieldType, String fieldName, RecordType elementType, BMap mapValue, XmlAnalyzerData analyzerData) { @@ -288,7 +389,7 @@ private void convertToRecordType(BXmlItem xmlItem, Type currentFieldType, String RecordType prevRecord = analyzerData.rootRecord; analyzerData.rootRecord = elementType; traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); - DataUtils.validateRequiredFields(analyzerData); + DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); DataUtils.popExpectedTypeStacks(analyzerData); analyzerData.rootRecord = prevRecord; currentNode = analyzerData.nodesStack.pop(); @@ -299,7 +400,7 @@ private void convertToMapType(BXmlItem xmlItem, Type fieldType, Type elementType updateNextMap(elementType, analyzerData); currentNode = updateNextMappingValue(elementType, fieldName, fieldType, mapValue, analyzerData); traverseXml(xmlItem.getChildrenSeq(), fieldType, analyzerData); - DataUtils.validateRequiredFields(analyzerData); + DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); DataUtils.popExpectedTypeStacks(analyzerData); currentNode = analyzerData.nodesStack.pop(); } @@ -345,11 +446,12 @@ private BMap updateNextMappingValue(Type type, String fieldName } Object temp = currentMapValue.get(StringUtils.fromString(fieldName)); - if (temp instanceof BArray) { + if (temp instanceof BArray tempArray) { ArrayType arrayType = (ArrayType) fieldType; int currentIndex = analyzerData.arrayIndexes.peek().get(fieldName); + if (arrayType.getState() == ArrayType.ArrayState.OPEN || currentIndex < arrayType.getSize()) { - ((BArray) temp).add(currentIndex, nextValue); + tempArray.add(currentIndex, nextValue); } else { DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(analyzerData.allowDataProjection); } @@ -386,6 +488,8 @@ private void checkRestTypeAndConvert(BXmlItem xmlItem, String elemName, Type res checkRestTypeAndConvert(xmlItem, elemName, restType, ((ArrayType) restType).getElementType(), mapValue, analyzerData); } + case TypeTags.UNION_TAG -> checkRestTypeAndConvertForUnionTypes(xmlItem, elemName, restType, + elementType, mapValue, analyzerData); default -> { BString bElementName = StringUtils.fromString(elemName); if (mapValue.containsKey(bElementName) && mapValue.get(bElementName) != null) { @@ -407,6 +511,31 @@ private void checkRestTypeAndConvert(BXmlItem xmlItem, String elemName, Type res } } + private void checkRestTypeAndConvertForUnionTypes(BXmlItem xmlItem, String elemName, + Type restType, Type elementType, BMap mapValue, XmlAnalyzerData analyzerData) { + boolean isRestTypeUnion = restType.getTag() == TypeTags.UNION_TAG; + XmlAnalyzerData clonedAnalyzerData = XmlAnalyzerData.copy(analyzerData); + + for (Type memberType: ((UnionType) elementType).getMemberTypes()) { + memberType = TypeUtils.getReferredType(memberType); + if (memberType.getTag() == TypeTags.ERROR_TAG) { + continue; + } + try { + checkRestTypeAndConvert(xmlItem, elemName, isRestTypeUnion ? memberType : restType, + memberType, mapValue, analyzerData); + return; + } catch (Exception ex) { + analyzerData.resetFrom(clonedAnalyzerData); + if (restType.getTag() != TypeTags.ARRAY_TAG) { + mapValue.put(StringUtils.fromString(elemName), null); + } + // ignore + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.FIELD_CANNOT_CAST_INTO_TYPE, elemName, elementType); + } + private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type restType, BMap mapValue, XmlAnalyzerData analyzerData) { BString bElementName = StringUtils.fromString(elemName); @@ -414,7 +543,7 @@ private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type boolean useSemanticEquality = analyzerData.useSemanticEquality; BArray arrayValue; if (!(currentElement instanceof BArray)) { - if (!DataUtils.isArrayValueAssignable(restType.getTag())) { + if (!DataUtils.isArrayValueAssignable(restType)) { throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, restType, elemName); } diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index bcb5cf9..65e56ee 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -70,3 +70,9 @@ error.xml.parse.error=\ error.undefined.field=\ undefined field ''{0}'' in record ''{1}'' + +error.cannot.convert.source.into.expected.type=\ + source value cannot be converted into ''{0}'' + +error.field.cannot.convert.into.type=\ + field ''{0}'' cannot be converted into the type ''{1}''