diff --git a/ballerina/tests/resources/xsd_tests/schemas/schema_1.xsd b/ballerina/tests/resources/xsd_tests/schemas/schema_1.xsd new file mode 100644 index 00000000..786e70b5 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/schemas/schema_1.xsd @@ -0,0 +1,3 @@ + + + diff --git a/ballerina/tests/resources/xsd_tests/schemas/schema_2.xsd b/ballerina/tests/resources/xsd_tests/schemas/schema_2.xsd new file mode 100644 index 00000000..bc9fb555 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/schemas/schema_2.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/ballerina/tests/resources/xsd_tests/schemas/schema_3.xsd b/ballerina/tests/resources/xsd_tests/schemas/schema_3.xsd new file mode 100644 index 00000000..94f57073 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/schemas/schema_3.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ballerina/tests/resources/xsd_tests/schemas/schema_4.xsd b/ballerina/tests/resources/xsd_tests/schemas/schema_4.xsd new file mode 100644 index 00000000..c18078c9 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/schemas/schema_4.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ballerina/tests/resources/xsd_tests/schemas/schema_5.xsd b/ballerina/tests/resources/xsd_tests/schemas/schema_5.xsd new file mode 100644 index 00000000..3d4dd6a8 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/schemas/schema_5.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_1_invalid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_1_invalid_xml.xml new file mode 100644 index 00000000..c684f571 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_1_invalid_xml.xml @@ -0,0 +1 @@ +Sample diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_1_valid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_1_valid_xml.xml new file mode 100644 index 00000000..9f606074 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_1_valid_xml.xml @@ -0,0 +1 @@ +Sample diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_2_invalid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_2_invalid_xml.xml new file mode 100644 index 00000000..3ac2835b --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_2_invalid_xml.xml @@ -0,0 +1,5 @@ + + + Sample Title + + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_2_valid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_2_valid_xml.xml new file mode 100644 index 00000000..5f57d006 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_2_valid_xml.xml @@ -0,0 +1,6 @@ + + + Sample Title + Sample Author + + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_3_invalid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_3_invalid_xml.xml new file mode 100644 index 00000000..646ec8bd --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_3_invalid_xml.xml @@ -0,0 +1,4 @@ + + John Doe + 30 + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_3_valid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_3_valid_xml.xml new file mode 100644 index 00000000..f34888a1 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_3_valid_xml.xml @@ -0,0 +1,4 @@ + + John Doe + 30 + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_4_invalid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_4_invalid_xml.xml new file mode 100644 index 00000000..110e9428 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_4_invalid_xml.xml @@ -0,0 +1,4 @@ + + Toyota + Yamaha + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_4_valid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_4_valid_xml.xml new file mode 100644 index 00000000..81605952 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_4_valid_xml.xml @@ -0,0 +1,3 @@ + + Toyota + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_5_invalid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_5_invalid_xml.xml new file mode 100644 index 00000000..e50c87f2 --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_5_invalid_xml.xml @@ -0,0 +1,3 @@ + + 12345 + diff --git a/ballerina/tests/resources/xsd_tests/xml_values/schema_5_valid_xml.xml b/ballerina/tests/resources/xsd_tests/xml_values/schema_5_valid_xml.xml new file mode 100644 index 00000000..98e265fc --- /dev/null +++ b/ballerina/tests/resources/xsd_tests/xml_values/schema_5_valid_xml.xml @@ -0,0 +1,4 @@ + + 12345 + Laptop + diff --git a/ballerina/tests/test_from_json.bal b/ballerina/tests/test_from_json.bal new file mode 100644 index 00000000..2adb500a --- /dev/null +++ b/ballerina/tests/test_from_json.bal @@ -0,0 +1,38 @@ +import ballerina/test; + +@test:Config {groups: ["xsd", "to_xml"], dataProvider: fromJsonDataProvider} +function testFromJson(json value, xml expected) returns error?{ + xml|Error xmlResult = fromJson(value); + test:assertEquals(xmlResult, expected); +} + +function fromJsonDataProvider() returns [json, xml][] { + return [ + [{a: {b: 2, c: 3}}, xml `23`], + [{a: {a: 1}}, xml `1`], + [{a: {d: 4, e: {f: 5, g: "text"}}}, xml `45text`], + [{root: {nested: {value1: "example", value2: 10}}}, xml `example10`], + [{book: {title: "XML Guide", author: "John Doe", year: 2024}}, xml `XML GuideJohn Doe2024`], + [{library: {section: {book1: "Book A", book2: "Book B"}}}, xml `
Book ABook B
`], + [{person: {name: "Alice", details: {age: 30, city: "Wonderland"}}}, xml `Alice
30Wonderland
`], + [{catalog: {item: [{id: 1, name: "Item 1"}, {id: 2, name: "Item 2"}]}}, xml `1Item 12Item 2`], + [{company: {employee: {id: 1001, name: "Bob", department: "Engineering"}}}, xml `1001BobEngineering`], + [{'order: {orderId: 5001, items: {item1: "Widget", item2: "Gadget"}}}, xml `5001WidgetGadget`], + [{menu: {dish: [{name: "Pasta", price: 12.5}, {name: "Salad", price: 8.0}]}}, xml `Pasta12.5Salad8.0`], + [{report: {entries: [{date: "2024-10-01", detail: "Entry 1"}, {date: "2024-10-02", detail: "Entry 2"}]}}, xml `2024-10-01Entry 12024-10-02Entry 2`], + [{shoppingList: {items: [{item: "Apples", quantity: 5}, {item: "Bananas", quantity: 3}]}}, xml `Apples5Bananas3`], + [{conference: {session: [{topic: "AI Trends", speaker: "Dr. Smith"}, {topic: "Web 3.0", speaker: "Jane Doe"}]}}, xml `AI TrendsDr. SmithWeb 3.0Jane Doe`], + [{project: {tasks: [{title: "Setup Environment", status: "Completed"}, {title: "Develop Module", status: "In Progress"}]}}, xml `Setup EnvironmentCompletedDevelop ModuleIn Progress`], + [{school: {students: [{name: "Emily", grade: "A"}, {name: "Michael", grade: "B"}]}}, xml `EmilyAMichaelB`], + [{portfolio: {stocks: [{symbol: "AAPL", shares: 50}, {symbol: "TSLA", shares: 30}]}}, xml `AAPL50TSLA30`], + [{university: {course: [{name: "Mathematics", credits: 4}, {name: "History", credits: 3}]}}, xml `Mathematics4History3`], + [{research: {papers: [{title: "Quantum Computing", author: "Alice Cooper"}, {title: "Blockchain Advances", author: "John Smith"}]}}, xml `Quantum ComputingAlice CooperBlockchain AdvancesJohn Smith`], + [{movieCollection: {movies: [{title: "Inception", director: "Nolan"}, {title: "Interstellar", director: "Nolan"}]}}, xml `InceptionNolanInterstellarNolan`], + [{library: {books: [{title: "XML Guide", author: "John Doe", year: 2024}, {title: "JSON Primer", author: "Jane Smith", year: 2023}]}}, xml `XML GuideJohn Doe2024JSON PrimerJane Smith2023`], + [{shoppingList: {items: [{item: "Apples", quantity: 5}, {item: "Bananas", quantity: 3}]}}, xml `Apples5Bananas3`], + [{project: {tasks: [{title: "Setup Environment", status: "Completed"}, {title: "Develop Module", status: "In Progress"}]}}, xml `Setup EnvironmentCompletedDevelop ModuleIn Progress`], + [{school: {students: [{name: "Emily", grade: "A"}, {name: "Michael", grade: "B"}]}}, xml `EmilyAMichaelB`], + [{conference: {sessions: [{topic: "AI Trends", speaker: "Dr. Smith"}, {topic: "Web 3.0", speaker: "Jane Doe"}]}}, xml `AI TrendsDr. SmithWeb 3.0Jane Doe`], + [{catalog: {items: [{id: 1, name: "Item 1"}, {id: 2, name: "Item 2"}]}}, xml `1Item 12Item 2`] + ]; +} diff --git a/ballerina/tests/toXml_test.bal b/ballerina/tests/toXml_test.bal index b9630914..47694791 100644 --- a/ballerina/tests/toXml_test.bal +++ b/ballerina/tests/toXml_test.bal @@ -95,7 +95,7 @@ function testToXmlWithNameAnnotation() returns error? { xml xmlVal1 = xml `1234`; Rec2 rec1 = check parseAsType(xmlVal1); xml result = check toXml(rec1); - test:assertTrue(result == xmlVal1); + test:assertEquals(result, xmlVal1); } @test:Config { diff --git a/ballerina/tests/xsd_choice_array_test.bal b/ballerina/tests/xsd_choice_array_test.bal new file mode 100644 index 00000000..d5e98c41 --- /dev/null +++ b/ballerina/tests/xsd_choice_array_test.bal @@ -0,0 +1,335 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdChoiceArray record {| + @Choice { + minOccurs: 1, + maxOccurs: 2 + } + Choice_XsdChoiceArray choice_XsdChoiceArray; +|}; + +type Choice_XsdChoiceArray record {| + int[] age?; + float[] salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArray() returns error? { + string xmlStr; + XsdChoiceArray|Error v; + + xmlStr = string `1311.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray: {age: [13], salary: [11.1]}}); + + xmlStr = string `1312`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray: {age: [13, 12]}}); + + xmlStr = string `11.112.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray: {salary: [11.1, 12.1]}}); + + xmlStr = string `1311.11414.11515.1`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArray' occurs more than the max allowed times"); +} + +type XsdChoiceArray2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 2 + } + Choice_XsdChoiceArray2 choice_XsdChoiceArray2; + + @Choice { + minOccurs: 0, + maxOccurs: 2 + } + Choice_XsdChoiceArray2_2 choice_XsdChoiceArray2_2?; +|}; + +type Choice_XsdChoiceArray2 record {| + int[] age?; + float[] salary?; +|}; + +type Choice_XsdChoiceArray2_2 record {| + int[] age2?; + float[] salary2?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArray2() returns error? { + string xmlStr; + XsdChoiceArray2|Error v; + + xmlStr = string `1311.11311.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray2: {age: [13], salary: [11.1]}, choice_XsdChoiceArray2_2: {age2: [13], salary2: [11.1]}}); + + xmlStr = string `13131313`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray2: {age: [13, 13]}, choice_XsdChoiceArray2_2: {age2: [13, 13]}}); + + xmlStr = string `13131311.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray2: {age: [13, 13]}, choice_XsdChoiceArray2_2: {age2: [13], salary2: [11.1]}}); + + xmlStr = string `13131311.11415`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArray2_2' occurs more than the max allowed times"); + + xmlStr = string `13131313`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArray2' occurs more than the max allowed times"); + + xmlStr = string `1313`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray2: {age: [13, 13]}}); + + xmlStr = string `1313`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "required field 'choice_XsdChoiceArray2' not present in XML"); +} + +type XSDChoiceArrayRecord13 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_XSDChoiceArrayRecord13_1 choice_XSDChoiceArrayRecord13_1; + + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_XSDChoiceArrayRecord13_2 choice_XSDChoiceArrayRecord13_2; +}; + +type Choice_XSDChoiceArrayRecord13_1 record { + Choice_Array_A_3[] field1?; + Choice_Array_B_3[] field2?; + Choice_Array_C_3[] field3?; +}; + +type Choice_XSDChoiceArrayRecord13_2 record { + Choice_Array_D_3[] field4?; + Choice_Array_E_3[] field5?; + Choice__Array_F_3[] field6?; +}; + +type Choice_Array_A_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_Array_3 value1; +}; + +type Choice_Array_B_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice2_Array_3 value2; +}; + +type Choice_Array_C_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice3_Array_3 value3; +}; + +type Choice_Array_D_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_Array_3 value1; +}; + +type Choice_Array_E_3 record { + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice2_Array_3 value2; +}; + +type Choice__Array_F_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice3_Array_3 value3; +}; + +type Choice_Array_3 record { + string[] a?; + string[] b?; + string[] c?; +}; + +type Choice2_Array_3 record { + string[] d?; + string[] e?; + string[] f?; +}; + +type Choice3_Array_3 record { + string[] g?; + string[] h?; + string[] i?; +}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXSDChoiceArrayRecord4() returns error? { + string xmlStr = string `123123123123123123`; + XSDChoiceArrayRecord13|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceArrayRecord13_1: {field1: [{value1: {a: ["1"], b:["2"], c: ["3"]}}],field2: [{value2: {d: ["1"], e: ["2"], f: ["3"]}}], field3: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}, choice_XSDChoiceArrayRecord13_2: {field4: [{value1: {a: ["1"], b: ["2"], c: ["3"]}}], field5: [{value2: {d: ["1"], e: ["2"], f:["3"]}}], field6: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}}); + + xmlStr = string `22123233123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceArrayRecord13_1: {field1: [{value1: {b:["2", "2"]}}, {value1: {a: ["1"], b:["2"], c: ["3"]}}], field3: [{value3: {h: ["2"], i: ["3", "3"]}}]}, choice_XSDChoiceArrayRecord13_2: {field5: [{value2: {d: ["1"], e: ["2"], f:["3"]}}, {value2: {d: ["1"], e: ["2"], f:["3"]}}], field6: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}}); + + xmlStr = string `22123233123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is error); + test:assertEquals((v2).message(), "'choice_XSDChoiceArrayRecord13_2' occurs less than the min required times"); + + xmlStr = string `221232331123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is error); + test:assertEquals((v2).message(), "'value2' occurs less than the min required times"); +} + +type XsdChoiceArray5 record {| + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_XsdChoiceArray5 choice_XsdChoiceArray5; +|}; + +type Choice_XsdChoiceArray5 record {| + int[] age?; + float[] salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArray5() returns error? { + string xmlStr; + XsdChoiceArray5|Error v; + + xmlStr = string `1311.114`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray5: {age: [13, 14], salary: [11.1]}}); + + xmlStr = string `1311.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XsdChoiceArray5: {age: [13], salary: [11.1]}}); + + xmlStr = string `1311.11414.11515.11515.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XsdChoiceArray5' occurs more than the max allowed times"); + + xmlStr = string `13`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XsdChoiceArray5' occurs less than the min required times"); +} + +type XSDChoiceArrayRecord6 record { + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice_XSDChoiceArrayRecord6_1 choice_XSDChoiceArrayRecord6_1; + + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice_XSDChoiceArrayRecord6_2 choice_XSDChoiceArrayRecord6_2; +}; + +type Choice_XSDChoiceArrayRecord6_1 record { + Choice_Array_A_6[] field1?; + Choice_Array_B_6[] field2?; +}; + +type Choice_XSDChoiceArrayRecord6_2 record { + Choice_Array_D_6[] field4?; + Choice_Array_E_6[] field5?; +}; + +type Choice_Array_A_6 record { + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_Array_6 value1; +}; + +type Choice_Array_B_6 record { + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice2_Array_6 value2; +}; + +type Choice_Array_D_6 record { + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice_Array_6 value1; +}; + +type Choice_Array_E_6 record { + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice2_Array_6 value2; +}; + +type Choice_Array_6 record { + string[] a?; +}; + +type Choice2_Array_6 record { + string[] d?; +}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXSDChoiceArrayRecord6() returns error? { + string xmlStr = string `1111111111111111`; + XSDChoiceArrayRecord6|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceArrayRecord6_1: {field1: [{value1: {a: ["1", "1"]}}, {value1: {a:["1", "1"]}}], field2: [{value2: {d: ["1", "1"]}}, {value2: {d: ["1", "1"]}}]}, choice_XSDChoiceArrayRecord6_2: {field4: [{value1: {a: ["1", "1"]}}, {value1: {a:["1", "1"]}}], field5: [{value2: {d: ["1", "1"]}}, {value2: {d: ["1","1"]}}]}}); +} diff --git a/ballerina/tests/xsd_choice_array_test_with_parse_type.bal b/ballerina/tests/xsd_choice_array_test_with_parse_type.bal new file mode 100644 index 00000000..9ee44904 --- /dev/null +++ b/ballerina/tests/xsd_choice_array_test_with_parse_type.bal @@ -0,0 +1,264 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XsdChoiceArrayWithXmlValue record {| + @Choice { + minOccurs: 1, + maxOccurs: 2 + } + Choice_XsdChoiceArrayWithXmlValue choice_XsdChoiceArrayWithXmlValue; +|}; + +type Choice_XsdChoiceArrayWithXmlValue record {| + int[] age?; + float[] salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArrayWithXmlValue() returns error? { + xml xmlValue; + XsdChoiceArrayWithXmlValue|Error v; + + xmlValue = xml `1311.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue: {age: [13], salary: [11.1]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `1312`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue: {age: [13, 12]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `11.112.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue: {salary: [11.1, 12.1]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `1311.11414.11515.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArrayWithXmlValue' occurs more than the max allowed times"); + xml|Error toXmlResult = toXml({choice_XsdChoiceArrayWithXmlValue: {age: [13, 14, 15], salary: [11.1, 14.1, 15.1]}}); + test:assertTrue(toXmlResult is error); + test:assertEquals((toXmlResult).message(), "'choice_XsdChoiceArrayWithXmlValue' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XsdChoiceArrayWithXmlValue2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 2 + } + Choice_XsdChoiceArrayWithXmlValue2 choice_XsdChoiceArrayWithXmlValue2; + + @Choice { + minOccurs: 0, + maxOccurs: 2 + } + Choice_XsdChoiceArrayWithXmlValue2_2 choice_XsdChoiceArrayWithXmlValue2_2?; +|}; + +type Choice_XsdChoiceArrayWithXmlValue2 record {| + int[] age?; + float[] salary?; +|}; + +type Choice_XsdChoiceArrayWithXmlValue2_2 record {| + int[] age2?; + float[] salary2?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArrayWithXmlValue2() returns error? { + xml xmlValue; + XsdChoiceArrayWithXmlValue2|Error v; + + xmlValue = xml `1311.11311.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue2: {age: [13], salary: [11.1]}, choice_XsdChoiceArrayWithXmlValue2_2: {age2: [13], salary2: [11.1]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13131313`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue2: {age: [13, 13]}, choice_XsdChoiceArrayWithXmlValue2_2: {age2: [13, 13]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13131311.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue2: {age: [13, 13]}, choice_XsdChoiceArrayWithXmlValue2_2: {age2: [13], salary2: [11.1]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13131311.11415`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArrayWithXmlValue2_2' occurs more than the max allowed times"); + xml|Error toXmlResult = toXml({choice_XsdChoiceArrayWithXmlValue2: {age: [13], salary: []}, choice_XsdChoiceArrayWithXmlValue2_2: {age2: [13, 14, 15], salary2: [11.1]}}); + test:assertTrue(toXmlResult is error); + test:assertEquals((toXmlResult).message(), "'choice_XsdChoiceArrayWithXmlValue2_2' occurs more than the max allowed times"); + + xmlValue = xml `13131313`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'choice_XsdChoiceArrayWithXmlValue2' occurs more than the max allowed times"); + toXmlResult = toXml({choice_XsdChoiceArrayWithXmlValue2: {age: [13, 14, 15], salary: [11.1, 14.1, 15.1]}}); + test:assertTrue(toXmlResult is error); + test:assertEquals((toXmlResult).message(), "'choice_XsdChoiceArrayWithXmlValue2' occurs more than the max allowed times"); + + xmlValue = xml `1313`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue2: {age: [13, 13]}}); + + xmlValue = xml `1313`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "required field 'choice_XsdChoiceArrayWithXmlValue2' not present in XML"); +} + +@Name { + value: "Root" +} +type XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13 record { + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_1 choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_1; + + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2 choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2; +}; + +type Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_1 record { + Choice_Array_A_3[] field1?; + Choice_Array_B_3[] field2?; + Choice_Array_C_3[] field3?; +}; + +type Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2 record { + Choice_Array_D_3[] field4?; + Choice_Array_E_3[] field5?; + Choice__Array_F_3[] field6?; +}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord4() returns error? { + xml xmlValue = xml `123123123123123123`; + XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_1: {field1: [{value1: {a: ["1"], b:["2"], c: ["3"]}}],field2: [{value2: {d: ["1"], e: ["2"], f: ["3"]}}], field3: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}, choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2: {field4: [{value1: {a: ["1"], b: ["2"], c: ["3"]}}], field5: [{value2: {d: ["1"], e: ["2"], f:["3"]}}], field6: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `22123233123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_1: {field1: [{value1: {b:["2", "2"]}}, {value1: {a: ["1"], b:["2"], c: ["3"]}}], field3: [{value3: {h: ["2"], i: ["3", "3"]}}]}, choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2: {field5: [{value2: {d: ["1"], e: ["2"], f:["3"]}}, {value2: {d: ["1"], e: ["2"], f:["3"]}}], field6: [{value3: {g: ["1"], h: ["2"], i: ["3"]}}]}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `22123233123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is error); + test:assertEquals((v2).message(), "'choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord13_2' occurs less than the min required times"); + + xmlValue = xml `221232331123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is error); + test:assertEquals((v2).message(), "'value2' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XsdChoiceArrayWithXmlValue5 record {| + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_XsdChoiceArrayWithXmlValue5 choice_XsdChoiceArrayWithXmlValue5; +|}; + +type Choice_XsdChoiceArrayWithXmlValue5 record {| + int[] age?; + float[] salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceArrayWithXmlValue5() returns error? { + xml xmlValue; + XsdChoiceArrayWithXmlValue5|Error v; + + xmlValue = xml `1311.114`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue5: {age: [13, 14], salary: [11.1]}}); + test:assertEquals(toXml(check v), xml `131411.1`); + + xmlValue = xml `1311.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XsdChoiceArrayWithXmlValue5: {age: [13], salary: [11.1]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `1311.11414.11515.11515.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XsdChoiceArrayWithXmlValue5' occurs more than the max allowed times"); + + xmlValue = xml `13`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XsdChoiceArrayWithXmlValue5' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6 record { + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_1 choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_1; + + @Choice { + minOccurs: 2, + maxOccurs: 4 + } + Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_2 choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_2; +}; + +type Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_1 record { + Choice_Array_A_6[] field1?; + Choice_Array_B_6[] field2?; +}; + +type Choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_2 record { + Choice_Array_D_6[] field4?; + Choice_Array_E_6[] field5?; +}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6() returns error? { + xml xmlValue = xml `1111111111111111`; + XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_1: {field1: [{value1: {a: ["1", "1"]}}, {value1: {a:["1", "1"]}}], field2: [{value2: {d: ["1", "1"]}}, {value2: {d: ["1", "1"]}}]}, choice_XSDChoiceArrayWithXmlValueXsdChoiceArrayWithXmlValueRecord6_2: {field4: [{value1: {a: ["1", "1"]}}, {value1: {a:["1", "1"]}}], field5: [{value2: {d: ["1", "1"]}}, {value2: {d: ["1","1"]}}]}}); + test:assertEquals(toXml(check v2), xml `1111111111111111`); +} diff --git a/ballerina/tests/xsd_choice_test.bal b/ballerina/tests/xsd_choice_test.bal new file mode 100644 index 00000000..24509d64 --- /dev/null +++ b/ballerina/tests/xsd_choice_test.bal @@ -0,0 +1,646 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XSDChoiceRecord record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord choice_XSDChoiceRecord?; +|}; + +type Choice_XSDChoiceRecord record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoice() returns error? { + string xmlStr; + XSDChoiceRecord|Error v; + + xmlStr = string `10`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecord: {age: 10}}); + + xmlStr = string `10.5`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecord: {salary: 10.5}}); + + xmlStr = string `1011.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecord' occurs more than the max allowed times"); + + xmlStr = string `11.111.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecord' occurs more than the max allowed times"); + + xmlStr = string ``; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecord' occurs less than the min required times"); +} + +type XSDChoiceRecordP2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecordP2 choice_XSDChoiceRecordP2; +|}; + +type Choice_XSDChoiceRecordP2 record {| + @Element { + minOccurs: 1, + maxOccurs: 3 + } + int[] age?; + float salary?; + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string[] name?; +|}; + +type XSDChoiceP1Record record {| + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_XSDChoiceP1Record choice_XSDChoiceP1Record?; +|}; + +type Choice_XSDChoiceP1Record record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceP1() returns error? { + string xmlStr; + XSDChoiceP1Record|Error v; + + xmlStr = string `10`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceP1Record: {age: 10}}); + + xmlStr = string `10.5`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceP1Record: {salary: 10.5}}); + + xmlStr = string `1011.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceP1Record' occurs more than the max allowed times"); + + xmlStr = string `11.111.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceP1Record' occurs more than the max allowed times"); + + xmlStr = string ``; + v = parseString(xmlStr); + test:assertEquals(v, {}); +} + +@test:Config {groups: ["xsd", "xsd_Choice"]} +function testXsdChoiceP2() returns error? { + string xmlStr = string `10`; + XSDChoiceRecordP2|Error v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecordP2: {age: [10]}}); + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecordP2: {name: ["ABC"]}}); + + xmlStr = string `11.1`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecordP2: {salary: 11.1}}); + + xmlStr = string `10101011.1ABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecordP2' occurs more than the max allowed times"); + + xmlStr = string `10ABC11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecordP2' occurs more than the max allowed times"); +} + +type XSDChoiceRecord2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord2 choice_XSDChoiceRecord2; + + int num; +|}; + +type Choice_XSDChoiceRecord2 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice2() returns error? { + string xmlStr = string `310`; + XSDChoiceRecord2|Error v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecord2: {age: 10}, num: 3}); + test:assertEquals((check v).choice_XSDChoiceRecord2.age, 10); + test:assertEquals((check v).num, 3); + + xmlStr = string `11.13`; + v = parseString(xmlStr); + test:assertEquals(v, {choice_XSDChoiceRecord2: {salary: 11.1}, num: 3}); + test:assertEquals((check v).choice_XSDChoiceRecord2.salary, 11.1); + test:assertEquals((check v).num, 3); + + xmlStr = string `11.1103`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecord2' occurs more than the max allowed times"); + + xmlStr = string `10311.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceRecord2' occurs more than the max allowed times"); +} + +type XSDChoiceRecord3 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord3 choice_XSDChoiceRecord3; + + record{int n;} num; +|}; + +type Choice_XSDChoiceRecord3 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice3() returns error? { + string xmlStr = string `310`; + XSDChoiceRecord3|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord3: {age: 10}, num: {n: 3}}); + test:assertEquals((check v2).choice_XSDChoiceRecord3.age, 10); + test:assertEquals((check v2).num, {n: 3}); + + xmlStr = string `11.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord3: {salary: 11.1}, num: {n: 3}}); + + xmlStr = string `10311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord3' occurs more than the max allowed times"); + + xmlStr = string `31011.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord3' occurs more than the max allowed times"); +} + +type XSDChoiceRecord4 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord4 choice_XSDChoiceRecord4; +|}; + +type Choice_XSDChoiceRecord4 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice4() returns error? { + string xmlStr = string `310`; + XSDChoiceRecord4|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord4: {age: 10}, num: {n: {n: 3}}}); + test:assertEquals((check v2).choice_XSDChoiceRecord4.age, 10); + test:assertEquals((check v2).num, {n: {n: 3}}); + + xmlStr = string `11.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord4: {salary: 11.1}, num: {n: {n: 3}}}); + + xmlStr = string `10311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord4' occurs more than the max allowed times"); + + xmlStr = string `31011.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord4' occurs more than the max allowed times"); +} + +type XSDChoiceRecord5 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord5 choice_XSDChoiceRecord5; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceRecord5 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice5() returns error? { + string xmlStr = string `3310`; + XSDChoiceRecord5|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord5: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + + xmlStr = string `311.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord5: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + + xmlStr = string `11.133`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord5: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + + xmlStr = string `103311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord5' occurs more than the max allowed times"); + + xmlStr = string `33`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceRecord5' not present in XML"); +} + +type XSDChoiceRecord6 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord6_1 choice_XSDChoiceRecord6_1; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord6_2 choice_XSDChoiceRecord6_2; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceRecord6_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceRecord6_2 record {| + string name?; + string status?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice6() returns error? { + string xmlStr = string `3success310`; + XSDChoiceRecord6|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord6_1: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceRecord6_2: {status: "success"}}); + test:assertEquals((check v2).choice_XSDChoiceRecord6_1.age, 10); + test:assertEquals((check v2).choice_XSDChoiceRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `SD33`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceRecord6_1' not present in XML"); + + xmlStr = string `33SD`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceRecord6_1' not present in XML"); + + xmlStr = string `SDsuccess33`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord6_2' occurs more than the max allowed times"); +} + +type XSDChoiceRecord7 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord7_1 choice_XSDChoiceRecord7_1; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord7_2 choice_XSDChoiceRecord7_2; +|}; + +type Choice_XSDChoiceRecord7_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceRecord7_2 record {| + string name?; + string status?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice7() returns error? { + string xmlStr = string `success11.1`; + XSDChoiceRecord7|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord7_1: {salary: 11.1}, choice_XSDChoiceRecord7_2: {status: "success"}}); +} + +type XSDChoiceRecord8 record {| + XSDChoiceRecord8P test; + int a; +|}; + +type XSDChoiceRecord8P record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord8_1 choice_XSDChoiceRecord8_1; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord8_2 choice_XSDChoiceRecord8_2; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceRecord8_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceRecord8_2 record {| + RecChoice8 name?; + RecChoice8 status?; +|}; + +type RecChoice8 record {| + string value1; + string value2; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice8() returns error? { + string xmlStr; + XSDChoiceRecord8|Error v2; + + xmlStr = string `3SDAB3102`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceRecord8_1: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceRecord8_2: {name: {value1: "SD", value2: "AB"}}}}); + test:assertEquals((check v2).test.choice_XSDChoiceRecord8_1.age, 10); + test:assertEquals((check v2).test.choice_XSDChoiceRecord8_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SuccessFail3311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceRecord8_1: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceRecord8_2: {status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.choice_XSDChoiceRecord8_1.salary, 11.1); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `33SuccessFail11.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceRecord8_1: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceRecord8_2: {status: {value1: "Success", value2: "Fail"}}}}); + + xmlStr = string `SDAB10SuccessFail11.12`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord8_2' occurs more than the max allowed times"); + + xmlStr = string `10SuccessFail11.12`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord8_1' occurs more than the max allowed times"); + + xmlStr = string `102`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord8_2' occurs less than the min required times"); + + xmlStr = string `SuccessFail2`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord8_1' occurs less than the min required times"); +} + +type XSDChoiceRecord9 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord9_1 choice_XSDChoiceRecord9_1; +}; + +type Choice_XSDChoiceRecord9_1 record { + Choice_A field1?; + Choice_B field2?; + Choice_C field3?; +}; + +type Choice_A record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice value1; +}; + +type Choice_B record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice value2; +}; + +type Choice_C record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice value3; +}; + +type Choice record { + string a?; + string b?; + string c?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice9() returns error? { + string xmlStr; + XSDChoiceRecord9|Error v2; + + xmlStr = string `1`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord9_1: {field2: {value2: {a: "1"}}}}); + + xmlStr = string `1`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord9_1: {field3: {value3: {c: "1"}}}}); + + xmlStr = string `11`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord9_1' occurs more than the max allowed times"); + + xmlStr = string `11`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value1' occurs more than the max allowed times"); +} + +type XSDChoiceRecord10 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_XSDChoiceRecord10_1 choice_XSDChoiceRecord10_1?; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceRecord10_2 choice_XSDChoiceRecord10_2; +}; + +type Choice_XSDChoiceRecord10_1 record { + Choice_A_10 field1?; + Choice_B_10 field2?; + Choice_C_10 field3?; +}; + +type Choice_XSDChoiceRecord10_2 record { + Choice_D_10 field4?; + Choice_E_10 field5?; + Choice_F_10 field6?; +}; + +type Choice_A_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_10 value1; +}; + +type Choice_B_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice2_10 value2; +}; + +type Choice_C_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice3_10 value3; +}; + +type Choice_D_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_10 value1; +}; + +type Choice_E_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice2_10 value2; +}; + +type Choice_F_10 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice3_10 value3; +}; + +type Choice_10 record { + string a?; + string b?; + string c?; +}; + +type Choice2_10 record { + string d?; + string e?; + string f?; +}; + +type Choice3_10 record { + string g?; + string h?; + string i?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoice10() returns error? { + string xmlStr = string `12`; + XSDChoiceRecord10|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {choice_XSDChoiceRecord10_1: {field1: {value1: {a: "1"}}}, choice_XSDChoiceRecord10_2: {field5: {value2: {"d": "2"}}}}); + + xmlStr = string `112`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord10_1' occurs more than the max allowed times"); + + xmlStr = string `122`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceRecord10_2' occurs more than the max allowed times"); + + xmlStr = string `112`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value2' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_choice_test_with_element_annotation.bal b/ballerina/tests/xsd_choice_test_with_element_annotation.bal new file mode 100644 index 00000000..f70664aa --- /dev/null +++ b/ballerina/tests/xsd_choice_test_with_element_annotation.bal @@ -0,0 +1,359 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdChoiceWithElementAnnotation record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_EA1 seq_EA1?; +}; + +type Choice_EA1 record { + + @Element { + maxOccurs: 2, + minOccurs: 0 + } + string[] EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotation() returns error? { + string xmlStr; + XsdChoiceWithElementAnnotation|Error v; + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlStr = string ``; + v = parseString(xmlStr); + test:assertEquals(v, {}); + + xmlStr = string `ABABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA3: ["AB", "AB", "AB", "AB"]}}); + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC"}}); + + xmlStr = string `ABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{EA3: ["AB", "AB"]}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC", "ABC"]}}); + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA1' occurs more than the max allowed times"); + + xmlStr = string `ABCABCABCABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_EA1' occurs more than the max allowed times"); +} + +type XsdChoiceWithElementAnnotation2 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_EA2 seq_EA2; +}; + +type Choice_EA2 record { + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotation2() returns error? { + string xmlStr; + XsdChoiceWithElementAnnotation2|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCABCD`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA2: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABCCD`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +type XsdChoiceWithElementAnnotation3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XsdChoiceWithElementAnnotation3_1 seq_XsdChoiceWithElementAnnotation3_1; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XsdChoiceWithElementAnnotation3_2 seq_XsdChoiceWithElementAnnotation3_2?; +}; + +type Choice_XsdChoiceWithElementAnnotation3_1 record { + @Element { + minOccurs: 1, + maxOccurs: 3 + } + Choice_A_3[] field1?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_B_3[] field2?; + + @Element { + minOccurs: 1, + maxOccurs: 3 + } + Choice_C_3 field3?; +}; + +type Choice_XsdChoiceWithElementAnnotation3_2 record { + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_D_3[] field4?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_E_3[] field5?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_F_3[] field6?; +}; + +type Choice_A_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_3 value1; +}; + +type Choice_B_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice2_3 value2; +}; + +type Choice_C_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice3_3 value3; +}; + +type Choice_D_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_3 value1; +}; + +type Choice_E_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice2_3 value2; +}; + +type Choice_F_3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice3_3 value3; +}; + +type Choice_3 record { + @Element { + minOccurs: 0, + maxOccurs: 3 + } + string[] a?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + string[] b?; + string c?; +}; + +type Choice2_3 record { + string d?; + string e?; + string f?; +}; + +type Choice3_3 record { + string g?; + string h?; + string i?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotation3() returns error? { + string xmlStr; + XsdChoiceWithElementAnnotation3|Error v2; + + xmlStr = string `ABCABCABCABCABCABCABCABCABC`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XsdChoiceWithElementAnnotation3_1: {field1: [{value1: {a: ["ABC"]}}, {value1: {a: ["ABC", "ABC"]}}, {value1: {a: ["ABC", "ABC", "ABC"]}}]}, seq_XsdChoiceWithElementAnnotation3_2: {field5: [{value2: {d: "ABC"}}, {value2: {d: "ABC"}}, {value2: {d: "ABC"}}]}}); +} diff --git a/ballerina/tests/xsd_choice_test_with_element_annotation_with_parse_type.bal b/ballerina/tests/xsd_choice_test_with_element_annotation_with_parse_type.bal new file mode 100644 index 00000000..c37f4d21 --- /dev/null +++ b/ballerina/tests/xsd_choice_test_with_element_annotation_with_parse_type.bal @@ -0,0 +1,251 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XsdChoiceWithElementAnnotationWithXmlValue record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_EA1 seq_EA1?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotationWithXmlValue() returns error? { + xml xmlValue; + XsdChoiceWithElementAnnotationWithXmlValue|Error v; + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml ``; + v = parseAsType(xmlValue); + test:assertEquals(v, {}); + + xmlValue = xml `ABABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA3: ["AB", "AB", "AB", "AB"]}}); + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC"}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1":{EA3: ["AB", "AB"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC", "ABC"]}}); + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA1' occurs more than the max allowed times"); + + xmlValue = xml `ABCABCABCABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_EA1' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XsdChoiceWithElementAnnotationWithXmlValue2 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_EA2 seq_EA2; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotationWithXmlValue2() returns error? { + xml xmlValue; + XsdChoiceWithElementAnnotationWithXmlValue2|Error v; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABCD`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA2: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + test:assertEquals(toXml(check v), xmlValue); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABCCD`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XsdChoiceWithElementAnnotationWithXmlValue3 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XsdChoiceWithElementAnnotationWithXmlValue3_1 seq_XsdChoiceWithElementAnnotationWithXmlValue3_1; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XsdChoiceWithElementAnnotationWithXmlValue3_2 seq_XsdChoiceWithElementAnnotationWithXmlValue3_2?; +}; + +type Choice_XsdChoiceWithElementAnnotationWithXmlValue3_1 record { + @Element { + minOccurs: 1, + maxOccurs: 3 + } + Choice_A_3[] field1?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_B_3[] field2?; + + @Element { + minOccurs: 1, + maxOccurs: 3 + } + Choice_C_3 field3?; +}; + +type Choice_XsdChoiceWithElementAnnotationWithXmlValue3_2 record { + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_D_3[] field4?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_E_3[] field5?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Choice_F_3[] field6?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithElementAnnotationWithXmlValue3() returns error? { + xml xmlValue; + XsdChoiceWithElementAnnotationWithXmlValue3|Error v2; + + xmlValue = xml `ABCABCABCABCABCABCABCABCABC`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XsdChoiceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {a: ["ABC"]}}, {value1: {a: ["ABC", "ABC"]}}, {value1: {a: ["ABC", "ABC", "ABC"]}}]}, seq_XsdChoiceWithElementAnnotationWithXmlValue3_2: {field5: [{value2: {d: "ABC"}}, {value2: {d: "ABC"}}, {value2: {d: "ABC"}}]}}); + test:assertEquals(toXml(check v2), xmlValue); +} diff --git a/ballerina/tests/xsd_choice_test_with_parse_type.bal b/ballerina/tests/xsd_choice_test_with_parse_type.bal new file mode 100644 index 00000000..25677648 --- /dev/null +++ b/ballerina/tests/xsd_choice_test_with_parse_type.bal @@ -0,0 +1,636 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord choice_XSDChoiceWithXmlValueRecord?; +|}; + +type Choice_XSDChoiceWithXmlValueRecord record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceWithXmlValue() returns error? { + xml xmlValue; + XSDChoiceWithXmlValueRecord|Error v; + + xmlValue = xml `10`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecord: {age: 10}}); + test:assertEquals(toXml(check v), xmlValue); + Error? e = validate(xmlValue, XSDChoiceWithXmlValueRecord); + test:assertEquals(e, ()); + + xmlValue = xml `10.5`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecord: {salary: 10.5}}); + test:assertEquals(toXml(check v), xmlValue); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord); + test:assertEquals(e, ()); + + xmlValue = xml `1011.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecord' occurs more than the max allowed times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord); + test:assertEquals((e).message(), "Invalid XML found: ''choice_XSDChoiceWithXmlValueRecord' occurs more than the max allowed times'"); + xml|Error toXmlResult = toXml({choice_XSDChoiceWithXmlValueRecord: {age: 10, salary: 11.1}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'choice_XSDChoiceWithXmlValueRecord' occurs more than the max allowed times"); + + xmlValue = xml `11.111.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecord' occurs more than the max allowed times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord); + test:assertEquals((e).message(), "Invalid XML found: ''choice_XSDChoiceWithXmlValueRecord' occurs more than the max allowed times'"); + + xmlValue = xml ``; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecord' occurs less than the min required times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord); + test:assertEquals((e).message(), "Invalid XML found: ''choice_XSDChoiceWithXmlValueRecord' occurs less than the min required times'"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecordP2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecordP2 choice_XSDChoiceWithXmlValueRecordP2; +|}; + +type Choice_XSDChoiceWithXmlValueRecordP2 record {| + @Element { + minOccurs: 1, + maxOccurs: 3 + } + int[] age?; + float salary?; + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string[] name?; +|}; + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueP1Record record {| + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueP1Record choice_XSDChoiceWithXmlValueP1Record?; +|}; + +type Choice_XSDChoiceWithXmlValueP1Record record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceWithXmlValueP1() returns error? { + xml xmlValue; + XSDChoiceWithXmlValueP1Record|Error v; + + xmlValue = xml `10`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueP1Record: {age: 10}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `10.5`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueP1Record: {salary: 10.5}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `1011.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueP1Record' occurs more than the max allowed times"); + + xmlValue = xml `11.111.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueP1Record' occurs more than the max allowed times"); + + xmlValue = xml ``; + v = parseAsType(xmlValue); + test:assertEquals(v, {}); +} + +@test:Config {groups: ["xsd", "xsd_Choice"]} +function testXsdChoiceWithXmlValueP2() returns error? { + xml xmlValue = xml `10`; + XSDChoiceWithXmlValueRecordP2|Error v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecordP2: {age: [10]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecordP2: {name: ["ABC"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `11.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecordP2: {salary: 11.1}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `10101011.1ABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecordP2' occurs more than the max allowed times"); + + xmlValue = xml `10ABC11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecordP2' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord2 choice_XSDChoiceWithXmlValueRecord2; + + int num; +|}; + +type Choice_XSDChoiceWithXmlValueRecord2 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue2() returns error? { + xml xmlValue = xml `310`; + XSDChoiceWithXmlValueRecord2|Error v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecord2: {age: 10}, num: 3}); + test:assertEquals((check v).choice_XSDChoiceWithXmlValueRecord2.age, 10); + test:assertEquals((check v).num, 3); + test:assertEquals(toXml(check v), xml `103`); + + xmlValue = xml `11.13`; + v = parseAsType(xmlValue); + test:assertEquals(v, {choice_XSDChoiceWithXmlValueRecord2: {salary: 11.1}, num: 3}); + test:assertEquals((check v).choice_XSDChoiceWithXmlValueRecord2.salary, 11.1); + test:assertEquals((check v).num, 3); + test:assertEquals(toXml(check v), xml `11.13`); + + xmlValue = xml `11.1103`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecord2' occurs more than the max allowed times"); + xml|Error toXmlResult = toXml({choice_XSDChoiceWithXmlValueRecord2: {age: 10, salary: 11.1}, num: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'choice_XSDChoiceWithXmlValueRecord2' occurs more than the max allowed times"); + + xmlValue = xml `10311.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'choice_XSDChoiceWithXmlValueRecord2' occurs more than the max allowed times"); + toXmlResult = toXml({num: 3, choice_XSDChoiceWithXmlValueRecord2: {age: 10, salary: 11.1}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'choice_XSDChoiceWithXmlValueRecord2' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord3 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord3 choice_XSDChoiceWithXmlValueRecord3; + + record{int n;} num; +|}; + +type Choice_XSDChoiceWithXmlValueRecord3 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue3() returns error? { + xml xmlValue = xml `310`; + XSDChoiceWithXmlValueRecord3|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord3: {age: 10}, num: {n: 3}}); + test:assertEquals((check v2).choice_XSDChoiceWithXmlValueRecord3.age, 10); + test:assertEquals((check v2).num, {n: 3}); + test:assertEquals(toXml(check v2), xml `103`); + + xmlValue = xml `11.13`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord3: {salary: 11.1}, num: {n: 3}}); + test:assertEquals(toXml(check v2), xml `11.13`); + + xmlValue = xml `10311.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord3' occurs more than the max allowed times"); + + xmlValue = xml `31011.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord3' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord4 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord4 choice_XSDChoiceWithXmlValueRecord4; +|}; + +type Choice_XSDChoiceWithXmlValueRecord4 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue4() returns error? { + xml xmlValue = xml `310`; + XSDChoiceWithXmlValueRecord4|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord4: {age: 10}, num: {n: {n: 3}}}); + test:assertEquals((check v2).choice_XSDChoiceWithXmlValueRecord4.age, 10); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `11.13`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord4: {salary: 11.1}, num: {n: {n: 3}}}); + test:assertEquals(toXml(check v2), xml `311.1`); + + xmlValue = xml `10311.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord4' occurs more than the max allowed times"); + + xmlValue = xml `31011.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord4' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord5 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord5 choice_XSDChoiceWithXmlValueRecord5; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceWithXmlValueRecord5 record {| + int age?; + float salary?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue5() returns error? { + xml xmlValue = xml `3310`; + XSDChoiceWithXmlValueRecord5|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord5: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals(toXml(check v2), xml `3103`); + + xmlValue = xml `311.13`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord5: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals(toXml(check v2), xml `311.13`); + + xmlValue = xml `11.133`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord5: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals(toXml(check v2), xml `311.13`); + + xmlValue = xml `103311.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord5' occurs more than the max allowed times"); + + xmlValue = xml `33`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceWithXmlValueRecord5' not present in XML"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord6 record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord6_1 choice_XSDChoiceWithXmlValueRecord6_1; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord6_2 choice_XSDChoiceWithXmlValueRecord6_2; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceWithXmlValueRecord6_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceWithXmlValueRecord6_2 record {| + string name?; + string status?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue6() returns error? { + xml xmlValue = xml `3success310`; + XSDChoiceWithXmlValueRecord6|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord6_1: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceWithXmlValueRecord6_2: {status: "success"}}); + test:assertEquals((check v2).choice_XSDChoiceWithXmlValueRecord6_1.age, 10); + test:assertEquals((check v2).choice_XSDChoiceWithXmlValueRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `310success3`); + + xmlValue = xml `SD33`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceWithXmlValueRecord6_1' not present in XML"); + + xmlValue = xml `33SD`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'choice_XSDChoiceWithXmlValueRecord6_1' not present in XML"); + + xmlValue = xml `SDsuccess33`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord6_2' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord7 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord7_1 choice_XSDChoiceWithXmlValueRecord7_1; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord7_2 choice_XSDChoiceWithXmlValueRecord7_2; +|}; + +type Choice_XSDChoiceWithXmlValueRecord7_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceWithXmlValueRecord7_2 record {| + string name?; + string status?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue7() returns error? { + xml xmlValue = xml `success11.1`; + XSDChoiceWithXmlValueRecord7|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord7_1: {salary: 11.1}, choice_XSDChoiceWithXmlValueRecord7_2: {status: "success"}}); + test:assertEquals(toXml(check v2), xml `11.1success`); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord8 record {| + XSDChoiceWithXmlValueRecord8P test; + int a; +|}; + +type XSDChoiceWithXmlValueRecord8P record {| + record{record {int n;} n;} num; + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord8_1 choice_XSDChoiceWithXmlValueRecord8_1; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord8_2 choice_XSDChoiceWithXmlValueRecord8_2; + record{record {int n;} n;} num2; +|}; + +type Choice_XSDChoiceWithXmlValueRecord8_1 record {| + int age?; + float salary?; +|}; + +type Choice_XSDChoiceWithXmlValueRecord8_2 record {| + RecChoice8 name?; + RecChoice8 status?; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue8() returns error? { + xml xmlValue; + XSDChoiceWithXmlValueRecord8|Error v2; + + xmlValue = xml `3SDAB3102`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceWithXmlValueRecord8_1: {age: 10}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceWithXmlValueRecord8_2: {name: {value1: "SD", value2: "AB"}}}}); + test:assertEquals((check v2).test.choice_XSDChoiceWithXmlValueRecord8_1.age, 10); + test:assertEquals((check v2).test.choice_XSDChoiceWithXmlValueRecord8_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `310SDAB32`); + + xmlValue = xml `SuccessFail3311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceWithXmlValueRecord8_1: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceWithXmlValueRecord8_2: {status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.choice_XSDChoiceWithXmlValueRecord8_1.salary, 11.1); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlValue = xml `33SuccessFail11.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {choice_XSDChoiceWithXmlValueRecord8_1: {salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, choice_XSDChoiceWithXmlValueRecord8_2: {status: {value1: "Success", value2: "Fail"}}}}); + + xmlValue = xml `SDAB10SuccessFail11.12`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord8_2' occurs more than the max allowed times"); + + xmlValue = xml `10SuccessFail11.12`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord8_1' occurs more than the max allowed times"); + + xmlValue = xml `102`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord8_2' occurs less than the min required times"); + + xmlValue = xml `SuccessFail2`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord8_1' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord9 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord9_1 choice_XSDChoiceWithXmlValueRecord9_1; +}; + +type Choice_XSDChoiceWithXmlValueRecord9_1 record { + Choice_A field1?; + Choice_B field2?; + Choice_C field3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue9() returns error? { + xml xmlValue; + XSDChoiceWithXmlValueRecord9|Error v2; + + xmlValue = xml `1`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord9_1: {field2: {value2: {a: "1"}}}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `1`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord9_1: {field3: {value3: {c: "1"}}}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `11`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord9_1' occurs more than the max allowed times"); + + xmlValue = xml `11`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value1' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDChoiceWithXmlValueRecord10 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord10_1 choice_XSDChoiceWithXmlValueRecord10_1?; + + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_XSDChoiceWithXmlValueRecord10_2 choice_XSDChoiceWithXmlValueRecord10_2; +}; + +type Choice_XSDChoiceWithXmlValueRecord10_1 record { + Choice_A_10 field1?; + Choice_B_10 field2?; + Choice_C_10 field3?; +}; + +type Choice_XSDChoiceWithXmlValueRecord10_2 record { + Choice_D_10 field4?; + Choice_E_10 field5?; + Choice_F_10 field6?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdChoiceWithXmlValue10() returns error? { + xml xmlValue = xml `12`; + XSDChoiceWithXmlValueRecord10|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {choice_XSDChoiceWithXmlValueRecord10_1: {field1: {value1: {a: "1"}}}, choice_XSDChoiceWithXmlValueRecord10_2: {field5: {value2: {"d": "2"}}}}); + test:assertEquals(toXml(check v2), xmlValue); + Error? e = validate(xmlValue, XSDChoiceWithXmlValueRecord10); + test:assertEquals(e, ()); + + xmlValue = xml `112`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord10_1' occurs more than the max allowed times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord10); + test:assertEquals((e).message(), "Invalid XML found: ''choice_XSDChoiceWithXmlValueRecord10_1' occurs more than the max allowed times'"); + xml|Error toXmlResult = toXml({choice_XSDChoiceWithXmlValueRecord10_1: {field1: {value1: {a: "1"}}, field2: {value2: {d: "1"}}}, choice_XSDChoiceWithXmlValueRecord10_2: {field5: {value2: {d: "2"}}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'choice_XSDChoiceWithXmlValueRecord10_1' occurs more than the max allowed times"); + + xmlValue = xml `122`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'choice_XSDChoiceWithXmlValueRecord10_2' occurs more than the max allowed times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord10); + test:assertEquals((e).message(), "Invalid XML found: ''choice_XSDChoiceWithXmlValueRecord10_2' occurs more than the max allowed times'"); + + xmlValue = xml `112`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value2' occurs more than the max allowed times"); + e = validate(xmlValue, XSDChoiceWithXmlValueRecord10); + test:assertEquals((e).message(), "Invalid XML found: ''value2' occurs more than the max allowed times'"); + toXmlResult = toXml({choice_XSDChoiceWithXmlValueRecord10_1: {field2: {value2: {d: "1", e: "1"}}}, choice_XSDChoiceWithXmlValueRecord10_2: {field5: {value2: {d: "2"}}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'value2' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_choice_with_name_annotations.bal b/ballerina/tests/xsd_choice_with_name_annotations.bal new file mode 100644 index 00000000..5b796a14 --- /dev/null +++ b/ballerina/tests/xsd_choice_with_name_annotations.bal @@ -0,0 +1,232 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdChoiceWithNameAnnotation record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_Name_EA1 seq_EA1?; +}; + +type Choice_Name_EA1 record { + + @Element { + maxOccurs: 2, + minOccurs: 0 + } + @Name { + value: "A1" + } + string[] EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @Name { + value: "A2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @Name { + value: "A3" + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithNameAnnotation() returns error? { + string xmlStr; + XsdChoiceWithNameAnnotation|Error v; + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlStr = string ``; + v = parseString(xmlStr); + test:assertEquals(v, {}); + + xmlStr = string `ABABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA3: ["AB", "AB", "AB", "AB"]}}); + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC"}}); + + xmlStr = string `ABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{EA3: ["AB", "AB"]}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC", "ABC"]}}); + + xmlStr = string `ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A1' occurs more than the max allowed times"); + + xmlStr = string `ABCABCABCABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_EA1' occurs more than the max allowed times"); +} + +type XsdChoiceWithNameAnnotation2 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_Name_EA2 seq_EA2; +}; + +type Choice_Name_EA2 record { + @Name { + value: "A" + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @Name { + value: "A1" + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @Name { + value: "A2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @Name { + value: "A3" + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithNameAnnotation2() returns error? { + string xmlStr; + XsdChoiceWithNameAnnotation2|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABCABCD`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA2: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABCCD`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_choice_with_name_annotations_with_parse_type.bal b/ballerina/tests/xsd_choice_with_name_annotations_with_parse_type.bal new file mode 100644 index 00000000..3cdfb6e9 --- /dev/null +++ b/ballerina/tests/xsd_choice_with_name_annotations_with_parse_type.bal @@ -0,0 +1,168 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdChoiceWithNameAnnotationWithXmlValue record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_Name_EA1 seq_EA1?; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithNameAnnotationWithXmlValue() returns error? { + xml xmlValue; + XsdChoiceWithNameAnnotationWithXmlValue|Error v; + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlValue = xml ``; + v = parseAsType(xmlValue); + test:assertEquals(v, {}); + + xmlValue = xml `ABABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA3: ["AB", "AB", "AB", "AB"]}}); + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC"}}); + + xmlValue = xml `ABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1":{EA3: ["AB", "AB"]}}); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC", "ABC"]}}); + + xmlValue = xml `ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1: {EA1: ["ABC"]}}); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A1' occurs more than the max allowed times"); + + xmlValue = xml `ABCABCABCABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_EA1' occurs more than the max allowed times"); +} + +type XsdChoiceWithNameAnnotationWithXmlValue2 record { + @Choice { + minOccurs: 0, + maxOccurs: 1 + } + Choice_Name_EA2 seq_EA2; +}; + +@test:Config {groups: ["xsd", "xsd_choice", "xsd_element", "xsd_element_and_sequence"]} +function testXsdChoiceWithNameAnnotationWithXmlValue2() returns error? { + xml xmlValue; + XsdChoiceWithNameAnnotationWithXmlValue2|Error v; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABCD`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA2: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABCCD`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_element_test.bal b/ballerina/tests/xsd_element_test.bal new file mode 100644 index 00000000..08f06976 --- /dev/null +++ b/ballerina/tests/xsd_element_test.bal @@ -0,0 +1,222 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type ElementRecord record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + string name?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + int age?; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElement() returns error? { + string xmlStr = "John25"; + ElementRecord|Error rec = parseString(xmlStr); + test:assertEquals(rec, {name: "John", age: 25}); + + xmlStr = "John"; + rec = parseString(xmlStr); + test:assertEquals(rec, {name: "John"}); +} + +type ElementRecord2 record { + @Element { + maxOccurs: 10, + minOccurs: 0 + } + string[] name?; + + @Element { + maxOccurs: 3, + minOccurs: 1 + } + int[] age?; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElement2() returns error? { + string xmlStr = "John25"; + ElementRecord2|Error rec = parseString(xmlStr); + test:assertEquals(rec, {name: ["John"], age: [25]}); + + xmlStr = "John"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs less than the min required times"); + + xmlStr = "25"; + rec = parseString(xmlStr); + test:assertEquals(rec, {age: [25]}); + + xmlStr = "1112131415"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "11Abc12131415"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "AbcAbcAbcAbcAbc1112131415"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "11AbcAbc12Abc13Abc1415"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); +} + +type ElementRecord3 record { + record { + @Element { + maxOccurs: 10, + minOccurs: 0 + } + string[] name?; + + @Element { + maxOccurs: 3, + minOccurs: 1 + } + int[] age?; + + @Element { + maxOccurs: 3, + minOccurs: 2 + } + int[] id?; + } user; + + @Element { + maxOccurs: 1, + minOccurs: 1 + } + int status; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElement3() returns error? { + string xmlStr; + ElementRecord3|Error rec; + + xmlStr = "John12353"; + rec = parseString(xmlStr); + test:assertEquals(rec, {user: {name: ["John"], age: [35], id: [1, 2]}, status: 3}); + + xmlStr = "John123"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs less than the min required times"); + + xmlStr = "12353"; + rec = parseString(xmlStr); + test:assertEquals(rec, {user: {age: [35], id: [1, 2]}, status: 3}); + + xmlStr = "1211131314153"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "1211Abc131314153"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "12AbcAbcAbcAbcAbc11131314153"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + + xmlStr = "1211AbcAbc13Abc13Abc14153"; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); +} + +type ElementRecord4 record { + @Element { + maxOccurs: 3, + minOccurs: 2 + } + record { + @Element { + maxOccurs: 4, + minOccurs: 3 + } + string[] firstName; + + @Element { + maxOccurs: 3, + minOccurs: 3 + } + string[] lastName; + }[] name; + + @Element { + maxOccurs: 4, + minOccurs: 3 + } + int[] status; + + @Element { + maxOccurs: 3, + minOccurs: 3 + } + int[] age; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElement4() returns error? { + string xmlStr = string `JohnJaneJimDoeSmithBrownJohnJaneJimDoeSmithBrown123202530`; + ElementRecord4|Error rec = parseString(xmlStr); + test:assertEquals(rec, {name: [{firstName: ["John", "Jane", "Jim"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3], age: [20, 25, 30]}); + + xmlStr = string `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseString(xmlStr); + test:assertEquals(rec, {name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + + xmlStr = string `JohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'name' occurs less than the min required times"); + + xmlStr = string `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'name' occurs more than the max allowed times"); + + xmlStr = string `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownBrownBrownBrownBrownBrownBrown1234202530`; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'lastName' occurs more than the max allowed times"); + + xmlStr = string `JohnJaneJimDoeSmithBrownJohnJaneJimJimJimJimJimJimJimJimDoeSmithBrown123202530`; + rec = parseString(xmlStr); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'firstName' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_element_test_with_parse_type.bal b/ballerina/tests/xsd_element_test_with_parse_type.bal new file mode 100644 index 00000000..af92fec2 --- /dev/null +++ b/ballerina/tests/xsd_element_test_with_parse_type.bal @@ -0,0 +1,288 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type ElementRecordWithXmlValue record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + string name?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + int age?; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElementWithXmlValue() returns error? { + xml xmlValue = xml `John25`; + ElementRecordWithXmlValue|Error rec = parseAsType(xmlValue); + test:assertEquals(rec, {name: "John", age: 25}); + test:assertEquals(toXml(check rec), xmlValue); + + xmlValue = xml `John`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {name: "John"}); + test:assertEquals(toXml(check rec), xmlValue); +} + +@Name { + value: "Root" +} +type ElementRecordWithXmlValue2 record { + @Element { + maxOccurs: 10, + minOccurs: 0 + } + string[] name?; + + @Element { + maxOccurs: 3, + minOccurs: 1 + } + int[] age; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElementWithXmlValue2() returns error? { + xml xmlValue; + ElementRecordWithXmlValue2|Error rec; + xml|Error toXmlResult; + + xmlValue = xml `John25`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {name: ["John"], age: [25]}); + test:assertEquals(toXml(check rec), xmlValue); + + xmlValue = xml `John`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs less than the min required times"); + toXmlResult = toXml({name: ["John"], age: []}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs less than the min required times"); + + xmlValue = xml `25`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {age: [25]}); + test:assertEquals(toXml(check rec), xmlValue); + + xmlValue = xml `1112131415`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is Error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({age: [11, 12, 13, 14, 15]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `11Abc12131415`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({name: ["Abc"], age: [11, 12, 13, 14, 15]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `AbcAbcAbcAbcAbc1112131415`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({name: ["Abc", "Abc", "Abc", "Abc", "Abc"], age: [11, 12, 13, 14, 15]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `11AbcAbc12Abc13Abc1415`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({name: ["Abc", "Abc", "Abc", "Abc"], age: [11, 12, 13, 14, 15]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type ElementRecordWithXmlValue3 record { + record { + @Element { + maxOccurs: 10, + minOccurs: 0 + } + string[] name?; + + @Element { + maxOccurs: 3, + minOccurs: 1 + } + int[] age; + + @Element { + maxOccurs: 3, + minOccurs: 2 + } + int[] id; + } user; + + @Element { + maxOccurs: 1, + minOccurs: 1 + } + int status; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElementWithXmlValue3() returns error? { + xml xmlValue; + ElementRecordWithXmlValue3|Error rec; + xml|Error toXmlResult; + + xmlValue = xml `John12353`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {user: {name: ["John"], id: [1, 2], age: [35]}, status: 3}); + test:assertEquals(toXml(check rec), xml `John35123`); + + xmlValue = xml `John123`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs less than the min required times"); + toXmlResult = toXml({user: {name: ["John"], id: [1, 2], age: []}, status: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs less than the min required times"); + + xmlValue = xml `12353`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {user: {id: [1, 2], age: [35]}, status: 3}); + test:assertEquals(toXml(check rec), xml `35123`); + + xmlValue = xml `1211131314153`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({user: {id: [1, 2], age: [11, 13, 13, 14, 15]}, status: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `1211Abc131314153`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({user: {id: [1,2], age: [11, 13, 13, 14, 15], name: ["Abc"]}, status: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `12AbcAbcAbcAbcAbc11131314153`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({user: {id: [1,2], age: [11, 13, 13, 14, 15], name: ["Abc", "Abc", "Abc", "Abc"]}, status: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `1211AbcAbc13Abc13Abc14153`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'age' occurs more than the max allowed times"); + toXmlResult = toXml({user: {id: [1,2], age: [11, 13, 13, 14, 15], name: ["Abc", "Abc", "Abc", "Abc"]}, status: 3}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'age' occurs more than the max allowed times"); +} + +type ElementRecordWithXmlValue4 record { + @Element { + maxOccurs: 3, + minOccurs: 2 + } + record { + @Element { + maxOccurs: 4, + minOccurs: 3 + } + string[] firstName; + + @Element { + maxOccurs: 3, + minOccurs: 3 + } + string[] lastName; + }[] name; + + @Element { + maxOccurs: 4, + minOccurs: 3 + } + int[] status; + + @Element { + maxOccurs: 3, + minOccurs: 3 + } + int[] age; +}; + +@test:Config{groups: ["xsd", "xsd_element"]} +function testXsdElementWithXmlValue4() returns error? { + xml|Error toXmlResult; + + xml xmlValue = xml `JohnJaneJimDoeSmithBrownJohnJaneJimDoeSmithBrown123202530`; + ElementRecordWithXmlValue4|Error rec = parseAsType(xmlValue); + test:assertEquals(rec, {name: [{firstName: ["John", "Jane", "Jim"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3], age: [20, 25, 30]}); + test:assertEquals(toXml(check rec), xmlValue); + + xmlValue = xml `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseAsType(xmlValue); + test:assertEquals(rec, {name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + test:assertEquals(toXml(check rec), xmlValue); + + xmlValue = xml `JohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'name' occurs less than the min required times"); + toXmlResult = toXml({name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'name' occurs less than the min required times"); + + xmlValue = xml `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrown1234202530`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'name' occurs more than the max allowed times"); + toXmlResult = toXml({name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'name' occurs more than the max allowed times"); + + xmlValue = xml `JohnJaneJimAnnaDoeSmithBrownJohnJaneJimAnnaDoeSmithBrownBrownBrownBrownBrownBrownBrown1234202530`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'lastName' occurs more than the max allowed times"); + toXmlResult = toXml({name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown", "Brown", "Brown", "Brown", "Brown", "Brown", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'lastName' occurs more than the max allowed times"); + + xmlValue = xml `JohnJaneJimDoeSmithBrownJohnJaneJimJimJimJimJimJimJimJimDoeSmithBrown123202530`; + rec = parseAsType(xmlValue); + test:assertTrue(rec is error); + test:assertEquals((rec).message(), "'firstName' occurs more than the max allowed times"); + toXmlResult = toXml({name: [{firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna"], lastName: ["Doe", "Smith", "Brown"]}, {firstName: ["John", "Jane", "Jim", "Anna", "John"], lastName: ["Doe", "Smith", "Brown"]}], status: [1, 2, 3, 4], age: [20, 25, 30]}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'firstName' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_sequence_array_test.bal b/ballerina/tests/xsd_sequence_array_test.bal new file mode 100644 index 00000000..eb5d2c9c --- /dev/null +++ b/ballerina/tests/xsd_sequence_array_test.bal @@ -0,0 +1,400 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdSequenceArray record {| + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArray[] seq_XsdSequenceArray; +|}; + +type Seq_XsdSequenceArray record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArray() returns error? { + string xmlStr; + XsdSequenceArray|Error v; + + xmlStr = string `1311.11415.1`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XsdSequenceArray: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}]}); + + xmlStr = string `1311.11414.11515.1`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArray' occurs more than the max allowed times"); +} + +type XsdSequenceArray2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArray2[] seq_XsdSequenceArray2; + + @Sequence { + minOccurs: 0, + maxOccurs: 2 + } + Seq_XsdSequenceArray2_2[] seq_XsdSequenceArray2_2 = []; +|}; + +type Seq_XsdSequenceArray2 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XsdSequenceArray2_2 record {| + @SequenceOrder { + value: 1 + } + int age2; + + @SequenceOrder { + value: 2 + } + float salary2; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArray2() returns error? { + string xmlStr; + XsdSequenceArray2|Error v; + + xmlStr = string `1311.11415.11311.11415.1`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XsdSequenceArray2: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}], seq_XsdSequenceArray2_2: [{age2: 13, salary2: 11.1}, {age2: 14, salary2: 15.1}]}); + + xmlStr = string `1311.11311.11415.11311.11415.1`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArray2' occurs more than the max allowed times"); + + xmlStr = string `1311.11415.11311.11311.11415.1`; + v = parseString(xmlStr); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArray2_2' occurs more than the max allowed times"); +} + +type XSDSequenceArrayRecord13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_XSDSequenceArrayRecord13_1[] seq_XSDSequenceArrayRecord13_1; + + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_XSDSequenceArrayRecord13_2[] seq_XSDSequenceArrayRecord13_2; +}; + +type Seq_XSDSequenceArrayRecord13_1 record { + @SequenceOrder {value: 1} + Seq_Array_A_3 field1; + + @SequenceOrder {value: 2} + Seq_Array_B_3 field2; + + @SequenceOrder {value: 3} + Seq_Array_C_3 field3; +}; + +type Seq_XSDSequenceArrayRecord13_2 record { + @SequenceOrder {value: 1} + Seq_Array_D_3 field4; + + @SequenceOrder {value: 2} + Seq_Array_E_3 field5; + + @SequenceOrder {value: 3} + Seq__Array_F_3 field6; +}; + +type Seq_Array_A_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_Array_3[] value1; +}; + +type Seq_Array_B_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq2_Array_3[] value2; +}; + +type Seq_Array_C_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq3_Array_3[] value3; +}; + +type Seq_Array_D_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_Array_3[] value1; +}; + +type Seq_Array_E_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq2_Array_3[] value2; +}; + +type Seq__Array_F_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq3_Array_3[] value3; +}; + +type Seq_Array_3 record { + @SequenceOrder {value: 1} + string a; + + @SequenceOrder {value: 2} + string b; + + @SequenceOrder {value: 3} + string c; +}; + +type Seq2_Array_3 record { + @SequenceOrder {value: 1} + string d; + + @SequenceOrder {value: 2} + string e; + + @SequenceOrder {value: 3} + string f; +}; + +type Seq3_Array_3 record { + @SequenceOrder {value: 1} + string g; + + @SequenceOrder {value: 2} + string h; + + @SequenceOrder {value: 3} + string i; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXSDSequenceArrayRecord4() returns error? { + string xmlStr = string `123123123123123123`; + XSDSequenceArrayRecord13|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceArrayRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}]}); + + xmlStr = string `123123123123123123123123123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceArrayRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}]}); + + xmlStr = string `123123123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceArrayRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}, {a: "1", b: "2", c: "3"}, {a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}]}}]}); + + xmlStr = string `123123123123123123123123123123123123123123123123123123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'seq_XSDSequenceArrayRecord13_2' occurs more than the max allowed times"); + + xmlStr = string `123123123123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value3' occurs more than the max allowed times"); + + xmlStr = string `123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field6' is not found in 'seq_XSDSequenceArrayRecord13_2'"); + + xmlStr = string `123123123123121233123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'f' is not found in 'value2'"); + + xmlStr = string `123123123123123132`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'i' is not in the correct order in 'value3'"); + + xmlStr = string `132123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'c' is not in the correct order in 'value1'"); + + xmlStr = string `123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'field5' is not in the correct order in 'seq_XSDSequenceArrayRecord13_2'"); +} + +type XsdSequenceArray5 record {| + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XsdSequenceArray5[] seq_XsdSequenceArray5; +|}; + +type Seq_XsdSequenceArray5 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArray5() returns error? { + string xmlStr; + XsdSequenceArray5|Error v; + + xmlStr = string `1311.11415.1`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XsdSequenceArray5: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}]}); + + xmlStr = string `1311.11414.11515.1`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XsdSequenceArray5: [{age: 13, salary: 11.1}, {age: 14, salary: 14.1}, {age: 15, salary: 15.1}]}); + + xmlStr = string `1311.11414.11515.11515.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_XsdSequenceArray5' occurs more than the max allowed times"); + + xmlStr = string `1311.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_XsdSequenceArray5' occurs less than the min required times"); +} + +type XSDSequenceArrayRecord6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XSDSequenceArrayRecord6_1[] seq_XSDSequenceArrayRecord6_1; + + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XSDSequenceArrayRecord6_2[] seq_XSDSequenceArrayRecord6_2; +}; + +type Seq_XSDSequenceArrayRecord6_1 record { + @SequenceOrder {value: 1} + Seq_Array_A_6 field1; + + @SequenceOrder {value: 2} + Seq_Array_B_6 field2; +}; + +type Seq_XSDSequenceArrayRecord6_2 record { + @SequenceOrder {value: 1} + Seq_Array_D_6 field4; + + @SequenceOrder {value: 2} + Seq_Array_E_6 field5; +}; + +type Seq_Array_A_6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_Array_6[] value1; +}; + +type Seq_Array_B_6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq2_Array_6[] value2; +}; + +type Seq_Array_D_6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_Array_6[] value1; +}; + +type Seq_Array_E_6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq2_Array_6[] value2; +}; + +type Seq_Array_6 record { + @SequenceOrder {value: 1} + string a; +}; + +type Seq2_Array_6 record { + @SequenceOrder {value: 1} + string d; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXSDSequenceArrayRecord6() returns error? { + string xmlStr = string `1111111111111111`; + XSDSequenceArrayRecord6|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {"seq_XSDSequenceArrayRecord6_1":[{"field1":{"value1":[{"a":"1"},{"a":"1"}]},"field2":{"value2":[{"d":"1"},{"d":"1"}]}},{"field1":{"value1":[{"a":"1"},{"a":"1"}]},"field2":{"value2":[{"d":"1"},{"d":"1"}]}}],"seq_XSDSequenceArrayRecord6_2":[{"field4":{"value1":[{"a":"1"},{"a":"1"}]},"field5":{"value2":[{"d":"1"},{"d":"1"}]}},{"field4":{"value1":[{"a":"1"},{"a":"1"}]},"field5":{"value2":[{"d":"1"},{"d":"1"}]}}]}); +} diff --git a/ballerina/tests/xsd_sequence_array_test_with_parse_type.bal b/ballerina/tests/xsd_sequence_array_test_with_parse_type.bal new file mode 100644 index 00000000..91f099e6 --- /dev/null +++ b/ballerina/tests/xsd_sequence_array_test_with_parse_type.bal @@ -0,0 +1,329 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XsdSequenceArrayWithXmlValue record {| + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArrayWithXmlValue[] seq_XsdSequenceArrayWithXmlValue; +|}; + +type Seq_XsdSequenceArrayWithXmlValue record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArrayWithXmlValue() returns error? { + xml xmlValue; + XsdSequenceArrayWithXmlValue|Error v; + xml|Error toXmlresult; + + xmlValue = xml `1311.11415.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XsdSequenceArrayWithXmlValue: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}]}); + test:assertEquals(toXml(check v), xml `1311.11415.1`); + + xmlValue = xml `1311.11414.11515.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArrayWithXmlValue' occurs more than the max allowed times"); + toXmlresult = toXml({seq_XsdSequenceArrayWithXmlValue: [{age: 13, salary: 11.1}, {age: 14, salary: 14.1}, {age: 15, salary: 15.1}]}); + test:assertTrue(toXmlresult is error); + test:assertEquals((toXmlresult).message(), "'seq_XsdSequenceArrayWithXmlValue' occurs more than the max allowed times"); + + xmlValue = xml `1311.114`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "Element(s) 'salary' is not found in 'seq_XsdSequenceArrayWithXmlValue'"); + + xmlValue = xml `131415.111.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'age' occurs more than the max allowed times in 'seq_XsdSequenceArrayWithXmlValue'"); +} + +@Name { + value: "Root" +} +type XsdSequenceArrayWithXmlValue2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArrayWithXmlValue2[] seq_XsdSequenceArrayWithXmlValue2; + + @Sequence { + minOccurs: 0, + maxOccurs: 2 + } + Seq_XsdSequenceArrayWithXmlValue2_2[] seq_XsdSequenceArrayWithXmlValue2_2 = []; +|}; + +type Seq_XsdSequenceArrayWithXmlValue2 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XsdSequenceArrayWithXmlValue2_2 record {| + @SequenceOrder { + value: 1 + } + int age2; + + @SequenceOrder { + value: 2 + } + float salary2; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArrayWithXmlValue2() returns error? { + xml xmlValue; + XsdSequenceArrayWithXmlValue2|Error v; + xml|Error toXmlresult; + + xmlValue = xml `1311.11415.11311.11415.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XsdSequenceArrayWithXmlValue2: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}], seq_XsdSequenceArrayWithXmlValue2_2: [{age2: 13, salary2: 11.1}, {age2: 14, salary2: 15.1}]}); + test:assertEquals(toXml(check v), xml `1311.11415.11311.11415.1`); + + xmlValue = xml `1311.11311.11415.11311.11415.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArrayWithXmlValue2' occurs more than the max allowed times"); + toXmlresult = toXml({seq_XsdSequenceArrayWithXmlValue2: [{age: 13, salary: 11.1}, {age: 13, salary: 11.1}, {age: 14, salary: 15.1}], seq_XsdSequenceArrayWithXmlValue2_2: [{age2: 13, salary2: 11.1}, {age2: 14, salary2: 15.1}]}); + test:assertTrue(toXmlresult is error); + test:assertEquals((toXmlresult).message(), "'seq_XsdSequenceArrayWithXmlValue2' occurs more than the max allowed times"); + + xmlValue = xml `1311.11415.11311.11311.11415.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is error); + test:assertEquals((v).message(), "'seq_XsdSequenceArrayWithXmlValue2_2' occurs more than the max allowed times"); + toXmlresult = toXml({seq_XsdSequenceArrayWithXmlValue2: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}], seq_XsdSequenceArrayWithXmlValue2_2: [{age2: 13, salary2: 11.1}, {age2: 13, salary2: 11.1}, {age2: 14, salary2: 15.1}]}); + test:assertTrue(toXmlresult is error); + test:assertEquals((toXmlresult).message(), "'seq_XsdSequenceArrayWithXmlValue2_2' occurs more than the max allowed times"); +} + +@Name { + value: "Root" +} +type XSDSequenceArrayWithXmlValueRecord13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_XSDSequenceArrayWithXmlValueRecord13_1[] seq_XSDSequenceArrayWithXmlValueRecord13_1; + + @Sequence { + minOccurs: 1, + maxOccurs: 3 + } + Seq_XSDSequenceArrayWithXmlValueRecord13_2[] seq_XSDSequenceArrayWithXmlValueRecord13_2; +}; + +type Seq_XSDSequenceArrayWithXmlValueRecord13_1 record { + @SequenceOrder {value: 1} + Seq_Array_A_3 field1; + + @SequenceOrder {value: 2} + Seq_Array_B_3 field2; + + @SequenceOrder {value: 3} + Seq_Array_C_3 field3; +}; + +type Seq_XSDSequenceArrayWithXmlValueRecord13_2 record { + @SequenceOrder {value: 1} + Seq_Array_D_3 field4; + + @SequenceOrder {value: 2} + Seq_Array_E_3 field5; + + @SequenceOrder {value: 3} + Seq__Array_F_3 field6; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXSDSequenceArrayWithXmlValueRecord4() returns error? { + xml|Error toXmlresult; + + xml xmlValue = xml `123123123123123123`; + XSDSequenceArrayWithXmlValueRecord13|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceArrayWithXmlValueRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayWithXmlValueRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}]}); + test:assertEquals(toXml(check v2), xml `123123123123123123`); + + xmlValue = xml `123123123123123123123123123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceArrayWithXmlValueRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayWithXmlValueRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}]}); + test:assertEquals(toXml(check v2), xml `123123123123123123123123123123123123123123123123123123`); + + xmlValue = xml `123123123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceArrayWithXmlValueRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}, {a: "1", b: "2", c: "3"}, {a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayWithXmlValueRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}]}}]}); + test:assertEquals(toXml(check v2), xml `123123123123123123123123123123123123`); + + xmlValue = xml `123123123123123123123123123123123123123123123123123123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'seq_XSDSequenceArrayWithXmlValueRecord13_2' occurs more than the max allowed times"); + toXmlresult = toXml({seq_XSDSequenceArrayWithXmlValueRecord13_1: [{field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}, {field1: {value1: [{a: "1", b: "2", c: "3"}]}, field2: {value2: [{d: "1", e: "2", f: "3"}]}, field3: {value3: [{g: "1", h: "2", i: "3"}]}}], seq_XSDSequenceArrayWithXmlValueRecord13_2: [{field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}]}}, {field4: {value1: [{a: "1", b: "2", c: "3"}]}, field5: {value2: [{d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}, {d: "1", e: "2", f: "3"}]}, field6: {value3: [{g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}, {g: "1", h: "2", i: "3"}]}}]}); + test:assertTrue(toXmlresult is error); + test:assertEquals((toXmlresult).message(), "'seq_XSDSequenceArrayWithXmlValueRecord13_2' occurs more than the max allowed times"); + + xmlValue = xml `123123123123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'value3' occurs more than the max allowed times"); +} + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXSDSequenceArrayWithXmlValueRecord4P2() returns error? { + xml xmlValue = xml `123123123123123123123`; + XSDSequenceArrayWithXmlValueRecord13|Error v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field6' is not found in 'seq_XSDSequenceArrayWithXmlValueRecord13_2'"); + + xmlValue = xml `123123123123121233123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'f' is not found in 'value2'"); + + xmlValue = xml `123123123123123132`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'i' is not in the correct order in 'value3'"); + + xmlValue = xml `132123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'c' is not in the correct order in 'value1'"); + + xmlValue = xml `123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'field5' is not in the correct order in 'seq_XSDSequenceArrayWithXmlValueRecord13_2'"); +} + +type XsdSequenceArrayWithXmlValue5 record {| + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XsdSequenceArrayWithXmlValue5[] seq_XsdSequenceArrayWithXmlValue5; +|}; + +type Seq_XsdSequenceArrayWithXmlValue5 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceArrayWithXmlValue5() returns error? { + xml xmlValue; + XsdSequenceArrayWithXmlValue5|Error v; + + xmlValue = xml `1311.11415.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XsdSequenceArrayWithXmlValue5: [{age: 13, salary: 11.1}, {age: 14, salary: 15.1}]}); + test:assertEquals(toXml(check v), xml `1311.11415.1`); + + xmlValue = xml `1311.11414.11515.1`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XsdSequenceArrayWithXmlValue5: [{age: 13, salary: 11.1}, {age: 14, salary: 14.1}, {age: 15, salary: 15.1}]}); + test:assertEquals(toXml(check v), xml `1311.11414.11515.1`); + + xmlValue = xml `1311.11414.11515.11515.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_XsdSequenceArrayWithXmlValue5' occurs more than the max allowed times"); + + xmlValue = xml `1311.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'seq_XsdSequenceArrayWithXmlValue5' occurs less than the min required times"); + xml|Error toXmlresult = toXml({seq_XsdSequenceArrayWithXmlValue5: [{age: 13, salary: 11.1}]}); + test:assertTrue(toXmlresult is error); + test:assertEquals((toXmlresult).message(), "'seq_XsdSequenceArrayWithXmlValue5' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XSDSequenceArrayWithXmlValueRecord6 record { + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XSDSequenceArrayWithXmlValueRecord6_1[] seq_XSDSequenceArrayWithXmlValueRecord6_1; + + @Sequence { + minOccurs: 2, + maxOccurs: 3 + } + Seq_XSDSequenceArrayWithXmlValueRecord6_2[] seq_XSDSequenceArrayWithXmlValueRecord6_2; +}; + +type Seq_XSDSequenceArrayWithXmlValueRecord6_1 record { + @SequenceOrder {value: 1} + Seq_Array_A_6 field1; + + @SequenceOrder {value: 2} + Seq_Array_B_6 field2; +}; + +type Seq_XSDSequenceArrayWithXmlValueRecord6_2 record { + @SequenceOrder {value: 1} + Seq_Array_D_6 field4; + + @SequenceOrder {value: 2} + Seq_Array_E_6 field5; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXSDSequenceArrayWithXmlValueRecord6() returns error? { + xml xmlValue = xml `1211111111111111`; + XSDSequenceArrayWithXmlValueRecord6|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {"seq_XSDSequenceArrayWithXmlValueRecord6_1":[{"field1":{"value1":[{"a":"1"},{"a":"2"}]},"field2":{"value2":[{"d":"1"},{"d":"1"}]}},{"field1":{"value1":[{"a":"1"},{"a":"1"}]},"field2":{"value2":[{"d":"1"},{"d":"1"}]}}],"seq_XSDSequenceArrayWithXmlValueRecord6_2":[{"field4":{"value1":[{"a":"1"},{"a":"1"}]},"field5":{"value2":[{"d":"1"},{"d":"1"}]}},{"field4":{"value1":[{"a":"1"},{"a":"1"}]},"field5":{"value2":[{"d":"1"},{"d":"1"}]}}]}); + test:assertEquals(toXml(check v2), xml `1211111111111111`); +} diff --git a/ballerina/tests/xsd_sequence_test_with_element_annotation.bal b/ballerina/tests/xsd_sequence_test_with_element_annotation.bal new file mode 100644 index 00000000..5f8b8e16 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_element_annotation.bal @@ -0,0 +1,421 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +// TODO: Add tests with attributes +type XsdSequenceWithElementAnnotation record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1 seq_EA1; +}; + +type Seq_EA1 record { + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotation() returns error? { + string xmlStr; + XsdSequenceWithElementAnnotation|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1'"); + + xmlStr = string `ABCABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{EA3: ["AB", "AB"]}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +type XsdSequenceWithElementAnnotation2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2 seq_EA2; +}; + +type Seq_EA2 record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotation2() returns error? { + string xmlStr; + XsdSequenceWithElementAnnotation2|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCABCD`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA2: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABCCD`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +type XsdSequenceWithElementAnnotation3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XsdSequenceWithElementAnnotation3_1 seq_XsdSequenceWithElementAnnotation3_1; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XsdSequenceWithElementAnnotation3_2 seq_XsdSequenceWithElementAnnotation3_2?; +}; + +type Seq_XsdSequenceWithElementAnnotation3_1 record { + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder {value: 1} + Seq_A_3[] field1; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 2} + Seq_B_3[] field2?; + + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder {value: 3} + Seq_C_3 field3; +}; + +type Seq_XsdSequenceWithElementAnnotation3_2 record { + @SequenceOrder {value: 1} + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Seq_D_3[] field4?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 2} + Seq_E_3[] field5?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 3} + Seq_F_3[] field6?; +}; + +type Seq_A_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_3 value1; +}; + +type Seq_B_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq2_3 value2; +}; + +type Seq_C_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq3_3 value3; +}; + +type Seq_D_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_3 value1; +}; + +type Seq_E_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq2_3 value2; +}; + +type Seq_F_3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq3_3 value3; +}; + +type Seq_3 record { + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 1} + string[] a?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 2} + string[] b?; + + @SequenceOrder {value: 3} + string c; +}; + +type Seq2_3 record { + @SequenceOrder {value: 1} + string d; + + @SequenceOrder {value: 2} + string e; + + @SequenceOrder {value: 3} + string f; +}; + +type Seq3_3 record { + @SequenceOrder {value: 1} + string g; + + @SequenceOrder {value: 2} + string h; + + @SequenceOrder {value: 3} + string i; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotation3() returns error? { + string xmlStr; + XsdSequenceWithElementAnnotation3|Error v2; + + xmlStr = string `123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotation3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotation3_2: {field4: [{value1: {a: ["1"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}, {value3: {g: "1", h: "2", i: "3"}}]}}); + + xmlStr = string `123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotation3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotation3_2: {field4: [{value1: {a: ["1"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}]}}); + + xmlStr = string `123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotation3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotation3_2: {field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}]}}); + + xmlStr = string `2312312312323123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotation3_1: {field1: [{value1: {b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotation3_2: {field4: [{value1: {a: ["1", "2", "3"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}]}}); + + xmlStr = string `33123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'seq_XsdSequenceWithElementAnnotation3_2' occurs less than the min required times"); + + xmlStr = string `2312312323123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field3' is not found in 'seq_XsdSequenceWithElementAnnotation3_1'"); + + xmlStr = string `231231231232312312`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'i' not present in XML"); + + xmlStr = string `123123123123123123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'field5' occurs more than the max allowed times"); + + xmlStr = string `1231222223123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'b' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_sequence_test_with_element_annotation_with_parse_type.bal b/ballerina/tests/xsd_sequence_test_with_element_annotation_with_parse_type.bal new file mode 100644 index 00000000..41460b03 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_element_annotation_with_parse_type.bal @@ -0,0 +1,388 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XsdSequenceWithElementAnnotationWithXmlValue record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1_Xml_Value seq_EA1_Xml_Value; +}; + +type Seq_EA1_Xml_Value record { + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotationWithXmlValue() returns error? { + xml xmlValue; + XsdSequenceWithElementAnnotationWithXmlValue|Error v; + xml|Error toXmlResult; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1_Xml_Value'"); + + xmlValue = xml `ABCABCABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1_Xml_Value: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + test:assertEquals(toXml(check v, {}), xmlValue); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_Xml_Value":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABCABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_Xml_Value":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_Xml_Value":{EA3: ["AB", "AB"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA3: ["AB", "AB", "AB", "AB", "AB", "AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_Xml_Value":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA1: "ABC", EA2: "ABC", EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA2: "ABC", EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA2: "ABC", EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_Xml_Value: {EA1: "ABC", EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XsdSequenceWithElementAnnotationWithXmlValue2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2_With_Xml_Value seq_EA2_With_Xml_Value; +}; + +type Seq_EA2_With_Xml_Value record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotationWithXmlValue2() returns error? { + xml xmlValue; + XsdSequenceWithElementAnnotationWithXmlValue2|Error v; + xml|Error toXmlResult; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA2_With_Xml_Value: {EA: {EA1: "ABC", EA2: "ABC", EA3: []}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA2_With_Xml_Value: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB"]}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABCD`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA2_With_Xml_Value: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + test:assertEquals(toXml(check v, {}), xmlValue); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_With_Xml_Value": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v, {}), xmlValue); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_With_Xml_Value": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v, {}), xmlValue); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_With_Xml_Value": {EA: {EA3: ["AB", "AB"]}}}); + test:assertEquals(toXml(check v, {}), xmlValue); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + toXmlResult = toXml({seq_EA2_With_Xml_Value: {EA: {EA3: ["AB", "AB", "AB", "AB", "AB", "AB"]}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_With_Xml_Value": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABCCD`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA2_With_Xml_Value: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["CD"]}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +@Name { + value: "Root" +} +type XsdSequenceWithElementAnnotationWithXmlValue3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XsdSequenceWithElementAnnotationWithXmlValue3_1 seq_XsdSequenceWithElementAnnotationWithXmlValue3_1; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XsdSequenceWithElementAnnotationWithXmlValue3_2 seq_XsdSequenceWithElementAnnotationWithXmlValue3_2?; +}; + +type Seq_XsdSequenceWithElementAnnotationWithXmlValue3_1 record { + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder {value: 1} + Seq_A_3[] field1; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 2} + Seq_B_3[] field2?; + + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder {value: 3} + Seq_C_3 field3; +}; + +type Seq_XsdSequenceWithElementAnnotationWithXmlValue3_2 record { + @SequenceOrder {value: 1} + @Element { + minOccurs: 0, + maxOccurs: 3 + } + Seq_D_3[] field4?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 2} + Seq_E_3[] field5?; + + @Element { + minOccurs: 0, + maxOccurs: 3 + } + @SequenceOrder {value: 3} + Seq_F_3[] field6?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithElementAnnotationWithXmlValue3() returns error? { + xml xmlValue; + XsdSequenceWithElementAnnotationWithXmlValue3|Error v2; + + xmlValue = xml `123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotationWithXmlValue3_2: {field4: [{value1: {a: ["1"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}, {value3: {g: "1", h: "2", i: "3"}}]}}); + test:assertEquals(toXml(check v2, {}), xml `123123123123123123123123123123`); + + xmlValue = xml `123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotationWithXmlValue3_2: {field4: [{value1: {a: ["1"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}]}}); + test:assertEquals(toXml(check v2, {}), xml `123123123123123123123123`); + + xmlValue = xml `123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotationWithXmlValue3_2: {field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}]}}); + test:assertEquals(toXml(check v2, {}), xml `123123123123123123123123`); + + xmlValue = xml `2312312312323123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XsdSequenceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2"], c: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XsdSequenceWithElementAnnotationWithXmlValue3_2: {field4: [{value1: {a: ["1", "2", "3"], b: ["2"], c: "3"}}], field5: [{value2: {d: "1", e: "2", f: "3"}}], field6: [{value3: {g: "1", h: "2", i: "3"}}]}}); + test:assertEquals(toXml(check v2, {}), xmlValue); + + xmlValue = xml `33123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'seq_XsdSequenceWithElementAnnotationWithXmlValue3_2' occurs less than the min required times"); + + xmlValue = xml `2312312323123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field3' is not found in 'seq_XsdSequenceWithElementAnnotationWithXmlValue3_1'"); + + xmlValue = xml `231231231232312312`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "required field 'i' not present in XML"); + + xmlValue = xml `123123123123123123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'field5' occurs more than the max allowed times"); + + xmlValue = xml `1231222223123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "'b' occurs more than the max allowed times"); + xml|Error toXmlResult = toXml({seq_XsdSequenceWithElementAnnotationWithXmlValue3_1: {field1: [{value1: {a: ["1"], b: ["2"], c: "3"}}, {value1: {a: ["1"], b: ["2", "2", "2", "2", "2"], c: "3"}}], field2: [{value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}, {value2: {d: "1", e: "2", f: "3"}}], field3: {value3: {g: "1", h: "2", i: "3"}}}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'b' occurs more than the max allowed times"); +} diff --git a/ballerina/tests/xsd_sequence_test_with_name_annotation.bal b/ballerina/tests/xsd_sequence_test_with_name_annotation.bal new file mode 100644 index 00000000..78bb9450 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_name_annotation.bal @@ -0,0 +1,231 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdSequenceWithNameAnnotation record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1_NameAnnotation seq_EA1_NameAnnotation; +}; + +type Seq_EA1_NameAnnotation record { + + @Name {value: "A1"} + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Name {value: "A2"} + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Name {value: "A3"} + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNameAnnotation() returns error? { + string xmlStr; + XsdSequenceWithNameAnnotation|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1_NameAnnotation'"); + + xmlStr = string `ABCABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1_NameAnnotation: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NameAnnotation":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NameAnnotation":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NameAnnotation":{EA3: ["AB", "AB"]}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NameAnnotation":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} + +type XsdSequenceWithNameAnnotation2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2_NameAnnotation seq_EA2_NameAnnotation; +}; + +type Seq_EA2_NameAnnotation record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Name {value: "A1"} + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Name {value: "A2"} + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + + @Name {value: "A3"} + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNameAnnotation2() returns error? { + string xmlStr; + XsdSequenceWithNameAnnotation2|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCABCABCD`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA2_NameAnnotation: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NameAnnotation": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NameAnnotation": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NameAnnotation": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NameAnnotation": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABCCD`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_sequence_test_with_name_annotation_with_parse_type.bal b/ballerina/tests/xsd_sequence_test_with_name_annotation_with_parse_type.bal new file mode 100644 index 00000000..880d2fc5 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_name_annotation_with_parse_type.bal @@ -0,0 +1,231 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdSequenceWithNameAnnotationWithXmlValue record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1_NameAnnotationWithXmlValue seq_EA1_NameAnnotationWithXmlValue; +}; + +type Seq_EA1_NameAnnotationWithXmlValue record { + + @Name {value: "A1"} + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + string EA1?; + + @Name {value: "A2"} + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + string EA2?; + + @Name {value: "A3"} + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNameAnnotationWithXmlValue() returns error? { + xml xmlValue; + XsdSequenceWithNameAnnotationWithXmlValue|Error v; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1_NameAnnotationWithXmlValue'"); + + xmlValue = xml `ABCABCABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA1_NameAnnotationWithXmlValue: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_NameAnnotationWithXmlValue":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + + xmlValue = xml `ABCABABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_NameAnnotationWithXmlValue":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_NameAnnotationWithXmlValue":{EA3: ["AB", "AB"]}}); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA1_NameAnnotationWithXmlValue":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} + +type XsdSequenceWithNameAnnotationWithXmlValue2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2_NameAnnotationWithXmlValue seq_EA2_NameAnnotationWithXmlValue; +}; + +type Seq_EA2_NameAnnotationWithXmlValue record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Name {value: "A1"} + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Name {value: "A2"} + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + + @Name {value: "A3"} + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNameAnnotationWithXmlValue2() returns error? { + xml xmlValue; + XsdSequenceWithNameAnnotationWithXmlValue2|Error v; + + xmlValue = xml `ABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCABCABCD`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_EA2_NameAnnotationWithXmlValue: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_NameAnnotationWithXmlValue": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_NameAnnotationWithXmlValue": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_NameAnnotationWithXmlValue": {EA: {EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABABABABABAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs more than the max allowed times"); + + xmlValue = xml `ABCABAB`; + v = parseAsType(xmlValue); + test:assertEquals(v, {"seq_EA2_NameAnnotationWithXmlValue": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlValue = xml `ABCABCCD`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `AB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); + + xmlValue = xml `ABCAB`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'A3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_sequence_test_with_namespace_annotation.bal b/ballerina/tests/xsd_sequence_test_with_namespace_annotation.bal new file mode 100644 index 00000000..5d66cbc4 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_namespace_annotation.bal @@ -0,0 +1,249 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XsdSequenceWithNamespaceAnnotation record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1_NamespaceAnnotation seq_EA1_NamespaceAnnotation; +}; + +type Seq_EA1_NamespaceAnnotation record { + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Namespace { + uri: "example1.com", + prefix: "ea1" + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Namespace { + uri: "example2.com", + prefix: "ea2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @Namespace { + uri: "example3.com", + prefix: "ea3" + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNamespaceAnnotation() returns error? { + string xmlStr; + XsdSequenceWithNamespaceAnnotation|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1_NamespaceAnnotation'"); + + xmlStr = string `ABCABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA1_NamespaceAnnotation: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotation":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotation":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotation":{EA3: ["AB", "AB"]}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotation":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +type XsdSequenceWithNamespaceAnnotation2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2_NamespaceAnnotation seq_EA2_NamespaceAnnotation; +}; + +type Seq_EA2_NamespaceAnnotation record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Namespace { + uri: "example1.com", + prefix: "ea1" + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Namespace { + uri: "example2.com", + prefix: "ea2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + + @Namespace { + uri: "example3.com", + prefix: "ea3" + } + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNamespaceAnnotation2() returns error? { + string xmlStr; + XsdSequenceWithNamespaceAnnotation2|Error v; + + xmlStr = string `ABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCABCABCD`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_EA2_NamespaceAnnotation: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotation": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotation": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotation": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABABABABABAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = string `ABCABAB`; + v = parseString(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotation": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = string `ABCABCCD`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `AB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = string `ABCAB`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_sequence_test_with_namespace_annotation_with_parse_type.bal b/ballerina/tests/xsd_sequence_test_with_namespace_annotation_with_parse_type.bal new file mode 100644 index 00000000..382cbcf4 --- /dev/null +++ b/ballerina/tests/xsd_sequence_test_with_namespace_annotation_with_parse_type.bal @@ -0,0 +1,263 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XsdSequenceWithNamespaceAnnotationWithXmlValue record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA1_NamespaceAnnotationWithXmlValue seq_EA1_NamespaceAnnotationWithXmlValue; +}; + +type Seq_EA1_NamespaceAnnotationWithXmlValue record { + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Namespace { + uri: "example1.com", + prefix: "ea1" + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Namespace { + uri: "example2.com", + prefix: "ea2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @Namespace { + uri: "example3.com", + prefix: "ea3" + } + @SequenceOrder { + value: 3 + } + string[] EA3?; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNamespaceAnnotationWithXmlValue() returns error? { + xml xmlStr; + XsdSequenceWithNamespaceAnnotationWithXmlValue|Error v; + xml|Error toXmlResult; + + xmlStr = xml `ABCABC`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'EA3' is not found in 'seq_EA1_NamespaceAnnotationWithXmlValue'"); + toXmlResult = toXml({seq_EA1_NamespaceAnnotationWithXmlValue: {EA1: "ABC", EA2: "ABC", EA3: []}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCABCABABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {seq_EA1_NamespaceAnnotationWithXmlValue: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "AB", "AB"]}}); + // // BUG: https://github.com/ballerina-platform/ballerina-library/issues/7389 + // test:assertEquals(toXml(check v), xml `ABCABCABABAB`); + + xmlStr = xml `ABCABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotationWithXmlValue":{"EA2": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = xml `ABCABABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotationWithXmlValue":{"EA2": "ABC", EA3: ["AB", "AB", "AB"]}}); + + xmlStr = xml `ABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotationWithXmlValue":{EA3: ["AB", "AB"]}}); + + xmlStr = xml `ABABABABABAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + toXmlResult = toXml({seq_EA1_NamespaceAnnotationWithXmlValue: {EA3: ["AB", "AB", "AB", "AB", "AB", "AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), (v).message()); + + xmlStr = xml `ABCABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA1_NamespaceAnnotationWithXmlValue":{"EA1": "ABC", EA3: ["AB", "AB"]}}); + + xmlStr = xml `ABCABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + toXmlResult = toXml({seq_EA1_NamespaceAnnotationWithXmlValue: {EA3: ["AB"]}}); + test:assertTrue(toXmlResult is Error); + test:assertEquals((toXmlResult).message(), (v).message()); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `AB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} + +type XsdSequenceWithNamespaceAnnotationWithXmlValue2 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_EA2_NamespaceAnnotationWithXmlValue seq_EA2_NamespaceAnnotationWithXmlValue; +}; + +type Seq_EA2_NamespaceAnnotationWithXmlValue record { + @SequenceOrder { + value: 1 + } + record { + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 1 + } + @Namespace { + uri: "example1.com", + prefix: "ea1" + } + string EA1?; + + @Element { + maxOccurs: 1, + minOccurs: 0 + } + @SequenceOrder { + value: 2 + } + @Namespace { + uri: "example2.com", + prefix: "ea2" + } + string EA2?; + + @Element { + maxOccurs: 4, + minOccurs: 2 + } + @Namespace { + uri: "example3.com", + prefix: "ea3" + } + @SequenceOrder { + value: 3 + } + string[] EA3?; + } EA; +}; + +@test:Config {groups: ["xsd", "xsd_sequence", "xsd_element", "xsd_element_and_sequence"]} +function testXsdSequenceWithNamespaceAnnotationWithXmlValue2() returns error? { + xml xmlStr; + XsdSequenceWithNamespaceAnnotationWithXmlValue2|Error v; + + xmlStr = xml `ABCABC`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCABCABCD`; + v = parseAsType(xmlStr); + test:assertEquals(v, {seq_EA2_NamespaceAnnotationWithXmlValue: {EA: {EA1: "ABC", EA2: "ABC", EA3: ["AB", "CD"]}}}); + + xmlStr = xml `ABCABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotationWithXmlValue": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = xml `ABCABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotationWithXmlValue": {EA: {"EA2": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = xml `ABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotationWithXmlValue": {EA: {EA3: ["AB", "AB"]}}}); + + xmlStr = xml `ABABABABABAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs more than the max allowed times"); + + xmlStr = xml `ABCABAB`; + v = parseAsType(xmlStr); + test:assertEquals(v, {"seq_EA2_NamespaceAnnotationWithXmlValue": {EA: {"EA1": "ABC", EA3: ["AB", "AB"]}}}); + + xmlStr = xml `ABCABCCD`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `AB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); + + xmlStr = xml `ABCAB`; + v = parseAsType(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'EA3' occurs less than the min required times"); +} diff --git a/ballerina/tests/xsd_sequence_tests.bal b/ballerina/tests/xsd_sequence_tests.bal new file mode 100644 index 00000000..382216e4 --- /dev/null +++ b/ballerina/tests/xsd_sequence_tests.bal @@ -0,0 +1,1074 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XSDSequenceRecord record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord seq_XSDSequenceRecord; +|}; + +type Seq_XSDSequenceRecord record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence() returns error? { + string xmlStr = string `1311.1`; + XSDSequenceRecord|Error v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecord: {age: 13, salary: 11.1}}); + test:assertEquals((check v).seq_XSDSequenceRecord.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecord.salary, 11.1); + + xmlStr = string `11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecord'"); + + xmlStr = string `13`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord'"); + + xmlStr = string ``; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), ("required field 'seq_XSDSequenceRecord' not present in XML"), msg = (v).message()); + + xmlStr = string `11.113`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecord'"); + + // TODO: Create an issue + // xmlStr = string `13`; + // v = parseString(xmlStr); + // test:assertTrue(v is Error); + // test:assertTrue((v).message().includes("Element age is not in the correct order in"), msg = (v).message()); +} + +type XSDSequenceRecordP2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordP2 seq_XSDSequenceRecordP2; +|}; + +type Seq_XSDSequenceRecordP2 record {| + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder { + value: 1 + } + int[] age; + + @SequenceOrder { + value: 2 + } + float salary; + + @SequenceOrder { + value: 3 + } + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string[] name; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceP2() returns error? { + string xmlStr; + XSDSequenceRecordP2|Error v; + + xmlStr = string `1311.1ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecordP2: {age: [13], salary: 11.1, name: ["ABC"]}}); + + xmlStr = string `13131311.1ABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecordP2: {age: [13, 13, 13], salary: 11.1, name: ["ABC"]}}); + + xmlStr = string `13ABC11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'name' is not in the correct order in 'seq_XSDSequenceRecordP2'"); + + xmlStr = string `1313ABC11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'name' is not in the correct order in 'seq_XSDSequenceRecordP2'"); + + xmlStr = string `1311.1ABC11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordP2'"); + + xmlStr = string `1313131311.1ABC11.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'age' occurs more than the max allowed times"); + + xmlStr = string `11.1ABC13`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordP2'"); + + xmlStr = string `13131311.1ABCABC`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecordP2: {age: [13, 13, 13], salary: 11.1, name: ["ABC", "ABC"]}}); + + xmlStr = string `13131311.1ABCABCABC`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'name' occurs more than the max allowed times"); + + xmlStr = string `131311.1ABC13`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), ("Element(s) 'salary, name' is not found in 'seq_XSDSequenceRecordP2'"), msg = (v).message()); +} + +// TODO: Test with open records. +type XSDSequenceRecord2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord2 seq_XSDSequenceRecord2; + + int num; +|}; + +type Seq_XSDSequenceRecord2 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence2() returns error? { + string xmlStr = string `31311.1`; + XSDSequenceRecord2|Error v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecord2: {age: 13, salary: 11.1}, num: 3}); + test:assertEquals((check v).seq_XSDSequenceRecord2.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecord2.salary, 11.1); + test:assertEquals((check v).num, 3); + + xmlStr = string `1311.13`; + v = parseString(xmlStr); + test:assertEquals(v, {seq_XSDSequenceRecord2: {age: 13, salary: 11.1}, num: 3}); + test:assertEquals((check v).seq_XSDSequenceRecord2.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecord2.salary, 11.1); + test:assertEquals((check v).num, 3); + + xmlStr = string `13311.1`; + v = parseString(xmlStr); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord2'"); +} + +type XSDSequenceRecord3 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord3 seq_XSDSequenceRecord3; + + // TODO: After adding XSD validation for traverse, check union fields as well + record{int n;} num; +|}; + +type Seq_XSDSequenceRecord3 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence3() returns error? { + string xmlStr = string `31311.1`; + XSDSequenceRecord3|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord3: {age: 13, salary: 11.1}, num: {n: 3}}); + test:assertEquals((check v2).seq_XSDSequenceRecord3.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord3.salary, 11.1); + test:assertEquals((check v2).num, {n: 3}); + + xmlStr = string `1311.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord3: {age: 13, salary: 11.1}, num: {n: 3}}); + test:assertEquals((check v2).seq_XSDSequenceRecord3.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord3.salary, 11.1); + test:assertEquals((check v2).num, {n: 3}); + + xmlStr = string `13311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord3'"); +} + +type XSDSequenceRecord4 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord4 seq_XSDSequenceRecord4; +|}; + +type Seq_XSDSequenceRecord4 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence4() returns error? { + string xmlStr = string `31311.1`; + XSDSequenceRecord4|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord4: {age: 13, salary: 11.1}, num: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecord4.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord4.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + + xmlStr = string `1311.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord4: {age: 13, salary: 11.1}, num: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecord4.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord4.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + + xmlStr = string `13311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord4'"); +} + +type XSDSequenceRecord5 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord5 seq_XSDSequenceRecord5; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecord5 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence5() returns error? { + string xmlStr = string `331311.1`; + XSDSequenceRecord5|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecord5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `31311.13`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecord5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `1311.133`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecord5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `133311.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord5'"); +} + +type XSDSequenceRecord6 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord6_1 seq_XSDSequenceRecord6_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord6_2 seq_XSDSequenceRecord6_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecord6_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord6_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence6() returns error? { + string xmlStr = string `3SDsuccess31311.1`; + XSDSequenceRecord6|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `SDsuccess331311.1`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `33SDsuccess1311.1`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `SDsuccess1311.133`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecord6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + + xmlStr = string `SD13success11.1`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecord6_2'"); + + xmlStr = string `13success11.1SD`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord6_1'"); + + xmlStr = string `successSD1311.133`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'status' is not in the correct order in 'seq_XSDSequenceRecord6_2'"); + + xmlStr = string `SDsuccess11.11333`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecord6_1'"); + + xmlStr = string `SDsuccess11.13133`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecord6_1'"); + + xmlStr = string `SDsuccess11313.13`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord6_1'"); +} + +type XSDSequenceRecord7 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord7_1 seq_XSDSequenceRecord7_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord7_2 seq_XSDSequenceRecord7_2; +|}; + +type Seq_XSDSequenceRecord7_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord7_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence7() returns error? { + string xmlStr = string `SDsuccess1311.1`; + XSDSequenceRecord7|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord7_1: {age: 13, salary: 11.1}, seq_XSDSequenceRecord7_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecord7_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecord7_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecord7_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecord7_2.status, "success"); +} + +type XSDSequenceRecord8 record {| + XSDSequenceRecord8P2 test; + int 'check; +|}; + +type XSDSequenceRecord8P2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord8_1 seq_XSDSequenceRecord8_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord8_2 seq_XSDSequenceRecord8_2; +|}; + +type Seq_XSDSequenceRecord8_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord8_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence8() returns error? { + string xmlStr = string `SDsuccess1311.12`; + XSDSequenceRecord8|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {'check: 2, test: {seq_XSDSequenceRecord8_1: {age: 13, salary: 11.1}, seq_XSDSequenceRecord8_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord8_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord8_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord8_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecord8_2.status, "success"); +} + +type XSDSequenceRecord9 record {| + XSDSequenceRecord9P test; + int a; +|}; + +type XSDSequenceRecord9P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord9_1 seq_XSDSequenceRecord9_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord9_2 seq_XSDSequenceRecord9_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecord9_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord9_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence9() returns error? { + string xmlStr = string `3SDsuccess31311.12`; + XSDSequenceRecord9|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SDsuccess331311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `33SDsuccess1311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SDsuccess1311.1332`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecord9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SD13success11.12`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecord9_2'"); + + xmlStr = string `13success11.1SD2`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord9_1'"); +} + +type XSDSequenceRecord10 record {| + XSDSequenceRecord10P test; + int a; +|}; + +type XSDSequenceRecord10P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord10_1 seq_XSDSequenceRecord10_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord10_2 seq_XSDSequenceRecord10_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecord10_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord10_2 record {| + @SequenceOrder { + value: 1 + } + Rec10 name; + + @SequenceOrder { + value: 2 + } + Rec10 status; +|}; + +type Rec10 record {| + string value1; + string value2; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence10() returns error? { + string xmlStr = string `3SDABSuccessFail31311.12`; + XSDSequenceRecord10|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SDABSuccessFail331311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `33SDABSuccessFail1311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SDABSuccessFail1311.1332`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `SDAB13SuccessFail11.12`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecord10_2'"); + + xmlStr = string `13SuccessFail11.1SDAB2`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord10_1'"); +} + +type XSDSequenceRecord11 record {| + XSDSequenceRecord11P test; + int a; + XSDSequenceRecord11P2 test2; +|}; + +type XSDSequenceRecord11P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord11_1 seq_XSDSequenceRecord11_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord11_2 seq_XSDSequenceRecord11_2; + record{record {int n;} n;} num2; +|}; + +type XSDSequenceRecord11P2 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord11_1 seq_XSDSequenceRecord11_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord11_2 seq_XSDSequenceRecord11_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecord11_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecord11_2 record {| + @SequenceOrder { + value: 1 + } + Rec11 name; + + @SequenceOrder { + value: 2 + } + Rec11 status; +|}; + +type Rec11 record {| + string value1; + string value2; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence11() returns error? { + string xmlStr = string `3SDABSuccessFail31311.123SDABSuccessFail31311.1`; + XSDSequenceRecord11|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `3SDABSuccessFail31311.1SDABSuccessFail331311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `33SDABSuccessFail1311.13SDABSuccessFail31311.12`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecord11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecord11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecord11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + + xmlStr = string `3SDABSuccessFail31311.113SuccessFail11.1SDAB2`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecord11_1'"); +} + +type XSDSequenceRecord12 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord12_1 seq_XSDSequenceRecord12_1; +}; + +type Seq_XSDSequenceRecord12_1 record { + @SequenceOrder {value: 1} + Seq_A field1; + + @SequenceOrder {value: 2} + Seq_B field2; + + @SequenceOrder {value: 3} + Seq_C field3; +}; + +type Seq_A record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq value1; +}; + +type Seq_B record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq value2; +}; + +type Seq_C record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq value3; +}; + +type Seq record { + @SequenceOrder {value: 1} + string a; + + @SequenceOrder {value: 2} + string b; + + @SequenceOrder {value: 3} + string c; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence12() returns error? { + string xmlStr = string `123123123`; + XSDSequenceRecord12|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord12_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {a: "1", b: "2", c: "3"}}, field3: {value3: {a: "1", b: "2", c: "3"}}}}); +} + +type XSDSequenceRecord13 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_XSDSequenceRecord13_1 seq_XSDSequenceRecord13_1?; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord13_2 seq_XSDSequenceRecord13_2; +}; + +type Seq_XSDSequenceRecord13_1 record { + @SequenceOrder {value: 1} + Seq_A_13 field1; + + @SequenceOrder {value: 2} + Seq_B_13 field2; + + @SequenceOrder {value: 3} + Seq_C_13 field3; +}; + +type Seq_XSDSequenceRecord13_2 record { + @SequenceOrder {value: 1} + Seq_D_13 field4; + + @SequenceOrder {value: 2} + Seq_E_13 field5; + + @SequenceOrder {value: 3} + Seq_F_13 field6; +}; + +type Seq_A_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_13 value1; +}; + +type Seq_B_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq2_13 value2; +}; + +type Seq_C_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq3_13 value3; +}; + +type Seq_D_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_13 value1; +}; + +type Seq_E_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq2_13 value2; +}; + +type Seq_F_13 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq3_13 value3; +}; + +type Seq_13 record { + @SequenceOrder {value: 1} + string a; + + @SequenceOrder {value: 2} + string b; + + @SequenceOrder {value: 3} + string c; +}; + +type Seq2_13 record { + @SequenceOrder {value: 1} + string d; + + @SequenceOrder {value: 2} + string e; + + @SequenceOrder {value: 3} + string f; +}; + +type Seq3_13 record { + @SequenceOrder {value: 1} + string g; + + @SequenceOrder {value: 2} + string h; + + @SequenceOrder {value: 3} + string i; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequence13() returns error? { + string xmlStr = string `123123123123123123`; + XSDSequenceRecord13|Error v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord13_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {d: "1", e: "2", f: "3"}}, field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XSDSequenceRecord13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}); + + xmlStr = string `123123123`; + v2 = parseString(xmlStr); + test:assertEquals(v2, {seq_XSDSequenceRecord13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}); + + xmlStr = string `123123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field6' is not found in 'seq_XSDSequenceRecord13_2'"); + + xmlStr = string `123123123123121233123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'f' is not found in 'value2'"); + + xmlStr = string `123123123123123132`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'i' is not in the correct order in 'value3'"); + + xmlStr = string `132123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'c' is not in the correct order in 'value1'"); + + xmlStr = string `123123123123123123`; + v2 = parseString(xmlStr); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), ("Element 'field5' is not in the correct order in 'seq_XSDSequenceRecord13_2'"), msg = (v2).message()); +} diff --git a/ballerina/tests/xsd_sequence_tests_with_parse_type.bal b/ballerina/tests/xsd_sequence_tests_with_parse_type.bal new file mode 100644 index 00000000..dcb28ee7 --- /dev/null +++ b/ballerina/tests/xsd_sequence_tests_with_parse_type.bal @@ -0,0 +1,1045 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue seq_XSDSequenceRecordWithXmlValue; +|}; + +type Seq_XSDSequenceRecordWithXmlValue record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue() returns error? { + xml xmlValue = xml `1311.1`; + XSDSequenceRecordWithXmlValue|Error v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XSDSequenceRecordWithXmlValue: {age: 13, salary: 11.1}}); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue.salary, 11.1); + test:assertEquals(toXml(check v), xmlValue); + Error? e = validate(xmlValue, XSDSequenceRecordWithXmlValue); + test:assertTrue(e is ()); + + xmlValue = xml `11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue); + test:assertTrue(e is Error); + test:assertEquals(( e).message(), "Invalid XML found: 'Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue''"); + + xmlValue = xml `13`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue); + test:assertTrue(e is Error); + test:assertEquals(( e).message(), "Invalid XML found: 'Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue''"); + + xmlValue = xml ``; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), ("required field 'seq_XSDSequenceRecordWithXmlValue' not present in XML"), msg = (v).message()); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue); + test:assertTrue(e is Error); + test:assertEquals(( e).message(), "Invalid XML found: 'required field 'seq_XSDSequenceRecordWithXmlValue' not present in XML'"); + + xmlValue = xml `11.113`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue); + test:assertTrue(e is Error); + test:assertEquals(( e).message(), "Invalid XML found: 'Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue''"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValueP2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValueP2 seq_XSDSequenceRecordWithXmlValueP2; +|}; + +type Seq_XSDSequenceRecordWithXmlValueP2 record {| + @Element { + minOccurs: 1, + maxOccurs: 3 + } + @SequenceOrder { + value: 1 + } + int[] age; + + @SequenceOrder { + value: 2 + } + float salary; + + @SequenceOrder { + value: 3 + } + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string[] name; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValueP2() returns error? { + xml xmlValue; + XSDSequenceRecordWithXmlValueP2|Error v; + + xmlValue = xml `1311.1ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XSDSequenceRecordWithXmlValueP2: {age: [13], salary: 11.1, name: ["ABC"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13131311.1ABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XSDSequenceRecordWithXmlValueP2: {age: [13, 13, 13], salary: 11.1, name: ["ABC"]}}); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13ABC11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'name' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValueP2'"); + + xmlValue = xml `1313ABC11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'name' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValueP2'"); + + xmlValue = xml `1311.1ABC11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValueP2'"); + + xmlValue = xml `1313131311.1ABC11.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'age' occurs more than the max allowed times"); + + xmlValue = xml `11.1ABC13`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValueP2'"); + + xmlValue = xml `13131311.1ABCABC`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XSDSequenceRecordWithXmlValueP2: {age: [13, 13, 13], salary: 11.1, name: ["ABC", "ABC"]}}); + + xmlValue = xml `13131311.1ABCABCABC`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "'name' occurs more than the max allowed times"); + + xmlValue = xml `131311.1ABC13`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), ("Element(s) 'salary, name' is not found in 'seq_XSDSequenceRecordWithXmlValueP2'"), msg = (v).message()); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue2 seq_XSDSequenceRecordWithXmlValue2; + int num; +|}; + +type Seq_XSDSequenceRecordWithXmlValue2 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue2() returns error? { + xml xmlValue = xml `31311.1`; + XSDSequenceRecordWithXmlValue2|Error v = parseAsType(xmlValue); + test:assertEquals(v, {num: 3, seq_XSDSequenceRecordWithXmlValue2: {age: 13, salary: 11.1}}); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue2.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue2.salary, 11.1); + test:assertEquals((check v).num, 3); + test:assertEquals(toXml(check v), xml `1311.13`); + + xmlValue = xml `1311.13`; + v = parseAsType(xmlValue); + test:assertEquals(v, {seq_XSDSequenceRecordWithXmlValue2: {age: 13, salary: 11.1}, num: 3}); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue2.age, 13); + test:assertEquals((check v).seq_XSDSequenceRecordWithXmlValue2.salary, 11.1); + test:assertEquals((check v).num, 3); + test:assertEquals(toXml(check v), xmlValue); + + xmlValue = xml `13311.1`; + v = parseAsType(xmlValue); + test:assertTrue(v is Error); + test:assertEquals((v).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue2'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue3 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue3 seq_XSDSequenceRecordWithXmlValue3; + + record{int n;} num; +|}; + +type Seq_XSDSequenceRecordWithXmlValue3 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue3() returns error? { + xml xmlValue = xml `31311.1`; + XSDSequenceRecordWithXmlValue3|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {num: {n: 3}, seq_XSDSequenceRecordWithXmlValue3: {age: 13, salary: 11.1}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue3.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue3.salary, 11.1); + test:assertEquals((check v2).num, {n: 3}); + test:assertEquals(toXml(check v2), xml `1311.13`); + + xmlValue = xml `1311.13`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue3: {age: 13, salary: 11.1}, num: {n: 3}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue3.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue3.salary, 11.1); + test:assertEquals((check v2).num, {n: 3}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `13311.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue3'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue4 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue4 seq_XSDSequenceRecordWithXmlValue4; +|}; + +type Seq_XSDSequenceRecordWithXmlValue4 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue4() returns error? { + xml xmlValue = xml `31311.1`; + XSDSequenceRecordWithXmlValue4|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue4: {age: 13, salary: 11.1}, num: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue4.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue4.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `31311.1`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue4: {age: 13, salary: 11.1}, num: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue4.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue4.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xmlValue); + + xmlValue = xml `13311.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue4'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue5 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue5 seq_XSDSequenceRecordWithXmlValue5; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue5 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue5() returns error? { + xml xmlValue = xml `331311.1`; + XSDSequenceRecordWithXmlValue5|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.13`); + + xmlValue = xml `31311.13`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.13`); + + xmlValue = xml `1311.133`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue5: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue5.salary, 11.1); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.13`); + + xmlValue = xml `1333`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue5'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue6 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue6_1 seq_XSDSequenceRecordWithXmlValue6_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue6_2 seq_XSDSequenceRecordWithXmlValue6_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue6_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue6_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue6() returns error? { + xml xmlValue = xml `3SDsuccess31311.1`; + XSDSequenceRecordWithXmlValue6|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess3`); + + xmlValue = xml `SDsuccess331311.1`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess3`); + + xmlValue = xml `33SDsuccess1311.1`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess3`); + + xmlValue = xml `SDsuccess1311.133`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue6_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue6_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.name, "SD"); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue6_2.status, "success"); + test:assertEquals((check v2).num, {n: {n: 3}}); + test:assertEquals((check v2).num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess3`); + + xmlValue = xml `SD13success11.1`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecordWithXmlValue6_2'"); + + xmlValue = xml `13success11.1SD`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue6_1'"); + + xmlValue = xml `successSD1311.133`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'status' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue6_2'"); + + xmlValue = xml `SDsuccess11.11333`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue6_1'"); + + xmlValue = xml `SDsuccess11.13133`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'salary' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue6_1'"); + + xmlValue = xml `SDsuccess11313.13`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue6_1'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue7 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue7_1 seq_XSDSequenceRecordWithXmlValue7_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue7_2 seq_XSDSequenceRecordWithXmlValue7_2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue7_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue7_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue7() returns error? { + xml xmlValue = xml `SDsuccess1311.1`; + XSDSequenceRecordWithXmlValue7|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue7_1: {age: 13, salary: 11.1}, seq_XSDSequenceRecordWithXmlValue7_2: {name: "SD", status: "success"}}); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue7_1.age, 13); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue7_1.salary, 11.1); + test:assertEquals((check v2).seq_XSDSequenceRecordWithXmlValue7_2.name, "SD"); + test:assertEquals(toXml(check v2), xml `1311.1SDsuccess`); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue8 record {| + XSDSequenceRecordWithXmlValue8P2 test; + int 'check; +|}; + +type XSDSequenceRecordWithXmlValue8P2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue8_1 seq_XSDSequenceRecordWithXmlValue8_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue8_2 seq_XSDSequenceRecordWithXmlValue8_2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue8_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue8_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue8() returns error? { + xml xmlValue = xml `SDsuccess1311.12`; + XSDSequenceRecordWithXmlValue8|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {'check: 2, test: {seq_XSDSequenceRecordWithXmlValue8_1: {salary: 11.1, age: 13}, seq_XSDSequenceRecordWithXmlValue8_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue8_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue8_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue8_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue8_2.status, "success"); + test:assertEquals(toXml(check v2), xml `1311.1SDsuccess2`); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue9 record {| + XSDSequenceRecordWithXmlValue9P test; + int a; +|}; + +type XSDSequenceRecordWithXmlValue9P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue9_1 seq_XSDSequenceRecordWithXmlValue9_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue9_2 seq_XSDSequenceRecordWithXmlValue9_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue9_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue9_2 record {| + @SequenceOrder { + value: 1 + } + string name; + + @SequenceOrder { + value: 2 + } + string status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue9() returns error? { + xml xmlValue = xml `3SDsuccess31311.12`; + XSDSequenceRecordWithXmlValue9|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess32`); + + xmlValue = xml `SDsuccess331311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess32`); + + xmlValue = xml `33SDsuccess1311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess32`); + + xmlValue = xml `SDsuccess1311.1332`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue9_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue9_2: {name: "SD", status: "success"}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.name, "SD"); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue9_2.status, "success"); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDsuccess32`); + + xmlValue = xml `SD13success11.12`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecordWithXmlValue9_2'"); + + xmlValue = xml `13success11.1SD2`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue9_1'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue10 record {| + XSDSequenceRecordWithXmlValue10P test; + int a; +|}; + +type XSDSequenceRecordWithXmlValue10P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue10_1 seq_XSDSequenceRecordWithXmlValue10_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue10_2 seq_XSDSequenceRecordWithXmlValue10_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue10_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue10_2 record {| + @SequenceOrder { + value: 1 + } + Rec10 name; + + @SequenceOrder { + value: 2 + } + Rec10 status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue10() returns error? { + xml xmlValue = xml `3SDABSuccessFail31311.12`; + XSDSequenceRecordWithXmlValue10|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail32`); + + xmlValue = xml `SDABSuccessFail331311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail32`); + + xmlValue = xml `33SDABSuccessFail1311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail32`); + + xmlValue = xml `SDABSuccessFail1311.1332`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue10_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue10_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue10_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail32`); + + xmlValue = xml `SDAB13SuccessFail11.12`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'status' is not found in 'seq_XSDSequenceRecordWithXmlValue10_2'"); + + xmlValue = xml `13SuccessFail11.1SDAB2`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue10_1'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue11 record {| + XSDSequenceRecordWithXmlValue11P test; + int a; + XSDSequenceRecordWithXmlValue11P2 test2; +|}; + +type XSDSequenceRecordWithXmlValue11P record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue11_1 seq_XSDSequenceRecordWithXmlValue11_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue11_2 seq_XSDSequenceRecordWithXmlValue11_2; + record{record {int n;} n;} num2; +|}; + +type XSDSequenceRecordWithXmlValue11P2 record {| + record{record {int n;} n;} num; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue11_1 seq_XSDSequenceRecordWithXmlValue11_1; + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue11_2 seq_XSDSequenceRecordWithXmlValue11_2; + record{record {int n;} n;} num2; +|}; + +type Seq_XSDSequenceRecordWithXmlValue11_1 record {| + @SequenceOrder { + value: 1 + } + int age; + + @SequenceOrder { + value: 2 + } + float salary; +|}; + +type Seq_XSDSequenceRecordWithXmlValue11_2 record {| + @SequenceOrder { + value: 1 + } + Rec11 name; + + @SequenceOrder { + value: 2 + } + Rec11 status; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue11() returns error? { + xml xmlValue = xml `3SDABSuccessFail31311.123SDABSuccessFail31311.1`; + XSDSequenceRecordWithXmlValue11|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail3231311.1SDABSuccessFail3`); + + xmlValue = xml `3SDABSuccessFail31311.1SDABSuccessFail331311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail3231311.1SDABSuccessFail3`); + + xmlValue = xml `33SDABSuccessFail1311.13SDABSuccessFail31311.12`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {a: 2, test: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}, test2: {seq_XSDSequenceRecordWithXmlValue11_1: {age: 13, salary: 11.1}, num: {n: {n: 3}}, num2: {n: {n: 3}}, seq_XSDSequenceRecordWithXmlValue11_2: {name: {value1: "SD", value2: "AB"}, status: {value1: "Success", value2: "Fail"}}}}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.age, 13); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_1.salary, 11.1); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.name, {value1: "SD", value2: "AB"}); + test:assertEquals((check v2).test.seq_XSDSequenceRecordWithXmlValue11_2.status, {value1: "Success", value2: "Fail"}); + test:assertEquals((check v2).test.num, {n: {n: 3}}); + test:assertEquals((check v2).test.num2, {n: {n: 3}}); + test:assertEquals(toXml(check v2), xml `31311.1SDABSuccessFail3231311.1SDABSuccessFail3`); + + xmlValue = xml `3SDABSuccessFail31311.113SuccessFail11.1SDAB2`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'salary' is not found in 'seq_XSDSequenceRecordWithXmlValue11_1'"); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue12 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue12_1 seq_XSDSequenceRecordWithXmlValue12_1; +}; + +type Seq_XSDSequenceRecordWithXmlValue12_1 record { + @SequenceOrder {value: 1} + Seq_A field1; + + @SequenceOrder {value: 2} + Seq_B field2; + + @SequenceOrder {value: 3} + Seq_C field3; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue12() returns error? { + xml xmlValue = xml `123123123`; + XSDSequenceRecordWithXmlValue12|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue12_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {a: "1", b: "2", c: "3"}}, field3: {value3: {a: "1", b: "2", c: "3"}}}}); + test:assertEquals(toXml(check v2), xml `123123123`); +} + +@Name { + value: "Root" +} +type XSDSequenceRecordWithXmlValue13 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue13_1 seq_XSDSequenceRecordWithXmlValue13_1?; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithXmlValue13_2 seq_XSDSequenceRecordWithXmlValue13_2; +}; + +type Seq_XSDSequenceRecordWithXmlValue13_1 record { + @SequenceOrder {value: 1} + Seq_A_13 field1; + + @SequenceOrder {value: 2} + Seq_B_13 field2; + + @SequenceOrder {value: 3} + Seq_C_13 field3; +}; + +type Seq_XSDSequenceRecordWithXmlValue13_2 record { + @SequenceOrder {value: 1} + Seq_D_13 field4; + + @SequenceOrder {value: 2} + Seq_E_13 field5; + + @SequenceOrder {value: 3} + Seq_F_13 field6; +}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithXmlValue13() returns error? { + xml xmlValue = xml `123123123123123123`; + XSDSequenceRecordWithXmlValue13|Error v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue13_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {d: "1", e: "2", f: "3"}}, field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XSDSequenceRecordWithXmlValue13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}); + test:assertEquals(toXml(check v2), xml `123123123123123123`); + Error? e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is ()); + + xmlValue = xml `123123123`; + v2 = parseAsType(xmlValue); + test:assertEquals(v2, {seq_XSDSequenceRecordWithXmlValue13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}); + test:assertEquals(toXml(check v2), xml `123123123`); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is ()); + + xmlValue = xml `123123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'field6' is not found in 'seq_XSDSequenceRecordWithXmlValue13_2'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is Error); + test:assertEquals((e).message(), "Invalid XML found: 'Element(s) 'field6' is not found in 'seq_XSDSequenceRecordWithXmlValue13_2''"); + + xmlValue = xml `123123123123121233123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element(s) 'f' is not found in 'value2'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is Error); + test:assertEquals((e).message(), "Invalid XML found: 'Element(s) 'f' is not found in 'value2''"); + + xmlValue = xml `123123123123123132`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'i' is not in the correct order in 'value3'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is Error); + test:assertEquals((e).message(), "Invalid XML found: 'Element 'i' is not in the correct order in 'value3''"); + + xmlValue = xml `132123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), "Element 'c' is not in the correct order in 'value1'"); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is Error); + test:assertEquals((e).message(), "Invalid XML found: 'Element 'c' is not in the correct order in 'value1''"); + + xmlValue = xml `123123123123123123`; + v2 = parseAsType(xmlValue); + test:assertTrue(v2 is Error); + test:assertEquals((v2).message(), ("Element 'field5' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue13_2'"), msg = (v2).message()); + e = validate(xmlValue, XSDSequenceRecordWithXmlValue13); + test:assertTrue(e is Error); + test:assertEquals((e).message(), "Invalid XML found: 'Element 'field5' is not in the correct order in 'seq_XSDSequenceRecordWithXmlValue13_2''"); +} diff --git a/ballerina/tests/xsd_test_toxml_tests.bal b/ballerina/tests/xsd_test_toxml_tests.bal new file mode 100644 index 00000000..eb4f5b1f --- /dev/null +++ b/ballerina/tests/xsd_test_toxml_tests.bal @@ -0,0 +1,427 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@Name { + value: "A" +} +type ToXml1 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml seq_a; +}; + +type Seq_A_toXml record { + @SequenceOrder { + value: 3 + } + string c; + + @SequenceOrder { + value: 1 + } + string a; + + @SequenceOrder { + value: 2 + } + string b; +}; + +@Name { + value: "A" +} +type ToXml2 record { + Seq_A_toXml2 name; +}; + +type Seq_A_toXml2 record { + @SequenceOrder { + value: 3 + } + string c; + + @SequenceOrder { + value: 1 + } + string a; + + @SequenceOrder { + value: 2 + } + string b; +}; + +@test:Config {groups: ["xsd", "to_xml"]} +function testToXmlWithSimpleRecord2() { + ToXml2 a; + xml|Error xmlResult; + + a = {name: {b: "B", a: "A", c: "C"}}; + xmlResult = toXml(a); + test:assertEquals(xmlResult, xml `CAB`); +} + +@Name { + value: "A" +} +type ToXml3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3_2 name2; +}; + +type Seq_A_toXml3 record { + @SequenceOrder { + value: 3 + } + string c; + + @SequenceOrder { + value: 1 + } + string a; + + @SequenceOrder { + value: 3 + } + string b?; +}; + +type Seq_A_toXml3_2 record { + @SequenceOrder { + value: 3 + } + string c2; + + @SequenceOrder { + value: 1 + } + string a2; + + @SequenceOrder { + value: 3 + } + string b2; +}; + +@Name { + value: "A" +} +type ToXml4 record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3_2 name2; + + record{record{int n;} name;} name3; +}; + +@Name { + value: "A" +} +type ToXml5 record { + record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3_2 name2; + + record{record{int n;} name;} name3; + } a; + + string c; + + record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXml3_2 name2; + + record{record{int n;} name;} name3; + } b; +}; + +@Name { + value: "Root" +} +type ToXml6 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_XSDSequenceRecord13_1 seq_XSDSequenceRecord13_1?; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord13_2 seq_XSDSequenceRecord13_2; +}; + +@Name { + value: "A" +} +type ToXml7 record { + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Seq_A_toXml[] seq_a; +}; + +@test:Config {groups: ["xsd", "to_xml"], dataProvider: testToXmlWithXsdProvider} +function testToXmlWithXsd(typedesc recordType, record{} value, xml expected) returns error?{ + xml|Error xmlResult = toXml(check value.ensureType(recordType), {}); + test:assertEquals(xmlResult, expected); +} + +function testToXmlWithXsdProvider() returns [typedesc, record{}, xml][] { + return [[ + ToXml1, + {seq_a: {b: "B", a: "A", c: "C"}}, + xml `ABC` + ], + [ + ToXml2, + {name: {b: "B", a: "A", c: "C"}}, + xml `CAB` + ], + [ + ToXml3, + {name: {b: "B", a: "A", c: "C"}, name2: {b2: "B", a2: "A", c2: "C"}}, + xml `ABCABC` + ], + [ + ToXml4, + {name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}, n: 1}, + xml `1ABCABC1` + ], + [ + ToXml5, + {a: {n: 1, name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}}, b: {n: 1, name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}}, c: "A"}, + xml `1ABCABC1A1ABCABC1` + ], + [ + ToXml6, + {seq_XSDSequenceRecord13_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {d: "1", e: "2", f: "3"}}, field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XSDSequenceRecord13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}, + xml `123123123123123123` + ], + [ + ToXml7, + {seq_a: [{b: "B", a: "A", c: "C"}, {b: "B", a: "A", c: "C"}]}, + xml `ABCABC` + ] + ]; +} + +@Name { + value: "A" +} +type ToXmlChoice1 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_A_toXml choice_a; +}; + +type Choice_A_toXml record { + string c?; + string a?; + string b?; +}; + +@Name { + value: "A" +} +type ToXmlChoice2 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_A_toXml2 choice_a; + + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_A_toXml3 choice_b; +}; + +type Choice_A_toXml2 record { + @Element { + minOccurs: 2, + maxOccurs: 3 + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 5 + } + string b?; +}; + +type Choice_A_toXml3 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string b?; +}; + +@Name { + value: "A" +} +type ToXmlChoice4 record { + record { + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_A_toXml4 choice_a; + } nestedName; + + record { + @Choice { + minOccurs: 2, + maxOccurs: 2 + } + Choice_A_toXml4 choice_a; + } nestedName2; +}; + +type Choice_A_toXml4 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string b?; +}; + +type Choice_A_toXml5 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string a?; + + @Element { + minOccurs: 2, + maxOccurs: 3 + } + string b?; +}; + +@test:Config {groups: ["xsd", "to_xml"], dataProvider: testToXmlWithXsdProvider2} +function testToXmlWithXsd2(typedesc recordType, record{} value, xml expected) returns error?{ + xml|Error xmlResult = toXml(check value.ensureType(recordType), {}); + test:assertEquals(xmlResult, expected); +} + +function testToXmlWithXsdProvider2() returns [typedesc, record{}, xml][] { + return [[ + ToXmlChoice1, + {choice_a: {b: "B"}}, + xml `B` + ], + [ + ToXmlChoice2, + {choice_a: {c: "C"}, choice_b: {b: "B", a: "A", c: "C"}}, + xml `CCAB` + ], + [ + ToXmlChoice4, + {nestedName: {choice_a: {b: "B", a: "A"}}, nestedName2: {choice_a: {b: "B", a: "A"}}}, + xml `ABAB` + ] + ]; +} diff --git a/ballerina/tests/xsd_test_toxml_with_namespaces.bal b/ballerina/tests/xsd_test_toxml_with_namespaces.bal new file mode 100644 index 00000000..e0fbdcd9 --- /dev/null +++ b/ballerina/tests/xsd_test_toxml_with_namespaces.bal @@ -0,0 +1,606 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type EmptyRec record {}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces1 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + @Namespace { + uri: "http://example.com", + prefix: "ex" + } + Seq_A_toXmlWithNamespaces seq_a; +}; + +type Seq_A_toXmlWithNamespaces record { + @SequenceOrder { + value: 3 + } + @Namespace { + uri: "http://example2.com", + prefix: "ex2" + } + string c; + + @Namespace { + uri: "http://example2.com", + prefix: "ex2" + } + @SequenceOrder { + value: 1 + } + string a; + + @SequenceOrder { + value: 2 + } + @Namespace { + uri: "http://example2.com", + prefix: "ex2" + } + string b; +}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces2 record { + Seq_A_toXmlWithNamespaces2 name; +}; + +type Seq_A_toXmlWithNamespaces2 record { + + @Namespace { + uri: "http://example3.com", + prefix: "c" + } + @SequenceOrder { + value: 3 + } + string c; + + @SequenceOrder { + value: 1 + } + + @Namespace { + uri: "http://example1.com", + prefix: "a" + } + string a; + + @Namespace { + uri: "http://example3.com", + prefix: "b" + } + @SequenceOrder { + value: 2 + } + string b; +}; + +@test:Config {groups: ["xsd", "to_xml"]} +function testToXmlWithSimpleRecordWithNamespaces2() { + ToXmlWithNamespaces2 a; + xml|Error xmlResult; + + a = {name: {b: "B", a: "A", c: "C"}}; + xmlResult = toXml(a); + test:assertEquals(xmlResult, xml `CAB`); +} + +@Name { + value: "A" +} +type ToXmlWithNamespaces3 record { + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3_2 name2; +}; + +type Seq_A_toXmlWithNamespaces3 record { + @SequenceOrder { + value: 3 + } + @Namespace { + uri: "http://example3.com", + prefix: "c" + } + string c; + + @SequenceOrder { + value: 1 + } + string a; + + @SequenceOrder { + value: 3 + } + @Namespace { + uri: "http://example2.com", + prefix: "b" + } + string b?; +}; + +type Seq_A_toXmlWithNamespaces3_2 record { + @SequenceOrder { + value: 3 + } + string c2; + + @Namespace { + uri: "http://example3.com", + prefix: "a2" + } + @SequenceOrder { + value: 1 + } + string a2; + + @SequenceOrder { + value: 3 + } + string b2; +}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces4 record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3_2 name2; + + record{record{int n;} name;} name3; +}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces5 record { + record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3_2 name2; + + record{record{int n;} name;} name3; + } a; + + string c; + + record { + int n; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3 name; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_A_toXmlWithNamespaces3_2 name2; + + record{record{int n;} name;} name3; + } b; +}; + +@Name { + value: "Root" +} +type ToXmlWithNamespaces6 record { + @Sequence { + minOccurs: 0, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithNamespace13_1 seq_XSDSequenceRecord13_1?; + + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecordWithNamespace13_2 seq_XSDSequenceRecord13_2; +}; + +type Seq_XSDSequenceRecordWithNamespace13_1 record { + @SequenceOrder {value: 1} + @Namespace { + uri: "http://example1.com", + prefix: "field1" + } + Seq_A_13 field1; + + @SequenceOrder {value: 2} + @Namespace { + uri: "http://example2.com", + prefix: "field2" + } + Seq_B_13 field2; + + @Namespace { + uri: "http://example3.com", + prefix: "field3" + } + @SequenceOrder {value: 3} + Seq_C_13 field3; +}; + +type Seq_XSDSequenceRecordWithNamespace13_2 record { + @SequenceOrder {value: 1} + @Namespace { + uri: "http://example4.com", + prefix: "field4" + } + Seq_D_13 field4; + + @SequenceOrder {value: 2} + @Namespace { + uri: "http://example5.com" + } + Seq_E_13 field5; + + @Namespace { + uri: "http://example6.com", + prefix: "field6" + } + @SequenceOrder {value: 3} + Seq_F_13 field6; +}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces7 record { + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + @Namespace { + uri: "http://example.com", + prefix: "ex" + } + Seq_A_toXmlWithNamespaces[] seq_a; +}; + +@Name { + value: "A" +} +type ToXmlWithNamespaces8 record { + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + @Namespace { + uri: "http://example.com", + prefix: "ex" + } + Seq_A_toXmlWithNamespaces[] seq_a; + + @Sequence { + minOccurs: 1, + maxOccurs: 2 + } + @Namespace { + uri: "http://example2.com", + prefix: "ex2" + } + Seq_A_toXmlWithNamespaces[] seq_a2; +}; + +@test:Config {groups: ["xsd", "to_xml"], dataProvider: testToXmlWithXsdProviderWithNamespaces} +function testToXmlWithXsdWithNamespaces(typedesc recordType, record{} value, xml expected) returns error?{ + xml|Error xmlResult = toXml(check value.ensureType(recordType), {}); + test:assertEquals(xmlResult, expected); +} + +function testToXmlWithXsdProviderWithNamespaces() returns [typedesc, record{}, xml][] { + return [[ + ToXmlWithNamespaces1, + {seq_a: {b: "B", a: "A", c: "C"}}, + xml `ABC` + ], + [ + ToXmlWithNamespaces2, + {name: {b: "B", a: "A", c: "C"}}, + xml `CAB` + ], + [ + ToXmlWithNamespaces3, + {name: {b: "B", a: "A", c: "C"}, name2: {b2: "B", a2: "A", c2: "C"}}, + xml `ABCABC` + ], + [ + ToXmlWithNamespaces4, + {name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}, n: 1}, + xml `1ABCABC1` + ], + [ + ToXmlWithNamespaces5, + {a: {n: 1, name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}}, b: {n: 1, name: {b: "B", a: "A", c: "C"}, name3: {name: {n: 1}}, name2: {b2: "B", a2: "A", c2: "C"}}, c: "A"}, + xml `1ABCABC1A1ABCABC1` + ], + // TODO: Values are exact equals but assertion is failing. Need to check. + // [ + // ToXmlWithNamespaces6, + // {seq_XSDSequenceRecord13_1: {field1: {value1: {a: "1", b: "2", c: "3"}}, field2: {value2: {d: "1", e: "2", f: "3"}}, field3: {value3: {g: "1", h: "2", i: "3"}}}, seq_XSDSequenceRecord13_2: {field4: {value1: {a: "1", b: "2", c: "3"}}, field5: {value2: {d: "1", e: "2", f: "3"}}, field6: {value3: {g: "1", h: "2", i: "3"}}}}, + // xml `123123123123123123` + // ], + [ + EmptyRec, + {}, + xml `` + ], + [ + ToXmlWithNamespaces7, + {seq_a: [{b: "B", a: "A", c: "C"}, {b: "B", a: "A", c: "C"}]}, + xml `ABCABC` + ], + [ + ToXmlWithNamespaces8, + {seq_a: [{b: "B", a: "A", c: "C"}, {b: "B", a: "A", c: "C"}], seq_a2: [{b: "B", a: "A", c: "C"}, {b: "B", a: "A", c: "C"}]}, + xml `ABCABCABCABC` + ] + ]; +} + +@Name { + value: "A" +} +type ToXmlChoiceWithNamespace1 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_A_toXmlWithNamespace choice_a; +}; + +type Choice_A_toXmlWithNamespace record { + @Namespace { + uri: "http://examplec.com", + prefix: "c" + } + string c?; + + @Namespace { + uri: "http://examplea.com", + prefix: "a" + } + string a?; + + @Namespace { + uri: "http://exampleb.com", + prefix: "b" + } + string b?; +}; + +@Name { + value: "A" +} +type ToXmlChoiceWithNamespace2 record { + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + Choice_A_toXmlWithNamespace2 choice_a; + + @Choice { + minOccurs: 1, + maxOccurs: 3 + } + Choice_A_toXmlWithNamespace3 choice_b; +}; + +type Choice_A_toXmlWithNamespace2 record { + @Element { + minOccurs: 2, + maxOccurs: 3 + } + @Namespace { + uri: "http://examplea.com", + prefix: "c" + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 5 + } + @Namespace { + uri: "http://examplea.com", + prefix: "b" + } + string b?; +}; + +type Choice_A_toXmlWithNamespace3 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string c?; + + @Namespace { + uri: "http://examplea.com", + prefix: "a" + } + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + string b?; +}; + +@Name { + value: "A" +} +type ToXmlChoiceWithNamespace4 record { + record { + @Choice { + minOccurs: 2, + maxOccurs: 3 + } + Choice_A_toXmlWithNamespace4 choice_a; + } nestedName; + + record { + @Choice { + minOccurs: 2, + maxOccurs: 2 + } + Choice_A_toXmlWithNamespace3 choice_a; + } nestedName2; +}; + +type Choice_A_toXmlWithNamespace4 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + @Namespace { + uri: "http://examplec.com", + prefix: "c" + } + string c?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + @Namespace { + uri: "http://exampleb.com", + prefix: "a" + } + string a?; + + @Element { + minOccurs: 1, + maxOccurs: 1 + } + @Namespace { + uri: "http://exampleb.com", + prefix: "b" + } + string b?; +}; + +type Choice_A_toXmlWithNamespace5 record { + @Element { + minOccurs: 1, + maxOccurs: 1 + } + @Namespace { + uri: "http://examplea.com", + prefix: "c" + } + string c?; + + @Namespace { + uri: "http://examplea.com", + prefix: "a" + } + @Element { + minOccurs: 1, + maxOccurs: 2 + } + string a?; + + @Element { + minOccurs: 2, + maxOccurs: 3 + } + @Namespace { + uri: "http://examplea.com", + prefix: "b" + } + string b?; +}; + +@test:Config {groups: ["xsd", "to_xml"], dataProvider: testToXmlDataProviderWithNamespaces2} +function testToXmlWithXsdWithNamespace(typedesc recordType, record{} value, xml expected) returns error?{ + xml|Error xmlResult = toXml(check value.ensureType(recordType), {}); + test:assertEquals(xmlResult, expected); +} + +function testToXmlDataProviderWithNamespaces2() returns [typedesc, record{}, xml][] { + return [[ + ToXmlChoiceWithNamespace1, + {choice_a: {b: "B"}}, + xml `B` + ], + [ + ToXmlChoiceWithNamespace2, + {choice_a: {c: "C"}, choice_b: {b: "B", a: "A", c: "C"}}, + xml `CCAB` + ], + [ + ToXmlChoiceWithNamespace4, + {nestedName: {choice_a: {b: "B", a: "A"}}, nestedName2: {choice_a: {b: "B", a: "A"}}}, + xml `ABAB` + ] + ]; +} diff --git a/ballerina/tests/xsd_test_with_invalid_records.bal b/ballerina/tests/xsd_test_with_invalid_records.bal new file mode 100644 index 00000000..301222fd --- /dev/null +++ b/ballerina/tests/xsd_test_with_invalid_records.bal @@ -0,0 +1,97 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +type XSDSequenceInvalidRecord record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + int a; +|}; + +type XSDSequenceInvalidRecord2 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + int[] a; +|}; + +type StringArr string[][]; + +type XSDSequenceInvalidRecord3 record {| + @Sequence { + minOccurs: 1, + maxOccurs: 1 + } + StringArr a; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithInvalidRecord() returns error? { + string xmlStr = string `1311.1`; + XSDSequenceInvalidRecord|Error v = parseString(xmlStr); + test:assertEquals((v).message(), "Cannot include Sequence annotation into 'a' of type 'int'"); + + XSDSequenceInvalidRecord2|Error v2 = parseString(xmlStr); + test:assertEquals((v2).message(), "Cannot include Sequence annotation into 'a' of type 'int[]'"); + + XSDSequenceInvalidRecord3|Error v3 = parseString(xmlStr); + test:assertEquals((v3).message(), "Cannot include Sequence annotation into 'a' of type 'string[][]'"); +} + +type XSDChoiceInvalidRecord record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + int a; +|}; + +type XSDChoiceInvalidRecord2 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + int[] a; +|}; + +type XSDChoiceInvalidRecord3 record {| + @Choice { + minOccurs: 1, + maxOccurs: 1 + } + StringArr a; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceWithInvalidRecord() returns error? { + string xmlStr = string `1311.1`; + XSDChoiceInvalidRecord|Error v = parseString(xmlStr); + test:assertEquals((v).message(), "Cannot include Choice annotation into 'a' of type 'int'"); + + XSDChoiceInvalidRecord2|Error v2 = parseString(xmlStr); + test:assertEquals((v2).message(), "Cannot include Choice annotation into 'a' of type 'int[]'"); + + XSDChoiceInvalidRecord3|Error v3 = parseString(xmlStr); + test:assertEquals((v3).message(), "Cannot include Choice annotation into 'a' of type 'string[][]'"); + + xmlStr = string `1311.1`; + XSDChoiceInvalidRecord|Error v4 = parseString(xmlStr); + test:assertEquals((v4).message(), "Cannot include Choice annotation into 'a' of type 'int'"); +} diff --git a/ballerina/tests/xsd_validation_with_file_path_tests.bal b/ballerina/tests/xsd_validation_with_file_path_tests.bal new file mode 100644 index 00000000..fcee985b --- /dev/null +++ b/ballerina/tests/xsd_validation_with_file_path_tests.bal @@ -0,0 +1,107 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/io; + +function readXmlFile(string path) returns xml|error { + return io:fileReadXml(path); +} + +@test:Config {groups: ["xsd"]} +function testValidateSchema1() returns error? { + string xsdPath = "tests/resources/xsd_tests/schemas/schema_1.xsd"; + string validXmlPath = "tests/resources/xsd_tests/xml_values/schema_1_valid_xml.xml"; + string invalidXmlPath = "tests/resources/xsd_tests/xml_values/schema_1_invalid_xml.xml"; + + xml validXml = check readXmlFile(validXmlPath); + xml invalidXml = check readXmlFile(invalidXmlPath); + + Error? e = validate(validXml, xsdPath); + test:assertTrue(e is (), msg = "Valid XML should pass validation"); + + e = validate(invalidXml, xsdPath); + test:assertTrue(e is Error, msg = "Invalid XML should fail validation"); + test:assertTrue((e).message().includes("Invalid XML found"), msg = "Invalid XML should fail validation"); +} + +@test:Config {groups: ["xsd"]} +function testValidateSchema2() returns error? { + string xsdPath = "tests/resources/xsd_tests/schemas/schema_2.xsd"; + string validXmlPath = "tests/resources/xsd_tests/xml_values/schema_2_valid_xml.xml"; + string invalidXmlPath = "tests/resources/xsd_tests/xml_values/schema_2_invalid_xml.xml"; + + xml validXml = check readXmlFile(validXmlPath); + xml invalidXml = check readXmlFile(invalidXmlPath); + + Error? e = validate(validXml, xsdPath); + test:assertTrue(e is (), msg = "Valid XML should pass validation"); + + e = validate(invalidXml, xsdPath); + test:assertTrue(e is Error, msg = "Invalid XML should fail validation"); + test:assertTrue((e).message().includes("Invalid XML found"), msg = "Invalid XML should fail validation"); +} + +@test:Config {groups: ["xsd"]} +function testValidateSchema3() returns error? { + string xsdPath = "tests/resources/xsd_tests/schemas/schema_3.xsd"; + string validXmlPath = "tests/resources/xsd_tests/xml_values/schema_3_valid_xml.xml"; + string invalidXmlPath = "tests/resources/xsd_tests/xml_values/schema_3_invalid_xml.xml"; + + xml validXml = check readXmlFile(validXmlPath); + xml invalidXml = check readXmlFile(invalidXmlPath); + + Error? e = validate(validXml, xsdPath); + test:assertTrue(e is (), msg = "Valid XML should pass validation"); + + e = validate(invalidXml, xsdPath); + test:assertTrue(e is Error, msg = "Invalid XML should fail validation"); + test:assertTrue((e).message().includes("Invalid XML found"), msg = "Invalid XML should fail validation"); +} + +@test:Config {groups: ["xsd"]} +function testValidateSchema4() returns error? { + string xsdPath = "tests/resources/xsd_tests/schemas/schema_4.xsd"; + string validXmlPath = "tests/resources/xsd_tests/xml_values/schema_4_valid_xml.xml"; + string invalidXmlPath = "tests/resources/xsd_tests/xml_values/schema_4_invalid_xml.xml"; + + xml validXml = check readXmlFile(validXmlPath); + xml invalidXml = check readXmlFile(invalidXmlPath); + + Error? e = validate(validXml, xsdPath); + test:assertTrue(e is (), msg = "Valid XML should pass validation"); + + e = validate(invalidXml, xsdPath); + test:assertTrue(e is Error, msg = "Invalid XML should fail validation"); + test:assertTrue((e).message().includes("Invalid XML found"), msg = "Invalid XML should fail validation"); +} + +@test:Config {groups: ["xsd"]} +function testValidateSchema5() returns error? { + string xsdPath = "tests/resources/xsd_tests/schemas/schema_5.xsd"; + string validXmlPath = "tests/resources/xsd_tests/xml_values/schema_5_valid_xml.xml"; + string invalidXmlPath = "tests/resources/xsd_tests/xml_values/schema_5_invalid_xml.xml"; + + xml validXml = check readXmlFile(validXmlPath); + xml invalidXml = check readXmlFile(invalidXmlPath); + + Error? e = validate(validXml, xsdPath); + test:assertTrue(e is (), msg = "Valid XML should pass validation"); + + e = validate(invalidXml, xsdPath); + test:assertTrue(e is Error, msg = "Invalid XML should fail validation"); + test:assertTrue((e).message().includes("Invalid XML found"), msg = "Invalid XML should fail validation"); +} diff --git a/ballerina/xml_api.bal b/ballerina/xml_api.bal index da4a4a35..4491e9e8 100644 --- a/ballerina/xml_api.bal +++ b/ballerina/xml_api.bal @@ -22,6 +22,47 @@ const ATTRIBUTE_PREFIX = "attribute_"; const XMLNS = "xmlns"; const EMPTY_STRING = ""; +# Define the configurations for min and max occurrences of members in the XML schema (XSD). +public type ParticleOccurrence record {| + # Specifies the minimum number of occurrences. + int:Unsigned32 minOccurs?; + # Specifies the maximum number of occurrences. + int:Unsigned32 maxOccurs?; +|}; + +# Defines the configuration for an XML element in the XML schema (XSD). +public type ElementConfig record {| + *ParticleOccurrence; +|}; + +# Annotation to define schema rules for an XML element in Ballerina. +public const annotation ElementConfig Element on record field; + +# Defines the configuration for an XML sequence in the XML schema (XSD). +public type SequenceConfig record {| + *ParticleOccurrence; +|}; + +# Annotation to define schema rules for an XML sequence in Ballerina. +public const annotation SequenceConfig Sequence on record field; + +# Defines the configuration for an XML choice in the XML schema (XSD). +public type ChoiceConfig record {| + *ParticleOccurrence; +|}; + +# Annotation to define schema rules for an XML choice in Ballerina. +public const annotation ChoiceConfig Choice on record field; + +# Defines the configuration for the sequence order in the XML schema (XSD). +public type SequenceOrderConfig record {| + # The element order in the sequence + int value; +|}; + +# Annotation to define schema rules for the sequence order in Ballerina. +public const annotation SequenceOrderConfig SequenceOrder on record field; + # Defines the name of the XML element. public type NameConfig record {| # The name of the XML element @@ -135,9 +176,9 @@ public isolated function toXml(map mapValue, Options options = {}) retu return convertMapXml(jsonValue); } else if jsonValue is json[] { jsonOptions.rootTag = jsonValue[1].toString(); - return fromJson(jsonValue[0], jsonOptions); + return fromRecordToXml(jsonValue[0], jsonOptions, inputType); } - return fromJson(jsonValue.toJson(), jsonOptions); + return fromRecordToXml(jsonValue.toJson(), jsonOptions, inputType); } isolated function convertMapXml(map|map mapValue) returns xml { @@ -159,7 +200,7 @@ isolated function getModifiedRecord(map mapValue, string textFieldName, returns json|record {}|Error = @java:Method {'class: "io.ballerina.lib.data.xmldata.utils.DataUtils"} external; # Provides configurations for converting JSON to XML. -type JsonOptions record {| +public type JsonOptions record {| # The prefix of JSON elements' key which is to be treated as an attribute in the XML representation string attributePrefix = "@"; # The name of the XML elements that represent a converted JSON array entry @@ -184,8 +225,8 @@ public isolated function fromJson(json jsonValue, JsonOptions options = {}) retu if !isSingleNode(jsonValue) { addNamespaces(allNamespaces, check getNamespacesMap(jsonValue, options, {})); return getElement(rootTag ?: "root", - check traverseNode(jsonValue, allNamespaces, {}, options), allNamespaces, options, - check getAttributesMap(jsonValue, options, allNamespaces)); + check traverseNode(jsonValue, allNamespaces, {}, options), allNamespaces, options, + check getAttributesMap(jsonValue, options, allNamespaces)); } map|error jMap = jsonValue.ensureType(); @@ -198,8 +239,8 @@ public isolated function fromJson(json jsonValue, JsonOptions options = {}) retu addNamespaces(allNamespaces, check getNamespacesMap(value, options, {})); if value is json[] { return getElement(rootTag ?: "root", - check traverseNode(value, allNamespaces, {}, options, jMap.keys()[0]), - allNamespaces, options, check getAttributesMap(value, options, allNamespaces)); + check traverseNode(value, allNamespaces, {}, options, jMap.keys()[0]), + allNamespaces, options, check getAttributesMap(value, options, allNamespaces)); } string key = jMap.keys()[0]; @@ -207,8 +248,8 @@ public isolated function fromJson(json jsonValue, JsonOptions options = {}) retu return xml:createText(value.toString()); } xml output = check getElement(jMap.keys()[0], check traverseNode(value, allNamespaces, {}, options), - allNamespaces, options, - check getAttributesMap(value, options, allNamespaces)); + allNamespaces, options, + check getAttributesMap(value, options, allNamespaces)); if rootTag is string { return xml:createElement(rootTag, {}, output); } @@ -239,8 +280,8 @@ isolated function traverseNode(json jNode, map allNamespaces, map allNamespaces, map allNamespaces, map namespace allNamespaces['key] = namespace; } } + +# Validates an XML document against an XML schema. +# +# The schema can either be a file path to the `.xsd` file or a Ballerina record type that represents +# the schema defined by the XSD. The function checks if the XML value conforms to the specified schema. +# +# + schema - A string representing the file path to the `.xsd` file or a +# Ballerina record type representing the schema defined by the XSD. +# + xmlValue - The XML document that needs to be validated against the schema. +# + return - Returns `()` if the XML value is valid according to the schema, otherwise returns `Error`. +public function validate(xml xmlValue, string|typedesc schema) + returns Error? = @java:Method {'class: "io.ballerina.lib.data.xmldata.xml.Native"} external; + +isolated function fromRecordToXml(json jsonValue, JsonOptions options, typedesc inputType) returns xml|Error + = @java:Method {'class: "io.ballerina.lib.data.xmldata.utils.ToXmlUtils"} external; 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 ef12cf53..7e122408 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 @@ -27,6 +27,8 @@ import java.util.List; import java.util.stream.Collectors; +import static io.ballerina.lib.data.xmldata.compiler.CompilerPluginTestUtils.getErrorMessage; + /** * This class includes tests for Ballerina Xmldata compiler plugin. */ @@ -39,7 +41,7 @@ public void testDuplicateFieldNegative1() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 1); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid field: duplicate field found"); } @@ -51,7 +53,7 @@ public void testDuplicateFieldNegative2() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 1); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid field: duplicate field found"); } @@ -63,7 +65,7 @@ public void testChildRecordWithNameAnnotNegative() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.WARNING)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 1); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid annotation attachment: child record does not allow name annotation"); } @@ -75,13 +77,13 @@ public void testDuplicateFieldInInlineRecordsNegative() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 4); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 1), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 2), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 3), "invalid field: duplicate field found"); } @@ -93,17 +95,17 @@ public void testUnionTypeNegative() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 6); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid type: expected a record type"); - Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 1), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 2), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 3), "invalid type: expected a record type"); - Assert.assertEquals(errorDiagnosticsList.get(4).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 4), "invalid field: duplicate field found"); - Assert.assertEquals(errorDiagnosticsList.get(5).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 5), "invalid field: duplicate field found"); } @@ -115,7 +117,7 @@ public void testCompilerPluginWithAProjectWithSubModule() { .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) .collect(Collectors.toList()); Assert.assertEquals(errorDiagnosticsList.size(), 1); - Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(), + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), "invalid field: duplicate field found"); List warningDiagnosticsList = diagnosticResult.diagnostics().stream() @@ -125,4 +127,112 @@ public void testCompilerPluginWithAProjectWithSubModule() { Assert.assertEquals(warningDiagnosticsList.get(0).diagnosticInfo().messageFormat(), "invalid annotation attachment: child record does not allow name annotation"); } + + @Test + public void testCompilerPluginWithXsdAnnotation() { + 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(), 24); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 1), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 2), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 3), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 4), + "Invalid sequence member: Sequence members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 5), + "Invalid sequence member: Sequence members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 6), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 7), + "Invalid sequence member: Sequence members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 8), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 9), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 10), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 11), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 12), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 13), + "Invalid choice member: Choice members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 14), + "Invalid choice member: Choice members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 15), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 16), + "Invalid choice member: Choice members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 17), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 18), + "A record field cannot contains sequence/choice/element/attribute annotations simultaneously"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 19), + "A record field cannot contains sequence/choice/element/attribute annotations simultaneously"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 20), + "A record field cannot contains sequence/choice/element/attribute annotations simultaneously"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 21), + "A record field cannot contains sequence/choice/element/attribute annotations simultaneously"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 22), + "Invalid choice member: Choice members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 23), + "A record field cannot contains sequence/choice/element/attribute annotations simultaneously"); + } + + @Test + public void testCompilerPluginWithXsdAnnotation2() { + 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(), 6); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 1), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 2), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 3), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 4), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 5), + "invalid xsd annotation: record type or record array type expected"); + } + + @Test + public void testCompilerPluginWithXsdAnnotation3() { + DiagnosticResult diagnosticResult = + CompilerPluginTestUtils.loadPackage("sample_package_13").getCompilation().diagnosticResult(); + List errorDiagnosticsList = diagnosticResult.diagnostics().stream() + .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) + .collect(Collectors.toList()); + Assert.assertEquals(errorDiagnosticsList.size(), 9); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 0), + "Invalid sequence member: Order should be defined in in all fields"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 1), + "Invalid sequence member: Sequence members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 2), + "Invalid sequence member: Order should be defined in in all fields"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 3), + "Invalid sequence member: Sequence members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 4), + "Invalid sequence member: Order should be defined in in all fields"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 5), + "Invalid sequence member: Order should be defined in in all fields"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 6), + "Invalid choice member: Choice members should be defined in a closed record"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 7), + "invalid xsd annotation: record type or record array type expected"); + Assert.assertEquals(getErrorMessage(errorDiagnosticsList, 8), + "Invalid choice member: Choice members should be defined in a closed record"); + } } diff --git a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTestUtils.java b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTestUtils.java index b54f3a37..280959b2 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTestUtils.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/lib/data/xmldata/compiler/CompilerPluginTestUtils.java @@ -23,9 +23,11 @@ import io.ballerina.projects.directory.BuildProject; import io.ballerina.projects.environment.Environment; import io.ballerina.projects.environment.EnvironmentBuilder; +import io.ballerina.tools.diagnostics.Diagnostic; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; /** * Utility functions related to compiler plugins tests. @@ -43,4 +45,8 @@ static Package loadPackage(String path) { BuildProject project = BuildProject.load(projectEnvironmentBuilder, projectDirPath); return project.currentPackage(); } + + static String getErrorMessage(List errorDiagnosticsList, int index) { + return errorDiagnosticsList.get(index).diagnosticInfo().messageFormat(); + } } diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal index e29de78d..cd278e46 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_1/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/main.bal index 393821c9..fbbaae2f 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_10/main.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { 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 new file mode 100644 index 00000000..89e123d6 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "ballerina" +name = "sample_package_11" +version = "0.1.0" +distribution = "2201.10.0" + +[build-options] +observabilityIncluded = true 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 new file mode 100644 index 00000000..f449909c --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_11/main.bal @@ -0,0 +1,210 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/data.xmldata; + +type StringArr string[]; +type recordArr record{}[]; +type Str string; + +type A record { + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + int aSeq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + int[] a2Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + int|string[] a3Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + int|record{}[] a4Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + record{}[] a5Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + record{} a6Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + StringArr a7Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + recordArr a8Seq; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + Str a9Seq; +}; + +type A2 record { + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + int aChoice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + int[] a2Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + int|string[] a3Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + int|record{}[] a4Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + record{}[] a5Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + record{} a6Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + StringArr a7Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + recordArr a8Choice; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + Str a9Choice; +}; + +type A4 record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArray[] seq_XsdSequenceArray; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Element { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArray[] seq_XsdSequenceArray2; + + @xmldata:Element { + minOccurs: 1, + maxOccurs: 2 + } + Seq_XsdSequenceArray[] seq_XsdSequenceArray3; + + @xmldata:Element { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Attribute + Seq_XsdSequenceArray[] seq_XsdSequenceArray4; + + @xmldata:Element { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Attribute + Choice_XsdSequenceArray[] Choice_XsdSequenceArray5; + + @xmldata:Element { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 2 + } + @xmldata:Attribute + Choice_XsdSequenceArray2[] Choice_XsdSequenceArray6; +|}; + +type Seq_XsdSequenceArray record {| + @xmldata:SequenceOrder { + value: 1 + } + int age; + + @xmldata:SequenceOrder { + value: 2 + } + float salary; +|}; + +type Choice_XsdSequenceArray record { + int? age; +}; + +type Choice_XsdSequenceArray2 record {| + int? age; +|}; 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 new file mode 100644 index 00000000..8beac1d6 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "ballerina" +name = "sample_package_12" +version = "0.1.0" +distribution = "2201.10.0" + +[build-options] +observabilityIncluded = true diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/main.bal new file mode 100644 index 00000000..abd2c183 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_12/main.bal @@ -0,0 +1,80 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/data.xmldata; + +type XSDSequenceInvalidRecord record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + int a; +|}; + +type XSDSequenceInvalidRecord2 record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + int[] a; +|}; + +type StringArr string[][]; + +type XSDSequenceInvalidRecord3 record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + StringArr a; +|}; + +@test:Config {groups: ["xsd", "xsd_sequence"]} +function testXsdSequenceWithInvalidRecord() returns xmldata:Error? { + string xmlStr = string `1311.1`; + XSDSequenceInvalidRecord _ = check xmldata:parseString(xmlStr); +} + +type XSDChoiceInvalidRecord record {| + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + int a; +|}; + +type XSDChoiceInvalidRecord2 record {| + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + int[] a; +|}; + +type XSDChoiceInvalidRecord3 record {| + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + StringArr a; +|}; + +@test:Config {groups: ["xsd", "xsd_choice"]} +function testXsdChoiceWithInvalidRecord() returns xmldata:Error? { + string xmlStr = string `1311.1`; + XSDChoiceInvalidRecord _ = check xmldata:parseString(xmlStr); +} diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/Ballerina.toml new file mode 100644 index 00000000..e0acc36f --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "ballerina" +name = "sample_package_13" +version = "0.1.0" +distribution = "2201.10.0" + +[build-options] +observabilityIncluded = true diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/main.bal new file mode 100644 index 00000000..72fe8d8e --- /dev/null +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_13/main.bal @@ -0,0 +1,144 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/data.xmldata; + +type XSDSequenceRecord record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord seq_XSDSequenceRecord; +|}; + +type Seq_XSDSequenceRecord record {| + @xmldata:SequenceOrder { + value: 1 + } + int age; + + @xmldata:SequenceOrder { + value: 2 + } + float salary; +|}; + +type XSDSequenceRecord2 record {| + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord2 seq_XSDSequenceRecord2; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord3 seq_XSDSequenceRecord3; + + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord4 seq_XSDSequenceRecord4; +|}; + +type Seq_XSDSequenceRecord2 record {| + @xmldata:SequenceOrder { + value: 1 + } + int age; + + @xmldata:SequenceOrder { + value: 2 + } + float salary; + + @xmldata:Element { + minOccurs: 2 + } + string name; +|}; + +type Seq_XSDSequenceRecord3 record { + @xmldata:SequenceOrder { + value: 1 + } + int age; + string name; + + @xmldata:SequenceOrder { + value: 2 + } + float salary; +}; + +type XSDSequenceRecord4 record {| + record { + @xmldata:Sequence { + minOccurs: 1, + maxOccurs: 1 + } + Seq_XSDSequenceRecord4 seq_XSDSequenceRecord; + } nested; +|}; + +type Seq_XSDSequenceRecord4 record { + int age; + @xmldata:SequenceOrder { + value: 1 + } + int age2; + + @xmldata:Element { + minOccurs: 2 + } + @xmldata:SequenceOrder { + value: 2 + } + float salary; + + @xmldata:Element { + minOccurs: 2 + } + string address; +}; + +type ChoiceRecord record { + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + record {} age; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + int salary; + + @xmldata:Choice { + minOccurs: 1, + maxOccurs: 1 + } + ChoiceRec1 choiceRec1; +}; + +type ChoiceRec1 record { + record {} age?; + int salary?; + string name?; +}; diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal index 338506c4..ce750100 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_2/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal index 03694bef..59c4b527 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_3/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal index 2916c14d..555f233c 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_4/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal index 222828dd..030b713e 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_5/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal index 393821c9..fbbaae2f 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_6/sample.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; @xmldata:Name { diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal index 0ec56b86..8db31fc5 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; string xmlStr = string `12`; diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal index e58dd0d8..de3ecd2c 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; string xmlStr = string `12`; diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal index cb6dbaa7..1502b4a0 100644 --- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal +++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/data.xmldata; string xmlStr = string `12`; diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/Constants.java index b6c330e8..8f06b083 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/Constants.java @@ -6,6 +6,7 @@ * @since 0.1.0 */ public class Constants { + public static final String ELEMENT = "Element"; static final String PARSE_STRING = "parseString"; static final String PARSE_BYTES = "parseBytes"; static final String PARSE_STREAM = "parseStream"; @@ -17,4 +18,7 @@ public class Constants { static final String XMLDATA = "xmldata"; static final String BALLERINA = "ballerina"; static final String DATA_XMLDATA = "data.xmldata"; + static final String SEQUENCE = "Sequence"; + static final String CHOICE = "Choice"; + static final String ORDER = "SequenceOrder"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataDiagnosticCodes.java index 741bf382..1db0b60a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataDiagnosticCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/lib/data/xmldata/compiler/XmldataDiagnosticCodes.java @@ -36,7 +36,17 @@ public enum XmldataDiagnosticCodes { UNSUPPORTED_TYPE("XML_ERROR_203", "unsupported type: the record field does not support the expected type", ERROR), EXPECTED_RECORD_TYPE("XML_ERROR_204", "invalid type: expected a record type", ERROR), NAME_ANNOTATION_NOT_ALLOWED("XML_ERROR_205", - "invalid annotation attachment: child record does not allow name annotation", WARNING); + "invalid annotation attachment: child record does not allow name annotation", WARNING), + INVALID_XSD_MODEL_GROUP_ANNOTATION("XML_ERROR_206", + "invalid xsd annotation: record type or record array type expected", ERROR), + INVALID_SEQUENCE_TYPE("XML_ERROR_207", + "Invalid sequence member: Order should be defined in in all fields", ERROR), + INVALID_SEQUENCE_REST_TYPE("XML_ERROR_208", + "Invalid sequence member: Sequence members should be defined in a closed record", ERROR), + INVALID_CHOICE_REST_TYPE("XML_ERROR_209", + "Invalid choice member: Choice members should be defined in a closed record", ERROR), + INVALID_ANNOTATIONS("XML_ERROR_210", "A record field cannot contains " + + "sequence/choice/element/attribute annotations simultaneously", ERROR); private final String code; private final String message; 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 d0da173c..8cec2d4b 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 @@ -155,7 +155,10 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini private void validateAnnotationUsageInAllInlineExpectedTypes(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) { switch (typeSymbol.typeKind()) { - case RECORD -> validateRecordFieldNames((RecordTypeSymbol) typeSymbol, ctx); + case RECORD -> { + validateRecordFieldNames((RecordTypeSymbol) typeSymbol, ctx); + validateXsdModelGroupAnnotations((RecordTypeSymbol) typeSymbol, ctx); + } case UNION -> { for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) { validateAnnotationUsageInAllInlineExpectedTypes(memberTSymbol, ctx); @@ -174,6 +177,7 @@ private void validateExpectedType(TypeSymbol typeSymbol, Optional loca case RECORD -> { RecordTypeSymbol recordSymbol = (RecordTypeSymbol) typeSymbol; validateRecordFieldNames(recordSymbol, ctx); + validateXsdModelGroupAnnotations(recordSymbol, ctx); processRecordFieldsType(recordSymbol, ctx); } case TYPE_REFERENCE -> validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), @@ -264,6 +268,163 @@ private void validateRecordTypeDefinition(TypeDefinitionNode typeDefinitionNode, } TypeDefinitionSymbol typeDefinitionSymbol = (TypeDefinitionSymbol) symbol.get(); validateRecordFieldNames((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx); + validateXsdModelGroupAnnotations((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx); + } + + private void validateXsdModelGroupAnnotations(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { + for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { + RecordFieldSymbol fieldSymbol = entry.getValue(); + boolean isUniqueAnnotationHasValue = false; + for (AnnotationAttachmentSymbol annotationAttachmentSymbol : fieldSymbol.annotAttachments()) { + AnnotationSymbol annotationSymbol = annotationAttachmentSymbol.typeDescriptor(); + if (!isAnnotFromXmldata(annotationSymbol)) { + continue; + } + Optional annotName = annotationSymbol.getName(); + if (annotName.isEmpty()) { + continue; + } + String name = annotName.get(); + + if (isModelGroupAnnotation(name) || name.equals(Constants.ELEMENT) + || name.equals(Constants.ATTRIBUTE)) { + if (isUniqueAnnotationHasValue) { + reportDiagnosticInfo(ctx, fieldSymbol.getLocation(), + XmldataDiagnosticCodes.INVALID_ANNOTATIONS); + } else { + isUniqueAnnotationHasValue = true; + } + } + + if (isModelGroupAnnotation(name)) { + validateXsdModelGroupAnnotation(fieldSymbol.typeDescriptor(), fieldSymbol.getLocation(), ctx); + } + + if (name.equals(Constants.SEQUENCE)) { + validateSequenceAnnotation(fieldSymbol.typeDescriptor(), fieldSymbol.getLocation(), ctx); + } + + if (name.equals(Constants.CHOICE)) { + validateChoiceAnnotation(fieldSymbol.typeDescriptor(), fieldSymbol.getLocation(), ctx); + } + } + } + } + + private void validateChoiceAnnotation(TypeSymbol typeSymbol, + Optional location, SyntaxNodeAnalysisContext ctx) { + RecordTypeSymbol recordTypeSymbol = null; + if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + validateChoiceAnnotation(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), location, ctx); + return; + } + + if (typeSymbol.typeKind() == TypeDescKind.RECORD) { + recordTypeSymbol = (RecordTypeSymbol) typeSymbol; + } + + if (typeSymbol.typeKind() == TypeDescKind.ARRAY) { + TypeSymbol memberTypeSymbol = ((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(); + if (memberTypeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + memberTypeSymbol = ((TypeReferenceTypeSymbol) memberTypeSymbol).typeDescriptor(); + } + if (memberTypeSymbol.typeKind() == TypeDescKind.RECORD) { + recordTypeSymbol = (RecordTypeSymbol) memberTypeSymbol; + } + } + + if (recordTypeSymbol != null) { + Optional loctaion = recordTypeSymbol.getLocation(); + recordTypeSymbol.restTypeDescriptor().ifPresent(restTypeSymbol -> { + reportDiagnosticInfo(ctx, loctaion, XmldataDiagnosticCodes.INVALID_CHOICE_REST_TYPE); + }); + } + } + + private void validateSequenceAnnotation(TypeSymbol typeSymbol, + Optional location, SyntaxNodeAnalysisContext ctx) { + RecordTypeSymbol recordTypeSymbol = null; + if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + validateSequenceAnnotation(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), location, ctx); + return; + } + + if (typeSymbol.typeKind() == TypeDescKind.RECORD) { + recordTypeSymbol = (RecordTypeSymbol) typeSymbol; + } + + if (typeSymbol.typeKind() == TypeDescKind.ARRAY) { + TypeSymbol memberTypeSymbol = ((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(); + if (memberTypeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + memberTypeSymbol = ((TypeReferenceTypeSymbol) memberTypeSymbol).typeDescriptor(); + } + if (memberTypeSymbol.typeKind() == TypeDescKind.RECORD) { + recordTypeSymbol = (RecordTypeSymbol) memberTypeSymbol; + } + } + + if (recordTypeSymbol != null) { + Optional loctaion = recordTypeSymbol.getLocation(); + recordTypeSymbol.restTypeDescriptor().ifPresent(restTypeSymbol -> { + reportDiagnosticInfo(ctx, loctaion, XmldataDiagnosticCodes.INVALID_SEQUENCE_REST_TYPE); + }); + + for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) { + RecordFieldSymbol fieldSymbol = entry.getValue(); + if (fieldSymbol.annotAttachments().isEmpty()) { + reportDiagnosticInfo(ctx, + fieldSymbol.getLocation(), XmldataDiagnosticCodes.INVALID_SEQUENCE_TYPE); + } + boolean isOrderAnnotationFound = false; + for (AnnotationAttachmentSymbol annotSymbol : fieldSymbol.annotAttachments()) { + AnnotationSymbol annotationSymbol = annotSymbol.typeDescriptor(); + if (!isAnnotFromXmldata(annotationSymbol)) { + continue; + } + Optional annotName = annotationSymbol.getName(); + if (annotName.isEmpty()) { + continue; + } + String name = annotName.get(); + if (name.equals(Constants.ORDER)) { + isOrderAnnotationFound = true; + } + } + + if (!isOrderAnnotationFound) { + reportDiagnosticInfo(ctx, fieldSymbol + .getLocation(), XmldataDiagnosticCodes.INVALID_SEQUENCE_TYPE); + } + } + } + } + + private boolean isModelGroupAnnotation(String annotationName) { + return annotationName.equals(Constants.SEQUENCE) || annotationName.equals(Constants.CHOICE); + } + + private void validateXsdModelGroupAnnotation(TypeSymbol typeSymbol, Optional location, + SyntaxNodeAnalysisContext ctx) { + if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + validateXsdModelGroupAnnotation(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(), location, ctx); + return; + } + + if (typeSymbol.typeKind() == TypeDescKind.RECORD) { + return; + } + + if (typeSymbol.typeKind() == TypeDescKind.ARRAY) { + TypeSymbol memberTypeSymbol = ((ArrayTypeSymbol) typeSymbol).memberTypeDescriptor(); + if (memberTypeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) { + memberTypeSymbol = ((TypeReferenceTypeSymbol) memberTypeSymbol).typeDescriptor(); + } + if (memberTypeSymbol.typeKind() == TypeDescKind.RECORD) { + return; + } + } + + reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.INVALID_XSD_MODEL_GROUP_ANNOTATION); } private void validateRecordFieldNames(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) { diff --git a/gradle.properties b/gradle.properties index 966f4743..ad95f12a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ researchgateReleaseVersion=2.8.0 ballerinaGradlePluginVersion=2.0.1 stdlibIoVersion=1.7.0-20241121-173300-0fbd5d4 -stdlibTimeVersion=2.6.0-20241121-152300-eb1feff -stdlibConstraintVersion=1.6.0-20241121-170800-002e6d6 +stdlibTimeVersion=2.6.0-20241122-120400-97742a7 +stdlibConstraintVersion=1.6.0-20241122-133100-98689e2 diff --git a/native/build.gradle b/native/build.gradle index 8398a3a2..a65edca9 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -33,6 +33,8 @@ dependencies { implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'value', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'xml', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'map', version: "${ballerinaLangVersion}" implementation group: 'io.ballerina.stdlib', name: 'constraint-native', version: "${stdlibConstraintVersion}" } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java index 146accc8..299ba33a 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java @@ -39,6 +39,9 @@ private Constants() {} public static final int DEFAULT_TYPE_FLAG = 2049; public static final String FIELD = "$field$."; + public static final String ROOT_TAG = "rootTag"; + public static final String ROOT = "root"; + public static final String EMPTY_STRING = ""; public static final String NAMESPACE = "Namespace"; public static final String UNDERSCORE = "_"; public static final String COLON = ":"; @@ -59,11 +62,20 @@ private Constants() {} public static final BString PREFIX = StringUtils.fromString("prefix"); public static final BString VALUE = StringUtils.fromString("value"); public static final BString ATTRIBUTE_PREFIX = StringUtils.fromString("attributePrefix"); + public static final BString USER_ATTRIBUTE_PREFIX = StringUtils.fromString("userAttributePrefix"); public static final BString TEXT_FIELD_NAME = StringUtils.fromString("textFieldName"); + public static final BString ARRAY_ENTRY_TAG = StringUtils.fromString("arrayEntryTag"); public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); public static final BString USE_SEMANTIC_EQUALITY = StringUtils.fromString("useSemanticEquality"); public static final BString ENABLE_CONSTRAINT_VALIDATION = StringUtils.fromString("enableConstraintValidation"); public static final MapType JSON_MAP_TYPE = TypeCreator.createMapType(PredefinedTypes.TYPE_JSON); public static final ArrayType JSON_ARRAY_TYPE = TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON); + + public static final String ELEMENT = "Element"; + public static final String SEQUENCE = "Sequence"; + public static final String CHOICE = "Choice"; + public static final BString MIN_OCCURS = StringUtils.fromString("minOccurs"); + public static final BString MAX_OCCURS = StringUtils.fromString("maxOccurs"); + public static final String ORDER = "SequenceOrder"; } 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 78021ebb..5615006e 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 @@ -23,6 +23,10 @@ import io.ballerina.lib.data.xmldata.xml.QualifiedNameFactory; import io.ballerina.lib.data.xmldata.xml.QualifiedNameMap; import io.ballerina.lib.data.xmldata.xml.QualifiedNameSemantic; +import io.ballerina.lib.data.xmldata.xml.xsd.ChoiceInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ElementInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ModelGroupInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.SequenceInfo; import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; @@ -49,15 +53,20 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.Stack; import javax.xml.namespace.QName; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.popXsdValidationStacks; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.ATTRIBUTE; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.ELEMENT; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.NOT_DEFINED; @@ -220,7 +229,7 @@ public static QualifiedName getFieldNameFromRecord(Map fieldAnn } @SuppressWarnings("unchecked") - private static String getModifiedName(Map fieldAnnotation, String attributeName) { + static String getModifiedName(Map fieldAnnotation, String attributeName) { for (BString key : fieldAnnotation.keySet()) { if (isNameAnnotationKey(key.getValue())) { return ((Map) fieldAnnotation.get(key)).get(Constants.VALUE).toString(); @@ -345,6 +354,8 @@ public static void updateExpectedTypeStacks(RecordType recordType, XmlAnalyzerDa analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(getAllFieldsInRecordType(recordType, analyzerData))); analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.restTypes.push(recordType.getRestFieldType()); + analyzerData.xsdModelGroupInfo.push(new HashMap<>()); + analyzerData.xmlElementInfo.push(new HashMap<>()); } public static void popExpectedTypeStacks(XmlAnalyzerData analyzerData) { @@ -353,6 +364,7 @@ public static void popExpectedTypeStacks(XmlAnalyzerData analyzerData) { analyzerData.restTypes.pop(); analyzerData.attributeHierarchy.pop(); analyzerData.arrayIndexes.pop(); + popXsdValidationStacks(analyzerData); } public static boolean isAnydataOrJson(int typeTag) { @@ -510,6 +522,37 @@ private static BMap addFields(BMap input, Type return recordValue; } + static BString[] getOrderedRecordKeysIfXsdSequencePresent(BMap input, + HashMap xsdSequencePriorityOrder) { + HashMap localPartKeys = getLocalPartKeys(input); + if (xsdSequencePriorityOrder.isEmpty()) { + return input.getKeys(); + } else { + return xsdSequencePriorityOrder.entrySet().stream() + .filter(entry -> localPartKeys.containsKey(entry.getKey())) + .sorted(Comparator.comparingInt(Map.Entry::getValue)) + .map(entry -> StringUtils.fromString(localPartKeys.get(entry.getKey()))) + .toArray(BString[]::new); + } + } + + private static HashMap getLocalPartKeys(BMap input) { + HashMap localPartKeys = new HashMap<>(); + for (Map.Entry entry : input.entrySet()) { + String k = entry.getKey().getValue(); + if (k.contains(ATTRIBUTE_PREFIX)) { + continue; + } + int i = k.indexOf(Constants.COLON); + if (i != -1) { + localPartKeys.put(k.substring(i + 1), k); + } else { + localPartKeys.put(k, k); + } + } + return localPartKeys; + } + private static void processRecordField(Type fieldType, BMap annotations, BMap recordValue, Map.Entry entry, String key, Object value) { @@ -537,11 +580,11 @@ private static void processTypeReferenceType(Type fieldType, BMap recordValue, String key, Object value) { BMap namespaceAnnotRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); boolean doesNamespaceDefinedInField = false; - if (annotations.size() > 0) { + if (!annotations.isEmpty()) { String fieldName = key; key = getKeyNameFromAnnotation(annotations, key); QName qName = addFieldNamespaceAnnotation(fieldName, key, annotations, namespaceAnnotRecord); - if (!qName.getNamespaceURI().equals("")) { + if (!qName.getNamespaceURI().isEmpty()) { doesNamespaceDefinedInField = true; } String localPart = qName.getLocalPart(); @@ -558,7 +601,7 @@ private static void processTypeReferenceType(Type fieldType, BMap subRecordValue = addFields(((BMap) value), referredType); addNamespaceToSubRecord(key, namespaceAnnotRecord, subRecordValue); - if (annotationRecord.size() > 0) { + if (!annotationRecord.isEmpty()) { subRecordValue.put(annotationRecord.getKeys()[0], annotationRecord.get(annotationRecord.getKeys()[0])); } recordValue.put(StringUtils.fromString(key), subRecordValue); @@ -641,17 +684,19 @@ private static void processRecord(String key, BMap parentAnnota BMap record, Object value, RecordType childType) { BMap parentRecordAnnotations = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); BMap annotation = childType.getAnnotations(); - if (parentAnnotations.size() > 0) { + if (!parentAnnotations.isEmpty()) { annotation.merge(getFieldNamespaceAndNameAnnotations(key, parentAnnotations), true); processSubRecordAnnotation(parentAnnotations, parentRecordAnnotations); } BMap subRecord = addFields(((BMap) value), childType); - if (annotation.size() > 0) { + if (!annotation.isEmpty()) { processSubRecordAnnotation(annotation, subRecord); } + key = getElementName(annotation, key); record.put(StringUtils.fromString(key), subRecord); - if (parentRecordAnnotations.size() > 0) { + + if (!parentRecordAnnotations.isEmpty()) { record.put(parentRecordAnnotations.getKeys()[0], parentRecordAnnotations.get(parentRecordAnnotations.getKeys()[0])); } @@ -694,8 +739,7 @@ private static void processArray(Type elementType, BMap annotat if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { List> records = new ArrayList<>(); for (int i = 0; i < arrayValue.getLength(); i++) { - BMap subRecord = addFields(((BMap) arrayValue.get(i)), - elementType); + BMap subRecord = addFields(((BMap) arrayValue.get(i)), elementType); subRecord = processParentAnnotation(elementType, subRecord); records.add((BMap) subRecord.get(subRecord.getKeys()[0])); } @@ -711,7 +755,7 @@ private static void processArray(Type elementType, BMap annotat record.put(StringUtils.fromString(keyName), ValueCreator.createArrayValue(records.toArray(), TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); } - if (annotationRecord.size() > 0) { + if (!annotationRecord.isEmpty()) { record.put(annotationRecord.getKeys()[0], annotationRecord.get(annotationRecord.getKeys()[0])); } @@ -745,7 +789,7 @@ private static BMap processParentAnnotation(Type type, BMap namespaces = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); BMap annotations = ((RecordType) type).getAnnotations(); BString rootName = processAnnotation(annotations, type.getName(), namespaces); - if (namespaces.size() > 0) { + if (!namespaces.isEmpty()) { for (Map.Entry namespace : namespaces.entrySet()) { record.put(namespace.getKey(), namespace.getValue()); } @@ -905,7 +949,7 @@ private static boolean isNamespaceAnnotationKey(String key) { return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.NAMESPACE); } - private static boolean isNameAnnotationKey(String key) { + public static boolean isNameAnnotationKey(String key) { return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.NAME); } @@ -1013,26 +1057,280 @@ public static boolean isContainsUnionType(Type expType) { return false; } + + public static Collection getXmlElementNames(RecordType fieldType) { + HashSet elementNames = new HashSet<>(fieldType.getFields().keySet()); + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (key.contains(Constants.FIELD)) { + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + updateFieldSetWithName(fieldAnnotation, elementNames, fieldAnnotationKey, fieldName); + } + } + } + return elementNames; + } + + private static void updateFieldSetWithName(Map fieldAnnotation, Set elementNames, + BString fieldAnnotationKey, String fieldName) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + if (fieldAnnotationKeyStr.endsWith(Constants.NAME)) { + BMap fieldAnnotationValue = + (BMap) fieldAnnotation.get(fieldAnnotationKey); + String xmlElementName = StringUtils.getStringValue(fieldAnnotationValue + .getStringValue(Constants.VALUE)); + elementNames.remove(fieldName); + elementNames.add(xmlElementName); + } + } + } + + public static HashMap getXmlElementNameMap(RecordType fieldType) { + HashMap elementMap = new HashMap<>(); + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (key.contains(Constants.FIELD)) { + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + getXmlElementNameFromFieldAnnotation(fieldAnnotation, fieldAnnotationKey, fieldName, elementMap); + } + } + } + return elementMap; + } + + private static void getXmlElementNameFromFieldAnnotation(Map fieldAnnotation, + BString fieldAnnotationKey, String fieldName, + HashMap xmlElementNameMap) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + if (fieldAnnotationKeyStr.endsWith(Constants.NAME)) { + BMap fieldAnnotationValue = + (BMap) fieldAnnotation.get(fieldAnnotationKey); + String xmlElementName = StringUtils.getStringValue(fieldAnnotationValue + .getStringValue(Constants.VALUE)); + xmlElementNameMap.remove(fieldName); + xmlElementNameMap.put(xmlElementName, fieldName); + } + } + } + + public static void popMappingTypeStacks(XmlAnalyzerMetaData xmlParserData) { + xmlParserData.fieldHierarchy.pop(); + xmlParserData.visitedFieldHierarchy.pop(); + xmlParserData.restTypes.pop(); + } + + public static HashMap getXsdSequencePriorityOrder(RecordType fieldType) { + return getXsdSequencePriorityOrder(fieldType, false); + } + + public static HashMap getXsdSequencePriorityOrder(Type type, boolean isSequenceElements) { + if (type.getTag() != TypeTags.RECORD_TYPE_TAG) { + return new HashMap<>(); + } + + RecordType fieldType = (RecordType) type; + HashMap elementPriorityOrder = new HashMap<>(); + if (!isSequenceElements) { + return elementPriorityOrder; + } + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (!key.contains(Constants.FIELD)) { + continue; + } + + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (!fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + continue; + } + if (fieldAnnotationKeyStr.endsWith(Constants.ORDER)) { + BMap fieldAnnotationValue = + (BMap) fieldAnnotation.get(fieldAnnotationKey); + elementPriorityOrder.put(fieldName, + fieldAnnotationValue.getIntValue(Constants.VALUE).intValue()); + } + } + } + return elementPriorityOrder; + } + + public static HashMap getFieldNamesWithModelGroupAnnotations(RecordType fieldType, + HashMap elementNamesMap) { + HashMap fieldNamesWithModelGroupAnnotations = new HashMap<>(); + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (!key.contains(Constants.FIELD)) { + continue; + } + + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + if (elementNamesMap.containsKey(fieldName)) { + fieldName = elementNamesMap.get(fieldName); + } + + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (!fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + continue; + } + if (fieldAnnotationKeyStr.endsWith(Constants.SEQUENCE)) { + SequenceInfo sequenceInfo = new SequenceInfo(fieldName, + (BMap) fieldAnnotation.get(fieldAnnotationKey), + fieldType, null); + fieldNamesWithModelGroupAnnotations.put(fieldName, sequenceInfo); + } + + if (fieldAnnotationKeyStr.endsWith(Constants.CHOICE)) { + ChoiceInfo choiceInfo = new ChoiceInfo(fieldName, + (BMap) fieldAnnotation.get(fieldAnnotationKey), + fieldType, null); + fieldNamesWithModelGroupAnnotations.put(fieldName, choiceInfo); + } + } + } + return fieldNamesWithModelGroupAnnotations; + } + + public static HashMap getFieldNamesWithElementGroupAnnotations( + RecordType fieldType, HashMap elementNamesMap, String xmlElementName) { + HashMap fieldNamesWithElementInfoAnnotations = new HashMap<>(); + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (!key.contains(Constants.FIELD)) { + continue; + } + + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + if (elementNamesMap.containsKey(fieldName)) { + fieldName = elementNamesMap.get(fieldName); + } + + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (!fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + continue; + } + + if (fieldAnnotationKeyStr.endsWith(Constants.ELEMENT)) { + ElementInfo elementInfo = new ElementInfo(xmlElementName, fieldName, + (BMap) fieldAnnotation.get(fieldAnnotationKey)); + fieldNamesWithElementInfoAnnotations.put(fieldName, elementInfo); + } + } + } + return fieldNamesWithElementInfoAnnotations; + } + + + public static ArrayList getFieldNamesWithSequenceAnnotations(RecordType fieldType, + HashMap elementNamesMap) { + ArrayList fieldNamesWithModelGroupAnnotations = new ArrayList<>(); + BMap annotations = fieldType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (!key.contains(Constants.FIELD)) { + continue; + } + + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (!fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + continue; + } + + if (fieldAnnotationKeyStr.endsWith(Constants.SEQUENCE)) { + if (elementNamesMap.containsKey(fieldName)) { + fieldNamesWithModelGroupAnnotations.add(elementNamesMap.get(fieldName)); + } else { + fieldNamesWithModelGroupAnnotations.add(fieldName); + } + } + } + } + return fieldNamesWithModelGroupAnnotations; + } + + public static HashMap getElementNameMap(Type type) { + HashMap names = new HashMap<>(); + if (type instanceof RecordType recordType) { + BMap annotations = recordType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (!key.contains(Constants.FIELD)) { + continue; + } + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + for (BString fieldAnnotationKey : fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (!fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + continue; + } + + if (fieldAnnotationKeyStr.endsWith(Constants.NAME)) { + BMap fieldAnnotationValue = + (BMap) fieldAnnotation.get(fieldAnnotationKey); + String xmlElementName = StringUtils.getStringValue(fieldAnnotationValue + .getStringValue(Constants.VALUE)); + names.put(xmlElementName, fieldName); + } + } + } + return names; + } + return names; + } + /** - * Holds data required for the traversing. + * Holds data required for the processing. * - * @since 0.1.0 + * @since 1.1.0 */ - public static class XmlAnalyzerData { + public static class XmlAnalyzerMetaData { + public Stack> attributeHierarchy = new Stack<>(); + public Stack> arrayIndexes = new Stack<>(); + public Stack> xmlElementInfo = new Stack<>(); + public Stack> xsdModelGroupInfo = new Stack<>(); + public Stack modelGroupStack = 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; - public String attributePrefix; - public String textFieldName; public boolean allowDataProjection; public boolean useSemanticEquality; + public String attributePrefix; + public String textFieldName; + public BMap currentNode; + public final Stack recordTypeStack = new Stack<>(); + } + /** + * Holds data required for the traversing. + * + * @since 0.1.0 + */ + public static class XmlAnalyzerData extends XmlAnalyzerMetaData { @SuppressWarnings("unchecked") public static XmlAnalyzerData copy(XmlAnalyzerData analyzerData) { XmlAnalyzerData data = new XmlAnalyzerData(); @@ -1044,11 +1342,13 @@ public static XmlAnalyzerData copy(XmlAnalyzerData analyzerData) { 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; + data.xmlElementInfo = (Stack>) analyzerData.xmlElementInfo.clone(); + data.xsdModelGroupInfo = (Stack>) analyzerData.xsdModelGroupInfo.clone(); + data.modelGroupStack = (Stack) analyzerData.modelGroupStack.clone(); return data; } @@ -1062,11 +1362,24 @@ public void resetFrom(XmlAnalyzerData analyzerData) { 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; + this.xmlElementInfo = analyzerData.xmlElementInfo; + this.xsdModelGroupInfo = analyzerData.xsdModelGroupInfo; + this.modelGroupStack = analyzerData.modelGroupStack; } } + + /** + * Holds data required for the parsing. + * + * @since 0.1.1 + */ + public static class XmlParserData extends XmlAnalyzerMetaData { + public final Stack restFieldsPoints = new Stack<>(); + public final Stack> parents = new Stack<>(); + public QualifiedNameMap siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); + } } 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 7f093046..21fa9fd7 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 @@ -43,7 +43,18 @@ public enum DiagnosticErrorCode { XML_PARSE_ERROR("XML_ERROR_016", "xml.parse.error"), 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"); + FIELD_CANNOT_CAST_INTO_TYPE("XML_ERROR_0019", "field.cannot.convert.into.type"), + ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES("XML_ERROR_0020", "element.occurs.more.than.max.allowed.times"), + ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES("XML_ERROR_0021", "element.occurs.less.than.min.required.times"), + INVALID_ELEMENT_FOUND("XML_ERROR_0022", "invalid.element.found"), + REQUIRED_ELEMENT_NOT_FOUND("XML_ERROR_0023", "required.element.not.found"), + ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES_IN_SEQUENCES( + "XML_ERROR_0024", "element.occurs.more.than.max.allowed.times.in.sequence"), + INCORRECT_ELEMENT_ORDER("XML_ERROR_0025", "incorrect.element.order"), + INVALID_SEQUENCE_ANNOTATION("XML_ERROR_0026", "invalid.sequence.annotation"), + INVALID_CHOICE_ANNOTATION("XML_ERROR_0027", "invalid.choice.annotation"), + INVALID_XSD_ANNOTATION("XML_ERROR_0028", "invalid.xsd.annotation"), + INVALID_XML("XML_ERROR_0029", "invalid.xml"); String diagnosticId; String messageKey; diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java new file mode 100644 index 00000000..541a23d2 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.utils; + +import io.ballerina.lib.data.xmldata.xml.xsd.ChoiceInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ElementInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ModelGroupInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.SequenceInfo; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.PredefinedTypes; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.utils.ValueUtils; +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 org.ballerinalang.langlib.map.ToArray; +import org.ballerinalang.langlib.xml.Concat; +import org.ballerinalang.langlib.xml.CreateElement; +import org.ballerinalang.langlib.xml.CreateText; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A util class for the record to xml implementation. + * + * @since 1.1.0 + */ +public class ToXmlUtils { + private static final BString XMLNS_NAMESPACE_URI = StringUtils.fromString("http://www.w3.org/2000/xmlns/"); + private static final BString ATTRIBUTE_PREFIX = StringUtils.fromString("attribute_"); + private static final BString XMLNS = StringUtils.fromString("xmlns"); + + public static Object fromRecordToXml(Object jsonValue, BMap options, BTypedesc typed) { + try { + Type type = typed.getDescribingType(); + Type referredType = TypeUtils.getReferredType(type); + Object rootTag = options.get(StringUtils.fromString(Constants.ROOT_TAG)); + BMap allNamespaces = getEmptyStringMap(); + BString rootTagBstring = + StringUtils.fromString(rootTag == null ? Constants.EMPTY_STRING : rootTag.toString()); + + if (!isSingleRecordMember(jsonValue)) { + addNamespaces(allNamespaces, getNamespacesMap(jsonValue, options, getEmptyStringMap())); + return getElementFromRecordMember( + rootTag == null ? StringUtils.fromString(Constants.ROOT) : rootTagBstring, + traverseRecordAndGenerateXml(jsonValue, allNamespaces, + getEmptyStringMap(), options, null, referredType, + false, false, null, null), + allNamespaces, options, + getAttributesMap(jsonValue, options, allNamespaces, getEmptyStringMap())); + } + + BMap jMap = null; + try { + jMap = (BMap) ValueUtils + .convert(jsonValue, TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); + } catch (BError e) { + return jsonValue == null ? ValueCreator.createXmlValue(Constants.EMPTY_STRING) + : CreateText.createText(StringUtils.fromString(jsonValue.toString())); + } + + if (jMap.isEmpty()) { + return ValueCreator.createXmlValue(Constants.EMPTY_STRING); + } + + BString key = jMap.getKeys()[0]; + String keyStr = key.getValue(); + HashMap elementNamesMap = DataUtils.getElementNameMap(referredType); + ArrayList sequenceFieldNames = getSequenceFieldNames(referredType, elementNamesMap); + HashMap modelGroupRelatedFieldNames = + getModelGroupRelatedFieldNames(referredType, elementNamesMap); + HashMap elementInfoRelatedFieldNames = + getElementInfoRelatedFieldNames(referredType, elementNamesMap); + String localJsonKeyPart = keyStr.contains(Constants.COLON) + ? keyStr.substring(keyStr.indexOf(Constants.COLON) + 1) : keyStr; + String recordKey = elementNamesMap.getOrDefault(localJsonKeyPart, localJsonKeyPart); + boolean isSequenceField = sequenceFieldNames.contains(recordKey); + boolean isContainsModelGroup = modelGroupRelatedFieldNames.containsKey(recordKey); + ModelGroupInfo parentModelGroupInfo = modelGroupRelatedFieldNames.get(recordKey); + ElementInfo elementInfo = elementInfoRelatedFieldNames.get(recordKey); + + Object value = ToArray.toArray(jMap).getValues()[0]; + addNamespaces(allNamespaces, getNamespacesMap(value, options, getEmptyStringMap())); + + if (value instanceof BArray) { + return getElementFromRecordMember(rootTag == null + ? StringUtils.fromString(Constants.ROOT) : rootTagBstring, traverseRecordAndGenerateXml( + value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( + referredType, keyStr), isSequenceField, isSequenceField, + parentModelGroupInfo, elementInfo), + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); + } + + if (key.equals(options.get(Constants.TEXT_FIELD_NAME))) { + return CreateText.createText(StringUtils.fromString(value.toString())); + } + + BXml output = getElementFromRecordMember(key, + traverseRecordAndGenerateXml(value, allNamespaces, getEmptyStringMap(), options, null, + getChildElementType(referredType, recordKey), isSequenceField, + isSequenceField, parentModelGroupInfo, elementInfo), + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); + if (isContainsModelGroup) { + output = output.children(); + } + if (rootTag != null) { + return CreateElement.createElement(rootTagBstring, getEmptyStringMap(), output); + } + return output; + } catch (BError e) { + return DiagnosticLog.createXmlError(e.getMessage()); + } + } + + private static BMap getEmptyStringMap() { + return (BMap) ((BMap) ValueCreator.createMapValue()); + } + + public static BXml traverseRecordAndGenerateXml(Object jNode, BMap allNamespaces, + BMap parentNamespaces, BMap options, Object keyObj, Type type, + boolean isParentSequence, boolean isParentSequenceArray, + ModelGroupInfo parentModelGroupInfo, ElementInfo parentElementInfo) throws BError { + BMap namespacesOfElem; + BXml xNode = ValueCreator.createXmlValue(Constants.EMPTY_STRING); + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); + Type referredType = TypeUtils.getReferredType(type); + HashMap elementNamesMap = DataUtils.getElementNameMap(referredType); + HashMap modelGroupRelatedFieldNames = + getModelGroupRelatedFieldNames(referredType, elementNamesMap); + HashMap elementInfoRelatedFieldNames = + getElementInfoRelatedFieldNames(referredType, elementNamesMap); + ArrayList sequenceFieldNames = getSequenceFieldNames(referredType, elementNamesMap); + BXml childElement; + + if (jNode instanceof BMap jMap) { + BMap mapNode = (BMap) jMap; + BString[] orderedRecordKeysIfXsdSequencePresent = DataUtils.getOrderedRecordKeysIfXsdSequencePresent( + mapNode, DataUtils.getXsdSequencePriorityOrder(referredType, isParentSequence)); + + if (parentModelGroupInfo instanceof ChoiceInfo) { + validateChoiceFields(parentModelGroupInfo, jMap, elementInfoRelatedFieldNames, elementNamesMap); + } + + for (BString k : orderedRecordKeysIfXsdSequencePresent) { + Object value = mapNode.get(k); + String jsonKey = k.getValue().trim(); + String localJsonKeyPart = jsonKey.contains(Constants.COLON) ? + jsonKey.substring(jsonKey.indexOf(Constants.COLON) + 1) : jsonKey; + String recordKey = elementNamesMap.getOrDefault(localJsonKeyPart, localJsonKeyPart); + boolean isContainsModelGroup = modelGroupRelatedFieldNames.containsKey(recordKey); + ModelGroupInfo modelGroupInfo = modelGroupRelatedFieldNames.get(recordKey); + ElementInfo elementInfo = elementInfoRelatedFieldNames.get(recordKey); + boolean isSequenceField = sequenceFieldNames.contains(recordKey); + + if (jsonKey.startsWith(attributePrefix)) { + continue; + } + + if (jsonKey.equals(options.get(Constants.TEXT_FIELD_NAME).toString())) { + xNode = Concat.concat(xNode, CreateText.createText(StringUtils.fromString(value.toString()))); + } else { + namespacesOfElem = getNamespacesMap(value, options, parentNamespaces); + addNamespaces(allNamespaces, namespacesOfElem); + + if (value instanceof BArray) { + childElement = traverseRecordAndGenerateXml(value, allNamespaces, namespacesOfElem, options, k, + getChildElementType(referredType, recordKey), + isSequenceField, isSequenceField, modelGroupInfo, elementInfo); + xNode = Concat.concat(xNode, childElement); + } else { + childElement = getElementFromRecordMember(k, traverseRecordAndGenerateXml( + value, allNamespaces, namespacesOfElem, options, null, getChildElementType( + referredType, recordKey), isSequenceField, isSequenceField, modelGroupInfo, elementInfo), + allNamespaces, options, getAttributesMap(value, options, allNamespaces, parentNamespaces)); + xNode = Concat.concat(xNode, !isContainsModelGroup || isParentSequenceArray ? childElement + : childElement.children()); + } + } + } + } else if (jNode instanceof BArray arrayNode) { + int size = arrayNode.size(); + if (isParentSequenceArray && parentModelGroupInfo != null && parentModelGroupInfo instanceof SequenceInfo) { + if (size < parentModelGroupInfo.getMinOccurs()) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, + parentModelGroupInfo.getFieldName()); + } + + if (size > parentModelGroupInfo.getMaxOccurs()) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, + parentModelGroupInfo.getFieldName()); + } + } else { + if (parentElementInfo != null && size > parentElementInfo.maxOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, + parentElementInfo.fieldName); + } + + if (parentElementInfo != null && size < parentElementInfo.minOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, + parentElementInfo.fieldName); + } + } + + for (Object i : arrayNode.getValues()) { + if (i == null) { + continue; + } + String arrayEntryTagKey = Constants.EMPTY_STRING; + if (keyObj instanceof BString key) { + arrayEntryTagKey = key.getValue(); + } else if (!options.get(Constants.ARRAY_ENTRY_TAG).toString().isEmpty()) { + arrayEntryTagKey = options.get(Constants.ARRAY_ENTRY_TAG).toString(); + } + + namespacesOfElem = getNamespacesMap(i, options, parentNamespaces); + addNamespaces(allNamespaces, namespacesOfElem); + if (options.get(Constants.ARRAY_ENTRY_TAG).toString().isEmpty()) { + childElement = getElementFromRecordMember(StringUtils.fromString(arrayEntryTagKey), + traverseRecordAndGenerateXml(i, allNamespaces, namespacesOfElem, + options, keyObj, getChildElementType(referredType, null), + isParentSequence, isParentSequenceArray, parentModelGroupInfo, parentElementInfo), + allNamespaces, options, getAttributesMap(i, options, allNamespaces, parentNamespaces)); + } else { + childElement = getElementFromRecordMember(StringUtils.fromString(arrayEntryTagKey), + traverseRecordAndGenerateXml(i, allNamespaces, namespacesOfElem, + options, null, getChildElementType(referredType, null), + isParentSequence, isParentSequenceArray, parentModelGroupInfo, parentElementInfo), + allNamespaces, options, getAttributesMap(i, options, allNamespaces, parentNamespaces)); + } + xNode = Concat.concat(xNode, isParentSequenceArray ? childElement.children() : childElement); + } + } else { + xNode = CreateText.createText(StringUtils.fromString(StringUtils.getStringValue(jNode))); + } + return xNode; + } + + private static void validateChoiceFields(ModelGroupInfo parentModelGroupInfo, BMap jMap, + HashMap elementInfoRelatedFieldNames, + HashMap elementNamesMap) { + // TODO: Update this later for validate choices with multiple element occurences. + boolean isMeasurable = true; + int occurences = 0; + + for (Object key : jMap.getKeys()) { + String jsonKey = key.toString(); + Object value = jMap.get(key); + String localJsonKeyPart = jsonKey.contains(Constants.COLON) ? + jsonKey.substring(jsonKey.indexOf(Constants.COLON) + 1) : jsonKey; + String recordKey = elementNamesMap.getOrDefault(localJsonKeyPart, localJsonKeyPart); + ElementInfo elementInfo = elementInfoRelatedFieldNames.get(recordKey); + if (elementInfo != null && elementInfo.maxOccurs != 1) { + isMeasurable = false; + break; + } + + if (value instanceof BArray array) { + occurences += array.size(); + } else { + occurences++; + } + } + + if (isMeasurable && occurences > parentModelGroupInfo.getMaxOccurs()) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, + parentModelGroupInfo.getFieldName()); + } + + if (isMeasurable && occurences < parentModelGroupInfo.getMinOccurs()) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, + parentModelGroupInfo.getFieldName()); + } + } + + private static HashMap getModelGroupRelatedFieldNames(Type expType, + HashMap elementNamesMap) { + Type referedType = TypeUtils.getReferredType(expType); + if (referedType instanceof RecordType recordType) { + return DataUtils.getFieldNamesWithModelGroupAnnotations(recordType, elementNamesMap); + } + return new HashMap<>(); + } + + private static HashMap getElementInfoRelatedFieldNames(Type expType, + HashMap elementNamesMap) { + Type referedType = TypeUtils.getReferredType(expType); + if (referedType instanceof RecordType recordType) { + return DataUtils.getFieldNamesWithElementGroupAnnotations(recordType, elementNamesMap, null); + } + return new HashMap<>(); + } + + private static ArrayList getSequenceFieldNames(Type expType, + HashMap elementNamesMap) { + Type referedType = TypeUtils.getReferredType(expType); + if (referedType instanceof RecordType recordType) { + return DataUtils.getFieldNamesWithSequenceAnnotations(recordType, elementNamesMap); + } + return new ArrayList<>(); + } + + + private static Type getChildElementType(Type type, String recordKey) throws BError { + try { + if (type instanceof ArrayType arrayType) { + return TypeUtils.getReferredType(arrayType.getElementType()); + } + + if (type instanceof RecordType recordType) { + Map fields = recordType.getFields(); + if (fields.containsKey(recordKey)) { + return fields.get(recordKey).getFieldType(); + } + + Optional fieldName = getFieldFromRecordNameAnnotation(fields, recordKey); + if (!(fieldName.isEmpty()) && fields.containsKey(fieldName.get())) { + return fields.get(fieldName.get()).getFieldType(); + } else { + assert false; + throw DiagnosticLog.createXmlError("Invalid xml provided"); + } + } + return type; + } catch (Exception e) { + throw DiagnosticLog.createXmlError("Invalid xml provided"); + } + } + + private static Optional getFieldFromRecordNameAnnotation(Map fields, String recordKey) { + for (Field field: fields.values()) { + Type fieldType = TypeUtils.getReferredType(field.getFieldType()); + if (fieldType instanceof RecordType recordType) { + for (Map.Entry annotation: recordType.getAnnotations().entrySet()) { + if (DataUtils.isNameAnnotationKey(annotation.getKey().getValue())) { + String name = ((BMap) annotation.getValue()).get(Constants.VALUE).toString(); + if (name.equals(recordKey)) { + return Optional.of(field.getFieldName()); + } + } + } + } + } + return Optional.empty(); + } + + public static boolean isSingleRecordMember(Object node) { + if (node instanceof BArray arrayNode) { + if (arrayNode.getElementType().getTag() == TypeTags.JSON_TAG) { + return false; + } + } + + try { + Object convertedValue = ValueUtils + .convert(node, TypeCreator.createMapType(PredefinedTypes.TYPE_ANYDATA)); + if (convertedValue instanceof BMap mapNode) { + return mapNode.size() <= 1; + } + } catch (BError e) { + return true; + } + return true; + } + public static BXml getElementFromRecordMember(BString name, BXml children, BMap namespaces, + BMap options, BMap attributes) { + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); + String userAttributePrefix = options.get(Constants.USER_ATTRIBUTE_PREFIX).toString(); + BXml element; + String nameStr = name.getValue(); + int index = nameStr.indexOf(Constants.COLON); + + if (index != -1) { + String prefix = nameStr.substring(0, index); + String elementName; + + if (!userAttributePrefix.isEmpty()) { + elementName = removeUserAttributePrefix(StringUtils.fromString(nameStr), + StringUtils.fromString(userAttributePrefix), (long) index).getValue(); + } else { + elementName = nameStr.substring(index + 1, nameStr.length()); + } + + String namespaceUrl = attributes.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)).toString(); + + if (namespaceUrl.isEmpty()) { + namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)).toString(); + + if (!namespaceUrl.isEmpty()) { + attributes.put(StringUtils.fromString(getXmlnsNameUrI() + prefix), + StringUtils.fromString(namespaceUrl)); + } + } + + if (namespaceUrl.equals(Constants.EMPTY_STRING)) { + element = CreateElement.createElement(StringUtils.fromString(elementName), attributes, children); + } else { + element = CreateElement.createElement(StringUtils.fromString("{" + namespaceUrl + "}" + elementName), + attributes, children); + } + } else { + if (nameStr.startsWith(attributePrefix)) { + throw DiagnosticLog.createXmlError("attribute cannot be an object or array."); + } + + BMap newAttributes = attributes; + if (newAttributes.containsKey(StringUtils.fromString(getXmlnsNameUrI()))) { + String value = newAttributes.get(StringUtils.fromString(getXmlnsNameUrI())).toString(); + newAttributes.remove(StringUtils.fromString(getXmlnsNameUrI())); + newAttributes.put(XMLNS, StringUtils.fromString(value)); + } + if (!userAttributePrefix.equals(Constants.EMPTY_STRING)) { + element = CreateElement.createElement(removeUserAttributePrefix(StringUtils.fromString(nameStr), + StringUtils.fromString(userAttributePrefix), null), newAttributes, children); + } else { + element = CreateElement.createElement(StringUtils.fromString(nameStr), newAttributes, children); + } + } + return element; + } + + + public static BString removeUserAttributePrefix(BString name, BString userAttributePrefix, Object index) { + String nameStr = name.getValue(); + String userAttributePrefixStr = userAttributePrefix.getValue(); + int usrAttIndex = nameStr.indexOf(userAttributePrefixStr); + + if (usrAttIndex != -1) { + return StringUtils.fromString(nameStr.substring(usrAttIndex + 1, nameStr.length())); + } + + if (index instanceof Long indexNum) { + return StringUtils.fromString(nameStr.substring(indexNum.intValue() + 1, nameStr.length())); + } + return StringUtils.fromString(nameStr); + } + + public static BMap getAttributesMap(Object jsonTree, + BMap options, + BMap namespaces, + BMap parentNamespaces) { + BMap attributes = (BMap) parentNamespaces.copy(new HashMap<>()); + try { + BMap attr = (BMap) ValueUtils.convert( + jsonTree, TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); + + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); + for (Map.Entry entry : attr.entrySet()) { + String key = entry.getKey().toString(); + Object value = entry.getValue(); + if (!key.startsWith(attributePrefix)) { + continue; + } + + if (value instanceof BMap || value instanceof BArray) { + DiagnosticLog.createXmlError("attribute cannot be an object or array."); + } + + int index = key.indexOf(Constants.COLON); + if (index != -1) { + String suffix = key.substring(index + 1); + if (key.startsWith(attributePrefix + XMLNS)) { + attributes.put(StringUtils.fromString(getXmlnsNameUrI() + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), StringUtils.fromString( + options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), StringUtils.fromString(key)); + String prefix = key.substring(startIndex.intValue(), index); + BString namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)); + attributes.put(StringUtils.fromString("{" + namespaceUrl + "}" + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } else { + if (key.equals(attributePrefix + XMLNS)) { + attributes.put(XMLNS, StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), + StringUtils.fromString(options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), + StringUtils.fromString(key)); + attributes.put(StringUtils.fromString(key.substring(startIndex.intValue())), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } + } + return attributes; + } catch (BError e) { + return attributes; + } + } + + public static Long getStartIndex(BString attributePrefix, BString userAttributePrefix, BString key) { + String attributePrefixStr = attributePrefix.toString(); + String userAttributePrefixStr = userAttributePrefix.toString(); + String keyStr = key.toString(); + int startIndex = 1; + + if (!attributePrefixStr.equals(ATTRIBUTE_PREFIX.toString())) { + return (long) startIndex; + } + + int location = userAttributePrefixStr.equals(Constants.EMPTY_STRING) ? keyStr.indexOf("_") + : keyStr.indexOf(userAttributePrefixStr); + if (location != -1) { + startIndex = location + 1; + } + return (long) startIndex; + } + + public static BMap getNamespacesMap(Object jsonTree, + BMap options, + BMap parentNamespaces) { + BMap namespaces = (BMap) parentNamespaces.copy(new HashMap<>()); + try { + Object jsonTreeObject = ValueUtils.convert(jsonTree, TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); + BMap attr = (BMap) jsonTreeObject; + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); + + for (Map.Entry entry : attr.entrySet()) { + BString key = entry.getKey(); + Object value = entry.getValue(); + if (!key.getValue().startsWith(attributePrefix)) { + continue; + } + + if (value instanceof BMap || value instanceof BArray) { + throw DiagnosticLog.createXmlError("attribute cannot be an object or array."); + } + + if (!key.getValue().startsWith(attributePrefix + XMLNS)) { + continue; + } + + int index = key.getValue().indexOf(Constants.COLON); + if (index != -1) { + String prefix = key.getValue().substring(index + 1); + namespaces.put(StringUtils.fromString(getXmlnsNameUrI() + prefix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + namespaces.put(StringUtils.fromString(getXmlnsNameUrI()), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } + return namespaces; + } catch (BError e) { + return namespaces; + } + } + + private static String getXmlnsNameUrI() { + return "{" + XMLNS_NAMESPACE_URI + "}"; + } + + public static void addNamespaces(BMap allNamespaces, BMap namespaces) { + for (Map.Entry entry: namespaces.entrySet()) { + allNamespaces.put(entry.getKey(), entry.getValue()); + } + } +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/XsdUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/XsdUtils.java new file mode 100644 index 00000000..8f3f8bf2 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/XsdUtils.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.utils; + +import io.ballerina.lib.data.xmldata.xml.QualifiedName; +import io.ballerina.lib.data.xmldata.xml.xsd.ChoiceInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ElementInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.ModelGroupInfo; +import io.ballerina.lib.data.xmldata.xml.xsd.SequenceInfo; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.util.HashMap; +import java.util.Map; + +import static io.ballerina.lib.data.xmldata.utils.DataUtils.XmlAnalyzerMetaData; +import static io.ballerina.lib.data.xmldata.utils.DataUtils.popMappingTypeStacks; + +/** + * A util class for the xsd validation. + * + * @since 1.1.0 + */ +public class XsdUtils { + public static void initializeXsdInformation(RecordType recordType, XmlAnalyzerMetaData parserData) { + BMap annotations = recordType.getAnnotations(); + for (BString annotationKey : annotations.getKeys()) { + String key = annotationKey.getValue(); + if (key.contains(Constants.FIELD)) { + String fieldName = key.split(Constants.FIELD_REGEX)[1].replaceAll("\\\\", ""); + Map fieldAnnotation = (Map) annotations.get(annotationKey); + String xmlElementName = DataUtils.getModifiedName(fieldAnnotation, fieldName); + for (BString fieldAnnotationKey: fieldAnnotation.keySet()) { + String fieldAnnotationKeyStr = fieldAnnotationKey.getValue(); + if (fieldAnnotationKeyStr.startsWith(Constants.MODULE_NAME)) { + handleModuleXsdAnnotations(fieldAnnotation, fieldAnnotationKeyStr, fieldName, + recordType, fieldAnnotationKey, xmlElementName, parserData); + } + } + } + } + } + + public static void handleModuleXsdAnnotations(Map fieldAnnotation, String fieldAnnotationKeyStr, + String fieldName, RecordType recordType, BString fieldAnnotationKey, + String xmlElementName, XmlAnalyzerMetaData parserData) { + if (fieldAnnotationKeyStr.endsWith(Constants.ELEMENT)) { + handleModuleXsdElementAnnotation(fieldAnnotation, fieldAnnotationKey, + fieldName, xmlElementName, parserData); + } else if (fieldAnnotationKeyStr.endsWith(Constants.SEQUENCE)) { + handleModuleXsdSequenceAnnotations(fieldAnnotation, fieldName, recordType, + fieldAnnotationKey, parserData); + } else if (fieldAnnotationKeyStr.endsWith(Constants.CHOICE)) { + handleModuleChoiceSequenceAnnotations(fieldAnnotation, fieldName, recordType, + fieldAnnotationKey, parserData); + } + } + + public static void handleModuleXsdElementAnnotation(Map fieldAnnotation, + BString fieldAnnotationKey, String fieldName, String xmlElementName, XmlAnalyzerMetaData parserData) { + BMap fieldAnnotationValue = (BMap) fieldAnnotation.get(fieldAnnotationKey); + parserData.xmlElementInfo.peek().put(xmlElementName, + new ElementInfo(xmlElementName, fieldName, fieldAnnotationValue)); + } + + public static void handleModuleXsdSequenceAnnotations(Map fieldAnnotation, String fieldName, + RecordType recordType, BString fieldAnnotationKey, XmlAnalyzerMetaData parserData) { + BMap fieldAnnotationValue = (BMap) fieldAnnotation.get(fieldAnnotationKey); + Type fieldType = TypeUtils.getReferredType(recordType + .getFields().get(fieldName).getFieldType()); + if (fieldType instanceof RecordType recType) { + parserData.xsdModelGroupInfo.peek().put(fieldName, + new SequenceInfo(fieldName, + fieldAnnotationValue, recType, parserData.xmlElementInfo)); + } else if (fieldType instanceof ArrayType arrayType) { + Type elementType = TypeUtils.getReferredType(arrayType.getElementType()); + if (elementType instanceof RecordType recType) { + parserData.xsdModelGroupInfo.peek().put(fieldName, + new SequenceInfo(fieldName, + fieldAnnotationValue, recType, parserData.xmlElementInfo)); + } else { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_SEQUENCE_ANNOTATION, fieldName, fieldType); + } + } else { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_SEQUENCE_ANNOTATION, fieldName, fieldType); + } + } + + public static void handleModuleChoiceSequenceAnnotations(Map fieldAnnotation, String fieldName, + RecordType recordType, BString fieldAnnotationKey, XmlAnalyzerMetaData parserData) { + BMap fieldAnnotationValue = (BMap) fieldAnnotation.get(fieldAnnotationKey); + Type fieldType = TypeUtils.getReferredType(recordType + .getFields().get(fieldName).getFieldType()); + if (fieldType instanceof RecordType recType) { + parserData.xsdModelGroupInfo.peek().put(fieldName, + new ChoiceInfo(fieldName, fieldAnnotationValue, recType, parserData.xmlElementInfo)); + } else { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_CHOICE_ANNOTATION, fieldName, fieldType); + } + } + + public static void validateCurrentElementInfo(XmlAnalyzerMetaData xmlAnalyzerMetaData) { + if (!xmlAnalyzerMetaData.xmlElementInfo.isEmpty()) { + xmlAnalyzerMetaData.xmlElementInfo.peek().forEach((key, value) -> value.validate()); + } + } + + public static void validateElementInfoStack(XmlAnalyzerMetaData xmlAnalyzerMetaData) { + while (!xmlAnalyzerMetaData.xmlElementInfo.isEmpty()) { + xmlAnalyzerMetaData.xmlElementInfo.pop().forEach((key, value) -> value.validate()); + } + } + + public static void validateModelGroupInfoStack(XmlAnalyzerMetaData xmlAnalyzerMetaData) { + while (!xmlAnalyzerMetaData.xsdModelGroupInfo.isEmpty()) { + xmlAnalyzerMetaData.xsdModelGroupInfo.pop().forEach((key, value) -> value.validateMinOccurrences()); + } + } + +public static void popXsdValidationStacks(XmlAnalyzerMetaData xmlAnalyzerMetaData) { + xmlAnalyzerMetaData.xsdModelGroupInfo.pop().forEach((key, value) -> value.validateMinOccurrences()); + xmlAnalyzerMetaData.xmlElementInfo.pop(); + } + + public static void updateElementOccurrence(XmlAnalyzerMetaData xmlAnalyzerMetaData, QualifiedName elemQName) { + if (!xmlAnalyzerMetaData.xmlElementInfo.isEmpty()) { + HashMap elementInfo = xmlAnalyzerMetaData.xmlElementInfo.peek(); + if (elementInfo.containsKey(elemQName.getLocalPart())) { + elementInfo.get(elemQName.getLocalPart()).updateOccurrences(); + } + } + } + + public static void validateModelGroupStack(XmlAnalyzerMetaData xmlAnalyzerMetaData, + QualifiedName elemQName, boolean isStartElement) { + String localPart = elemQName.getLocalPart(); + while (!xmlAnalyzerMetaData.modelGroupStack.isEmpty()) { + ModelGroupInfo modelGroup = xmlAnalyzerMetaData.modelGroupStack.peek(); + if ((!modelGroup.isElementContains(localPart) + && !modelGroup.isMiddleOfModelGroup())) { + validateModelGroup(modelGroup, xmlAnalyzerMetaData); + continue; + } + + if (isStartElement && modelGroup.predictStartNewModelGroup(localPart)) { + validateModelGroup(modelGroup, xmlAnalyzerMetaData, false); + return; + } + + if (modelGroup.isElementContains(localPart)) { + modelGroup.visit(localPart, isStartElement); + } + return; + } + } + + public static void validateModelGroup(ModelGroupInfo modelGroup, XmlAnalyzerMetaData xmlAnalyzerMetaData) { + validateModelGroup(modelGroup, xmlAnalyzerMetaData, true); + } + + public static void validateModelGroup(ModelGroupInfo modelGroup, + XmlAnalyzerMetaData xmlAnalyzerMetaData, boolean isTerminated) { + modelGroup.validate(); + if (isTerminated) { + modelGroup.validateMinOccurrences(); + } + xmlAnalyzerMetaData.currentNode = (BMap) xmlAnalyzerMetaData.nodesStack.pop(); + xmlAnalyzerMetaData.modelGroupStack.pop(); + xmlAnalyzerMetaData.rootRecord = xmlAnalyzerMetaData.recordTypeStack.pop(); + validateCurrentElementInfo(xmlAnalyzerMetaData); + popElementStacksForValidatingGroup(xmlAnalyzerMetaData); + } + + public static void popElementStacksForValidatingGroup(XmlAnalyzerMetaData xmlAnalyzerMetaData) { + popMappingTypeStacks(xmlAnalyzerMetaData); + xmlAnalyzerMetaData.attributeHierarchy.pop(); + xmlAnalyzerMetaData.arrayIndexes.pop(); + xmlAnalyzerMetaData.xsdModelGroupInfo.pop().forEach((key, value) -> value.validateMinOccurrences()); + xmlAnalyzerMetaData.xmlElementInfo.pop(); + } +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/Native.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/Native.java index 8eda9f97..b363a8cc 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/Native.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/Native.java @@ -81,4 +81,12 @@ public static Object parseStream(Environment env, BStream xml, BMap getDefaultSourceOptions() { + BMap sourceOptions = ValueCreator + .createRecordValue(ModuleUtils.getModule(), SOURCE_OPTIONS); + sourceOptions.put(ATTRIBUTE_PREFIX, StringUtils.fromString("")); + sourceOptions.put(TEXT_FIELD_NAME, StringUtils.fromString(CONTENT_FIELD)); + return sourceOptions; + } +} 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 0ed318e9..19e186c0 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 @@ -23,6 +23,7 @@ import io.ballerina.lib.data.xmldata.utils.DataUtils; import io.ballerina.lib.data.xmldata.utils.DiagnosticErrorCode; import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; +import io.ballerina.lib.data.xmldata.xml.xsd.ModelGroupInfo; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; @@ -58,6 +59,13 @@ import javax.xml.stream.XMLStreamReader; import static io.ballerina.lib.data.xmldata.utils.Constants.ENABLE_CONSTRAINT_VALIDATION; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.initializeXsdInformation; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.popXsdValidationStacks; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.updateElementOccurrence; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateCurrentElementInfo; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateElementInfoStack; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateModelGroupInfoStack; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateModelGroupStack; import static javax.xml.stream.XMLStreamConstants.CDATA; import static javax.xml.stream.XMLStreamConstants.CHARACTERS; import static javax.xml.stream.XMLStreamConstants.COMMENT; @@ -66,6 +74,7 @@ import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; import static javax.xml.stream.XMLStreamConstants.PROCESSING_INSTRUCTION; import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; +import static io.ballerina.lib.data.xmldata.utils.DataUtils.XmlParserData; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.ATTRIBUTE; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.ELEMENT; import static io.ballerina.lib.data.xmldata.xml.QualifiedName.AttributeState.NOT_DEFINED; @@ -185,6 +194,8 @@ public Object parse(XmlParserData xmlParserData) { handleXMLStreamException(e); } + validateElementInfoStack(xmlParserData); + validateModelGroupInfoStack(xmlParserData); return xmlParserData.currentNode; } @@ -221,6 +232,7 @@ public void parseRecordRest(String startElementName, XmlParserData xmlParserData if (next == END_ELEMENT) { QName endElement = xmlStreamReader.getName(); if (endElement.getLocalPart().equals(startElementName)) { + validateCurrentElementInfo(xmlParserData); validateRequiredFields(xmlParserData); popExpectedTypeStacks(xmlParserData); break; @@ -251,22 +263,21 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, xmlParserData.currentNode = ValueCreator.createRecordValue(rootRecord.getPackage(), rootRecord.getName()); boolean useSemanticEquality = xmlParserData.useSemanticEquality; QualifiedName elementQName = getElementName(xmlStreamReader, useSemanticEquality); - xmlParserData.rootElement = - DataUtils.validateAndGetXmlNameFromRecordAnnotation(rootRecord, rootRecord.getName(), elementQName, + DataUtils.validateAndGetXmlNameFromRecordAnnotation(rootRecord, rootRecord.getName(), elementQName, useSemanticEquality); DataUtils.validateTypeNamespace(elementQName.getPrefix(), elementQName.getNamespaceURI(), rootRecord); // Keep track of fields and attributes xmlParserData.recordTypeStack.push(rootRecord); updateExpectedTypeStacks(rootRecord, xmlParserData); + initializeXsdInformation(rootRecord, xmlParserData); handleAttributes(xmlStreamReader, xmlParserData); xmlParserData.arrayIndexes.push(new HashMap<>()); } @SuppressWarnings("unchecked") private void readText(XMLStreamReader xmlStreamReader, - boolean isCData, - XmlParserData xmlParserData) throws XMLStreamException { + boolean isCData, XmlParserData xmlParserData) throws XMLStreamException { Field currentField = xmlParserData.currentField; TextValue textValue = new TextValue(); String text; @@ -357,6 +368,7 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, } xmlParserData.currentNode = parent; + validateCurrentElementInfo(xmlParserData); popExpectedTypeStacks(xmlParserData); updateSiblingAndRootRecord(xmlParserData); } @@ -397,6 +409,7 @@ private void handleTruncatedCharacters(XMLStreamReader xmlStreamReader, TextValu @SuppressWarnings("unchecked") private void handleContentFieldInRecordType(RecordType recordType, BString text, XmlParserData xmlParserData) { + validateCurrentElementInfo(xmlParserData); popExpectedTypeStacks(xmlParserData); updateSiblingAndRootRecord(xmlParserData); for (String key : recordType.getFields().keySet()) { @@ -452,6 +465,7 @@ private void buildDocument(XmlParserData xmlParserData) { if (xmlParserData.fieldHierarchy.empty()) { return; } + validateCurrentElementInfo(xmlParserData); validateRequiredFields(xmlParserData); } @@ -461,14 +475,18 @@ private void endElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParser QualifiedName elemQName = getElementName(xmlStreamReader, xmlParserData.useSemanticEquality); QualifiedNameMap siblings = xmlParserData.siblings; Stack> parents = xmlParserData.parents; + if (siblings.contains(elemQName) && !siblings.get(elemQName)) { siblings.put(elemQName, true); } if (parents.isEmpty() || !parents.peek().contains(elemQName)) { + validateModelGroupStack(xmlParserData, elemQName, false); return; } + validateCurrentElementInfo(xmlParserData); validateRequiredFields(xmlParserData); + validateModelGroupStack(xmlParserData, elemQName, false); xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.pop(); popExpectedTypeStacks(xmlParserData); updateSiblingAndRootRecord(xmlParserData); @@ -492,6 +510,9 @@ private void validateRequiredFields(XmlParserData xmlParserData) { private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader, xmlParserData.useSemanticEquality); + updateElementOccurrence(xmlParserData, elemQName); + validateModelGroupStack(xmlParserData, elemQName, true); + QualifiedNameMap fieldMap = xmlParserData.fieldHierarchy.peek(); Field currentField = null; if (xmlParserData.visitedFieldHierarchy.peek().contains(elemQName)) { @@ -500,8 +521,21 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse elemQName = fieldMap.getMatchedQualifiedName(elemQName); currentField = fieldMap.remove(elemQName); } + xmlParserData.currentField = currentField; if (currentField == null) { + // TODO: In here assume that if XSD is present then no rest type can be there. + HashMap modelGroupInfo = null; + if (!xmlParserData.xsdModelGroupInfo.isEmpty()) { + modelGroupInfo = xmlParserData.xsdModelGroupInfo.peek(); + } + + if (modelGroupInfo != null && !modelGroupInfo.isEmpty()) { + validateElementInXsdSequenceOrElement(elemQName, modelGroupInfo, + xmlStreamReader, xmlParserData, xmlParserData.visitedFieldHierarchy.peek(), fieldMap); + return; + } + String elemName = elemQName.getLocalPart(); if (xmlParserData.restTypes.peek() != null) { if (fieldMap.contains(elemName)) { @@ -525,23 +559,91 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse Type referredFieldType = TypeUtils.getReferredType(fieldType); if (!xmlParserData.siblings.contains(elemQName)) { xmlParserData.siblings.put(elemQName, false); + } else if (DataUtils.isAnydataOrJson(referredFieldType.getTag()) && !(temp instanceof BArray)) { + BArray tempArray = DataUtils.createArrayValue(referredFieldType); + tempArray.append(temp); + currentNode.put(bFieldName, tempArray); } else { - if (DataUtils.isAnydataOrJson(referredFieldType.getTag()) && !(temp instanceof BArray)) { - BArray tempArray = DataUtils.createArrayValue(referredFieldType); - tempArray.append(temp); - currentNode.put(bFieldName, tempArray); - } else if (referredFieldType.getTag() != TypeTags.ARRAY_TAG) { - throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); + if (!xmlParserData.modelGroupStack.isEmpty()) { + ModelGroupInfo modelGroup = xmlParserData.modelGroupStack.peek(); + + // Check whether this is a model group array + if (referredFieldType.getTag() != TypeTags.ARRAY_TAG && modelGroup == null) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); + } + } else { + if (referredFieldType.getTag() != TypeTags.ARRAY_TAG) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); + } } } if (DataUtils.isRegExpType(fieldType)) { throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE); } - initializeNextValueBasedOnExpectedType(fieldName, fieldType, temp, currentNode, xmlParserData); } + private void validateElementInXsdSequenceOrElement(QualifiedName elemQName, + HashMap modelGroupInfo, XMLStreamReader xmlStreamReader, + XmlParserData xmlParserData, QualifiedNameMap visitedFields, QualifiedNameMap fieldMap) { + if (modelGroupInfo != null) { + for (Map.Entry entry : modelGroupInfo.entrySet()) { + ModelGroupInfo modelGroupValue = entry.getValue(); + String key = entry.getKey(); + QualifiedName qualifiedName = QualifiedNameFactory + .createQualifiedName("", key, "", xmlParserData.useSemanticEquality); + + if (modelGroupValue.isElementContains(elemQName.getLocalPart())) { + Object temp = null; + Field field = xmlParserData.rootRecord.getFields().get(key); + if (visitedFields.contains(qualifiedName)) { + temp = xmlParserData.currentNode.get(StringUtils.fromString(key)); + } + xmlParserData.modelGroupStack.push(modelGroupValue); + visitedFields.put(qualifiedName, field); + fieldMap.remove(qualifiedName); + Type referredType = TypeUtils.getReferredType(field.getFieldType()); + updateNextRecordForXsd(xmlParserData, key, referredType, temp, xmlParserData.currentNode); + readElement(xmlStreamReader, xmlParserData); + return; + } + } + } + } + + private void updateNextRecordForXsd(XmlParserData xmlParserData, + String fieldName, Type fieldType, Object temp, BMap currentNode) { + xmlParserData.recordTypeStack.push(xmlParserData.rootRecord); + if (fieldType.getTag() == TypeTags.RECORD_TYPE_TAG) { + RecordType recType = (RecordType) fieldType; + xmlParserData.currentNode = updateNextValue(recType, fieldName, fieldType, xmlParserData); + xmlParserData.rootRecord = recType; + return; + } + + if (fieldType.getTag() == TypeTags.ARRAY_TAG) { + Type elementType = TypeUtils.getReferredType(((ArrayType) fieldType).getElementType()); + if (temp == null) { + xmlParserData.arrayIndexes.peek().put(fieldName, 0); + currentNode.put(StringUtils.fromString(fieldName), + ValueCreator.createArrayValue((ArrayType) fieldType)); + } else { + HashMap indexes = xmlParserData.arrayIndexes.peek(); + indexes.put(fieldName, indexes.get(fieldName) + 1); + } + + if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { + RecordType recType = (RecordType) elementType; + xmlParserData.rootRecord = recType; + xmlParserData.currentNode = updateNextValue(recType, + fieldName, fieldType, xmlParserData); + return; + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_XSD_ANNOTATION, fieldName, fieldType); + } + private void initializeNextValueBasedOnExpectedType(String fieldName, Type fieldType, Object temp, BMap currentNode, XmlParserData xmlParserData) { @@ -614,6 +716,8 @@ private BMap updateNextMappingValue(XmlParserData xmlParserData xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.recordTypeStack.push(null); + xmlParserData.xsdModelGroupInfo.push(new HashMap<>()); + xmlParserData.xmlElementInfo.push(new HashMap<>()); BMap currentNode = xmlParserData.currentNode; Object temp = currentNode.get(StringUtils.fromString(fieldName)); if (temp instanceof BArray) { @@ -657,6 +761,7 @@ private BMap updateNextValue(RecordType rootRecord, String fiel XmlParserData xmlParserData) { BMap nextValue = ValueCreator.createRecordValue(rootRecord.getPackage(), rootRecord.getName()); updateExpectedTypeStacks(rootRecord, xmlParserData); + initializeXsdInformation(rootRecord, xmlParserData); BMap currentNode = xmlParserData.currentNode; Object temp = currentNode.get(StringUtils.fromString(fieldName)); if (temp instanceof BArray) { @@ -681,18 +786,19 @@ private void updateExpectedTypeStacks(RecordType recordType, XmlParserData xmlPa xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(getAllFieldsInRecordType(recordType, xmlParserData))); xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.restTypes.push(recordType.getRestFieldType()); + xmlParserData.xsdModelGroupInfo.push(new HashMap<>()); + xmlParserData.xmlElementInfo.push(new HashMap<>()); } private void popExpectedTypeStacks(XmlParserData xmlParserData) { popMappingTypeStacks(xmlParserData); xmlParserData.attributeHierarchy.pop(); xmlParserData.arrayIndexes.pop(); + popXsdValidationStacks(xmlParserData); } private void popMappingTypeStacks(XmlParserData xmlParserData) { - xmlParserData.fieldHierarchy.pop(); - xmlParserData.visitedFieldHierarchy.pop(); - xmlParserData.restTypes.pop(); + DataUtils.popMappingTypeStacks(xmlParserData); } private void updateSiblingAndRootRecord(XmlParserData xmlParserData) { @@ -760,6 +866,8 @@ private void updateExpectedTypeStacksOfRestType(Type restType, XmlParserData xml xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.restTypes.push(restType); xmlParserData.arrayIndexes.push(new HashMap<>()); + xmlParserData.xsdModelGroupInfo.push(new HashMap<>()); + xmlParserData.xmlElementInfo.push(new HashMap<>()); } } @@ -1142,30 +1250,4 @@ static class TextValue { String text; boolean isCommentInTheMiddle = false; } - - /** - * Holds data required for the parsing. - * - * @since 0.1.0 - */ - public static class XmlParserData { - private final Stack nodesStack = new Stack<>(); - private final Stack> fieldHierarchy = new Stack<>(); - Stack> visitedFieldHierarchy = new Stack<>(); - private final Stack> attributeHierarchy = new Stack<>(); - private final Stack restTypes = new Stack<>(); - private final Stack restFieldsPoints = new Stack<>(); - private final Stack recordTypeStack = new Stack<>(); - Stack> arrayIndexes = new Stack<>(); - private RecordType rootRecord; - private Field currentField; - private QualifiedName rootElement; - private final Stack> parents = new Stack<>(); - private QualifiedNameMap siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); - private BMap currentNode; - private String attributePrefix; - private String textFieldName; - private boolean allowDataProjection; - private boolean useSemanticEquality; - } } 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 273ee4a8..1340ca28 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 @@ -23,6 +23,7 @@ import io.ballerina.lib.data.xmldata.utils.DataUtils.XmlAnalyzerData; import io.ballerina.lib.data.xmldata.utils.DiagnosticErrorCode; import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; +import io.ballerina.lib.data.xmldata.xml.xsd.ModelGroupInfo; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; @@ -56,6 +57,12 @@ import javax.xml.namespace.QName; import static io.ballerina.lib.data.xmldata.utils.Constants.ENABLE_CONSTRAINT_VALIDATION; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.initializeXsdInformation; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.updateElementOccurrence; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateCurrentElementInfo; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateElementInfoStack; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateModelGroupInfoStack; +import static io.ballerina.lib.data.xmldata.utils.XsdUtils.validateModelGroupStack; /** * Convert Xml value to a ballerina record. @@ -68,6 +75,7 @@ public class XmlTraversal { public static Object traverse(BXml xml, BMap options, BTypedesc typed) { Object convertedValue = traverse(xml, options, typed.getDescribingType()); + if (convertedValue instanceof BError) { return convertedValue; } @@ -81,12 +89,13 @@ public static Object traverse(BXml xml, BMap options, Type type } 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); + traverseXml(xml, analyzerData, type); + validateElementInfoStack(analyzerData); + validateModelGroupInfoStack(analyzerData); + return analyzerData.currentNode; } public Object traverseXml(BXml xml, XmlAnalyzerData analyzerData, Type type) { @@ -105,12 +114,14 @@ public Object traverseXml(BXml xml, XmlAnalyzerData analyzerData, Type type) { } } - private Object traverseXmlWithRecordAsExpectedType(BXml xml, - XmlAnalyzerData analyzerData, RecordType recordType) { - currentNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); + private Object traverseXmlWithRecordAsExpectedType(BXml xml, XmlAnalyzerData analyzerData, + RecordType recordType) { + analyzerData.currentNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); BXml nextXml = validateRootElement(xml, recordType, analyzerData); Object resultRecordValue = traverseXml(nextXml, recordType, analyzerData); - DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); + validateModelGroupStackForRootElement(analyzerData); + validateCurrentElementInfo(analyzerData); + DataUtils.validateRequiredFields(analyzerData, analyzerData.currentNode); return resultRecordValue; } @@ -143,13 +154,14 @@ private Object traverseXml(BXml xml, Type type, XmlAnalyzerData analyzerData) { case SEQUENCE -> convertSequence((BXmlSequence) xml, type, analyzerData); case TEXT -> convertText(xml.toString(), analyzerData); } - return currentNode; + + return analyzerData.currentNode; } @SuppressWarnings("unchecked") private void convertText(String text, XmlAnalyzerData analyzerData) { Field currentField = analyzerData.currentField; - BMap mapValue = (BMap) currentNode; + BMap mapValue = analyzerData.currentNode; String textFieldName = analyzerData.textFieldName; if (currentField == null) { @@ -226,14 +238,24 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { @SuppressWarnings("unchecked") private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { QualifiedName elementQName = DataUtils.getElementName(xmlItem.getQName(), analyzerData.useSemanticEquality); + updateElementOccurrence(analyzerData, elementQName); + validateModelGroupStack(analyzerData, elementQName, true); QualifiedNameMap fieldsMap = analyzerData.fieldHierarchy.peek(); Field currentField; if (analyzerData.visitedFieldHierarchy.peek().contains(elementQName)) { currentField = analyzerData.visitedFieldHierarchy.peek().get(elementQName); Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); - if (!DataUtils.isArrayValueAssignable(fieldType)) { - throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, - currentField.getFieldName()); + String fieldName = currentField.getFieldName(); + if (!analyzerData.modelGroupStack.isEmpty()) { + ModelGroupInfo modelGroup = analyzerData.modelGroupStack.peek(); + + // Check whether this is a model group array + if (fieldType.getTag() != TypeTags.ARRAY_TAG && modelGroup == null) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, + fieldType, fieldName); + } + } else if (!DataUtils.isArrayValueAssignable(fieldType)) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); } } else { currentField = fieldsMap.get(elementQName); @@ -246,6 +268,18 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { analyzerData.currentField = currentField; if (currentField == null) { + // TODO: In here assume that if XSD is present then no rest type can be there. + HashMap modelGroupInfo = null; + if (!analyzerData.xsdModelGroupInfo.isEmpty()) { + modelGroupInfo = analyzerData.xsdModelGroupInfo.peek(); + } + + if (modelGroupInfo != null && !modelGroupInfo.isEmpty()) { + validateElementInXsdSequenceOrElement(elementQName, modelGroupInfo, + xmlItem, analyzerData, analyzerData.visitedFieldHierarchy.peek(), fieldsMap); + return; + } + Type restType = analyzerData.restTypes.peek(); String elementName = elementQName.getLocalPart(); if (restType != null) { @@ -272,7 +306,8 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { } convertToFieldType(xmlItem, currentField, currentField.getFieldName(), currentFieldType, - (BMap) currentNode, analyzerData); + analyzerData.currentNode, analyzerData); + validateModelGroupStack(analyzerData, elementQName, false); } private void convertToFieldType(BXmlItem xmlItem, Field currentField, String fieldName, Type currentFieldType, @@ -288,6 +323,7 @@ private void convertToFieldType(BXmlItem xmlItem, Field currentField, String fie updateNextMap(currentFieldType, analyzerData); analyzerData.arrayIndexes.push(new HashMap<>()); convertToRestType(xmlItem, currentFieldType, analyzerData); + validateCurrentElementInfo(analyzerData); DataUtils.popExpectedTypeStacks(analyzerData); } case TypeTags.TYPE_REFERENCED_TYPE_TAG -> @@ -384,25 +420,28 @@ private void convertToUnionMemberType(BXmlItem xmlItem, String fieldName, ArrayT private void convertToRecordType(BXmlItem xmlItem, Type currentFieldType, String fieldName, RecordType elementType, BMap mapValue, XmlAnalyzerData analyzerData) { - currentNode = updateNextRecord(xmlItem, elementType, fieldName, + analyzerData.currentNode = updateNextRecord(xmlItem, elementType, fieldName, currentFieldType, mapValue, analyzerData); RecordType prevRecord = analyzerData.rootRecord; analyzerData.rootRecord = elementType; traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); - DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); + validateCurrentElementInfo(analyzerData); + DataUtils.validateRequiredFields(analyzerData, analyzerData.currentNode); DataUtils.popExpectedTypeStacks(analyzerData); analyzerData.rootRecord = prevRecord; - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); } private void convertToMapType(BXmlItem xmlItem, Type fieldType, Type elementType, String fieldName, BMap mapValue, XmlAnalyzerData analyzerData) { updateNextMap(elementType, analyzerData); - currentNode = updateNextMappingValue(elementType, fieldName, fieldType, mapValue, analyzerData); + analyzerData.currentNode = updateNextMappingValue( + elementType, fieldName, fieldType, mapValue, analyzerData); traverseXml(xmlItem.getChildrenSeq(), fieldType, analyzerData); - DataUtils.validateRequiredFields(analyzerData, (BMap) currentNode); + validateCurrentElementInfo(analyzerData); + DataUtils.validateRequiredFields(analyzerData, analyzerData.currentNode); DataUtils.popExpectedTypeStacks(analyzerData); - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); } private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { @@ -414,12 +453,15 @@ private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.attributeHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.xsdModelGroupInfo.push(new HashMap<>()); + analyzerData.xmlElementInfo.push(new HashMap<>()); } private BMap updateNextRecord(BXmlItem xmlItem, RecordType recordType, String fieldName, Type fieldType, BMap currentMapValue, XmlAnalyzerData analyzerData) { DataUtils.updateExpectedTypeStacks(recordType, analyzerData); + initializeXsdInformation(recordType, analyzerData); BMap nextValue = updateNextMappingValue(recordType, fieldName, fieldType, currentMapValue, analyzerData); QName qName = xmlItem.getQName(); @@ -467,7 +509,7 @@ private BMap updateNextMappingValue(Type type, String fieldName private void convertToRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerData analyzerData) { String elemName = xmlItem.getQName().getLocalPart(); analyzerData.currentField = TypeCreator.createField(restType, elemName, SymbolFlags.PUBLIC); - BMap mapValue = (BMap) currentNode; + BMap mapValue = analyzerData.currentNode; checkRestTypeAndConvert(xmlItem, elemName, restType, restType, mapValue, analyzerData); } @@ -564,10 +606,10 @@ private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type if (!nextValue.isEmpty()) { analyzerData.currentField = TypeCreator.createField(restType, analyzerData.textFieldName, SymbolFlags.REQUIRED); - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; + analyzerData.nodesStack.push(analyzerData.currentNode); + analyzerData.currentNode = nextValue; traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); return; } } @@ -588,12 +630,14 @@ private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type arrayValue.add(currentIndex, nextValue); } } - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; + analyzerData.nodesStack.push(analyzerData.currentNode); + analyzerData.currentNode = nextValue; handleAttributesRest(xmlItem, nextValue, restType, useSemanticEquality); analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.xsdModelGroupInfo.push(new HashMap<>()); + analyzerData.xmlElementInfo.push(new HashMap<>()); analyzerData.arrayIndexes.push(new HashMap<>()); if (restType.getTag() == TypeTags.ARRAY_TAG) { Type memberType = ((ArrayType) restType).getElementType(); @@ -607,7 +651,7 @@ private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type analyzerData.visitedFieldHierarchy.pop(); analyzerData.restTypes.pop(); analyzerData.arrayIndexes.pop(); - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); } private void convertContentToRestType(BXmlItem xmlItem, BString bElementName, Type restType, @@ -621,10 +665,10 @@ private void convertContentToRestType(BXmlItem xmlItem, BString bElementName, Ty if (!nextValue.isEmpty()) { analyzerData.currentField = TypeCreator.createField(restType, analyzerData.textFieldName, SymbolFlags.REQUIRED); - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; + analyzerData.nodesStack.push(analyzerData.currentNode); + analyzerData.currentNode = nextValue; traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); return; } } @@ -636,11 +680,11 @@ private void convertToJsonOrAnydataAsRestType(BXmlItem xmlItem, BString bElement BMap nextValue = ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); mapValue.put(bElementName, nextValue); - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; + analyzerData.nodesStack.push(analyzerData.currentNode); + analyzerData.currentNode = nextValue; handleAttributesRest(xmlItem, nextValue, restType, analyzerData.useSemanticEquality); traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - currentNode = analyzerData.nodesStack.pop(); + analyzerData.currentNode = (BMap) analyzerData.nodesStack.pop(); } private boolean isElementHasAttributes(BXmlItem xmlItem) { @@ -684,10 +728,11 @@ private void convertSequence(BXmlSequence xmlSequence, Type type, XmlAnalyzerDat } if (newSequence.size() == 1) { - currentNode = traverseXml(newSequence.get(0), type, analyzerData); + analyzerData.currentNode = (BMap) traverseXml(newSequence.get(0), type, analyzerData); return; } - currentNode = convertHeterogeneousSequence(newSequence, type, analyzerData); + analyzerData.currentNode = + (BMap) convertHeterogeneousSequence(newSequence, type, analyzerData); } private Object convertHeterogeneousSequence(List sequence, Type type, XmlAnalyzerData analyzerData) { @@ -700,7 +745,7 @@ private Object convertHeterogeneousSequence(List sequence, Type type, XmlA traverseXml(bXml, type, analyzerData); } } - return currentNode; + return analyzerData.currentNode; } private boolean isAllChildrenText(List sequence) { @@ -722,7 +767,7 @@ private Object handleCommentInMiddleOfText(List sequence, Type type, XmlAn textBuilder.append(bXml.toString()); } convertText(textBuilder.toString(), analyzerData); - return currentNode; + return analyzerData.currentNode; } private static boolean isCommentOrPi(BXml bxml) { @@ -744,14 +789,14 @@ private BXml validateRootElement(BXml xml, RecordType recordType, XmlAnalyzerDat analyzerData.rootRecord = recordType; boolean useSemanticEquality = analyzerData.useSemanticEquality; QualifiedName elementQName = DataUtils.getElementName(xmlItem.getQName(), useSemanticEquality); - analyzerData.rootElement = - DataUtils.validateAndGetXmlNameFromRecordAnnotation(recordType, recordType.getName(), elementQName, + DataUtils.validateAndGetXmlNameFromRecordAnnotation(recordType, recordType.getName(), elementQName, useSemanticEquality); DataUtils.validateTypeNamespace(elementQName.getPrefix(), elementQName.getNamespaceURI(), recordType); // Keep track of fields and attributes DataUtils.updateExpectedTypeStacks(recordType, analyzerData); - handleAttributes(xmlItem, (BMap) currentNode, analyzerData); + initializeXsdInformation(recordType, analyzerData); + handleAttributes(xmlItem, analyzerData.currentNode, analyzerData); analyzerData.arrayIndexes.push(new HashMap<>()); return xmlItem.getChildrenSeq(); } @@ -867,5 +912,77 @@ private HashSet findAllInnerElement(BXmlItem xmlItem) { } return elements; } + + private void validateElementInXsdSequenceOrElement(QualifiedName elemQName, + HashMap modelGroupInfo, BXmlItem xmlItem, + XmlAnalyzerData xmlAnalyzerData, QualifiedNameMap visitedFields, + QualifiedNameMap fieldMap) { + if (modelGroupInfo != null) { + for (Map.Entry entry : modelGroupInfo.entrySet()) { + ModelGroupInfo modelGroupValue = entry.getValue(); + String key = entry.getKey(); + QualifiedName qualifiedName = QualifiedNameFactory + .createQualifiedName("", key, "", xmlAnalyzerData.useSemanticEquality); + + if (modelGroupValue.isElementContains(elemQName.getLocalPart())) { + Object temp = null; + Field field = xmlAnalyzerData.rootRecord.getFields().get(key); + if (visitedFields.contains(qualifiedName)) { + temp = (xmlAnalyzerData.currentNode).get(StringUtils.fromString(key)); + } + xmlAnalyzerData.modelGroupStack.push(modelGroupValue); + visitedFields.put(qualifiedName, field); + fieldMap.remove(qualifiedName); + Type referredType = TypeUtils.getReferredType(field.getFieldType()); + updateNextRecordForXsd(xmlAnalyzerData, key, referredType, temp, xmlAnalyzerData.currentNode); + convertElement(xmlItem, xmlAnalyzerData); + return; + } + } + } + } + + private void updateNextRecordForXsd(XmlAnalyzerData xmlAnalyzerData, String fieldName, + Type fieldType, Object temp, Object currentNode) { + if (fieldType.getTag() == TypeTags.RECORD_TYPE_TAG) { + RecordType recType = (RecordType) fieldType; + DataUtils.updateExpectedTypeStacks((RecordType) fieldType, xmlAnalyzerData); + initializeXsdInformation(recType, xmlAnalyzerData); + xmlAnalyzerData.currentNode = updateNextMappingValue(recType, fieldName, fieldType, + (BMap) currentNode, xmlAnalyzerData); + xmlAnalyzerData.recordTypeStack.push(xmlAnalyzerData.rootRecord); + xmlAnalyzerData.rootRecord = recType; + return; + } + + if (fieldType.getTag() == TypeTags.ARRAY_TAG) { + Type elementType = TypeUtils.getReferredType(((ArrayType) fieldType).getElementType()); + if (temp == null) { + xmlAnalyzerData.arrayIndexes.peek().put(fieldName, 0); + ((BMap) currentNode).put(StringUtils.fromString(fieldName), + ValueCreator.createArrayValue((ArrayType) fieldType)); + } else { + HashMap indexes = xmlAnalyzerData.arrayIndexes.peek(); + indexes.put(fieldName, indexes.get(fieldName) + 1); + } + + if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { + RecordType recType = (RecordType) elementType; + DataUtils.updateExpectedTypeStacks((RecordType) elementType, xmlAnalyzerData); + initializeXsdInformation(recType, xmlAnalyzerData); + xmlAnalyzerData.recordTypeStack.push(xmlAnalyzerData.rootRecord); + xmlAnalyzerData.rootRecord = recType; + xmlAnalyzerData.currentNode = updateNextMappingValue(recType, fieldName, + fieldType, (BMap) currentNode, xmlAnalyzerData); + return; + } + } + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_XSD_ANNOTATION, fieldName, fieldType); + } + + private void validateModelGroupStackForRootElement(XmlAnalyzerData analyzerData) { + validateModelGroupStack(analyzerData, + QualifiedNameFactory.createQualifiedName("", "", "", analyzerData.useSemanticEquality), false); + } } } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ChoiceInfo.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ChoiceInfo.java new file mode 100644 index 00000000..23bf6078 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ChoiceInfo.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.xml.xsd; + +import io.ballerina.lib.data.xmldata.utils.Constants; +import io.ballerina.lib.data.xmldata.utils.DataUtils; +import io.ballerina.lib.data.xmldata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * Represent xsd choice. + * + * @since 1.1.0 + */ +public class ChoiceInfo implements ModelGroupInfo { + + private final Stack> xmlElementInfo; + private final Map remainingElementCount = new HashMap<>(); + private final Map minimumElementCount = new HashMap<>(); + private final Map maxElementCount = new HashMap<>(); + private final Map elementOptionality = new HashMap<>(); + public String fieldName; + public long minOccurs; + public long maxOccurs; + public int occurrences; + String lastElement = ""; + public final Set allElements = new HashSet<>(); + public final Set visitedElements = new HashSet<>(); + public final Set containElements = new HashSet<>(); + private final HashMap xmlElementNameMap; + private boolean isMiddleOfElement = false; + + + public ChoiceInfo(String fieldName, BMap element, RecordType fieldType, + Stack> xmlElementInfo) { + this.fieldName = fieldName; + if (element.containsKey(Constants.MIN_OCCURS)) { + this.minOccurs = element.getIntValue(Constants.MIN_OCCURS); + } else { + this.minOccurs = 1; + } + + if (element.containsKey(Constants.MAX_OCCURS)) { + this.maxOccurs = element.getIntValue(Constants.MAX_OCCURS); + } else { + this.maxOccurs = Math.max(this.minOccurs, 1); + } + this.occurrences = 0; + this.allElements.addAll(DataUtils.getXmlElementNames(fieldType)); + this.xmlElementNameMap = DataUtils.getXmlElementNameMap(fieldType); + reOrderElementNamesBasedOnTheNameAnnotation(); + this.xmlElementInfo = xmlElementInfo; + } + + public void updateOccurrences() { + this.occurrences++; + if (occurrences > maxOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, fieldName); + } + } + + @Override + public void validate() { + generateElementOptionalityMapIfNotPresent(); + markOtherElementsAsOptional(); + reset(); + } + + private void markOtherElementsAsOptional() { + if (!allElements.isEmpty()) { + HashMap elementInfo = xmlElementInfo.peek(); + for (String element : allElements) { + if (!containElements.contains(element)) { + ElementInfo eleInfo = elementInfo.get(element); + if (eleInfo != null) { + eleInfo.isInsideChoice = true; + } + } + } + } + } + + private void reset() { + this.visitedElements.clear(); + isMiddleOfElement = false; + this.remainingElementCount.putAll(this.maxElementCount); + this.lastElement = ""; + this.containElements.clear(); + } + + @Override + public void visit(String element, boolean isStartElement) { + generateElementOptionalityMapIfNotPresent(); + if (isMiddleOfElement && isStartElement) { + return; + } + + isMiddleOfElement = isStartElement; + + if (isStartElement) { + return; + } + + remainingElementCount.put(element, remainingElementCount.get(element) - 1); + containElements.add(element); + + if (visitedElements.contains(element)) { + if (remainingElementCount.get(element) == 0) { + remainingElementCount.putAll(maxElementCount); + visitedElements.remove(element); + } + return; + } + + if (allElements.contains(element)) { + int count = maxElementCount.get(element) - remainingElementCount.get(element); + if (count >= minimumElementCount.get(element)) { + this.visitedElements.add(element); + updateOccurrences(); + } + + if (remainingElementCount.get(element) == 0) { + remainingElementCount.putAll(maxElementCount); + visitedElements.remove(element); + } + return; + } + + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_ELEMENT_FOUND, xmlElementNameMap.get(element), fieldName); + } + + @Override + public boolean isElementContains(String elementName) { + return allElements.contains(elementName); + } + + @Override + public boolean isMiddleOfModelGroup() { + return isMiddleOfElement; + } + + @Override + public boolean predictStartNewModelGroup(String element) { + generateElementOptionalityMapIfNotPresent(); + return !isMiddleOfElement && !isElementContains(element); + } + + public void validateMinOccurrences() { + if (this.occurrences < this.minOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, fieldName); + } + } + + private void generateElementOptionalityMapIfNotPresent() { + if (elementOptionality.isEmpty()) { + if (!xmlElementInfo.isEmpty()) { + allElements.forEach(element -> { + HashMap elementInfo = xmlElementInfo.peek(); + if (elementInfo.containsKey(element)) { + ElementInfo info = elementInfo.get(element); + elementOptionality.put(element, info.minOccurs == 0); + remainingElementCount.put(element, (int) info.maxOccurs); + maxElementCount.put(element, (int) info.maxOccurs); + minimumElementCount.put(element, (int) info.minOccurs); + } else { + elementOptionality.put(element, false); + remainingElementCount.put(element, 1); + maxElementCount.put(element, 1); + minimumElementCount.put(element, 1); + } + }); + } else { + allElements.forEach(element -> { + elementOptionality.put(element, false); + remainingElementCount.put(element, 1); + maxElementCount.put(element, 1); + minimumElementCount.put(element, 1); + }); + } + } + } + + private void reOrderElementNamesBasedOnTheNameAnnotation() { + allElements.forEach(element -> { + if (!xmlElementNameMap.containsKey(element)) { + xmlElementNameMap.put(element, element); + } + }); + } + + public long getMinOccurs() { + return minOccurs; + } + + public long getMaxOccurs() { + return maxOccurs; + } + + public String getFieldName() { + return fieldName; + } +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ElementInfo.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ElementInfo.java new file mode 100644 index 00000000..be7f6ff4 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ElementInfo.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.xml.xsd; + +import io.ballerina.lib.data.xmldata.utils.Constants; +import io.ballerina.lib.data.xmldata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +/** + * Represent xsd element. + * + * @since 1.1.0 + */ +public class ElementInfo { + String name; + public String fieldName; + public long minOccurs; + public long maxOccurs; + public int occurrences; + + public boolean isInsideChoice = false; + + public ElementInfo(String name, String fieldName, BMap element) { + this.name = name; + this.fieldName = fieldName; + if (element.containsKey(Constants.MIN_OCCURS)) { + this.minOccurs = element.getIntValue(Constants.MIN_OCCURS); + } else { + this.minOccurs = 1; + } + + if (element.containsKey(Constants.MAX_OCCURS)) { + this.maxOccurs = element.getIntValue(Constants.MAX_OCCURS); + } else { + this.maxOccurs = Math.max(this.minOccurs, 1); + } + this.occurrences = 0; + } + + public void updateOccurrences() { + this.occurrences++; + if (this.occurrences > this.maxOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, name); + } + } + + public void validate() throws BError { + validateMinOccurrences(); + } + + private void validateMinOccurrences() throws BError { + if (!isInsideChoice && this.occurrences < this.minOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, name); + } + } +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ModelGroupInfo.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ModelGroupInfo.java new file mode 100644 index 00000000..bf0f612e --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/ModelGroupInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.xml.xsd; + +/** + * Represent the xsd model group. + * + * @since 1.1.0 + */ +public interface ModelGroupInfo { + void validate(); + void visit(String element, boolean isStartElement); + boolean isElementContains(String elementName); + boolean isMiddleOfModelGroup(); + boolean predictStartNewModelGroup(String element); + void validateMinOccurrences(); + long getMinOccurs(); + long getMaxOccurs(); + String getFieldName(); +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/SequenceInfo.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/SequenceInfo.java new file mode 100644 index 00000000..770cc5f1 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/xsd/SequenceInfo.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.data.xmldata.xml.xsd; + +import io.ballerina.lib.data.xmldata.utils.Constants; +import io.ballerina.lib.data.xmldata.utils.DataUtils; +import io.ballerina.lib.data.xmldata.utils.DiagnosticErrorCode; +import io.ballerina.lib.data.xmldata.utils.DiagnosticLog; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +/** + * Represent xsd sequence. + * + * @since 1.1.0 + */ +public class SequenceInfo implements ModelGroupInfo { + public String fieldName; + public long minOccurs; + public long maxOccurs; + public int occurrences; + private final Map remainingElementCount = new HashMap<>(); + private final Map minimumElementCount = new HashMap<>(); + private final Map maxElementCount = new HashMap<>(); + private final Map elementOptionality = new HashMap<>(); + private final List allElements = new ArrayList<>(); + int currentIndex = 0; + int elementCount; + String lastElement = ""; + private boolean isCompleted = false; + private boolean isMiddleOfElement = false; + private final Stack> xmlElementInfo; + private HashMap xmlElementNameMap = new HashMap<>(); + + public SequenceInfo(String fieldName, BMap element, RecordType fieldType, + Stack> xmlElementInfo) { + this.fieldName = fieldName; + if (element.containsKey(Constants.MIN_OCCURS)) { + this.minOccurs = element.getIntValue(Constants.MIN_OCCURS); + } else { + this.minOccurs = 1; + } + + if (element.containsKey(Constants.MAX_OCCURS)) { + this.maxOccurs = element.getIntValue(Constants.MAX_OCCURS); + } else { + this.maxOccurs = Math.max(this.minOccurs, 1); + } + this.occurrences = 0; + this.xmlElementInfo = xmlElementInfo; + updateUnvisitedElementsBasedOnPriorityOrder(fieldType); + this.xmlElementNameMap = DataUtils.getXmlElementNameMap(fieldType); + reOrderElementNamesBasedOnTheNameAnnotation(); + this.elementCount = allElements.size(); + } + + public void updateOccurrences() { + this.occurrences++; + if (this.occurrences > this.maxOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES, fieldName); + } + } + + public void validateMinOccurrences() { + if (this.occurrences < this.minOccurs) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_LESS_THAN_MIN_REQUIRED_TIMES, fieldName); + } + } + + @Override + public void validate() { + generateElementOptionalityMapIfNotPresent(); + validateCompletedSequences(); + reset(); + } + + private void reset() { + this.isCompleted = false; + this.isMiddleOfElement = false; + this.currentIndex = 0; + this.remainingElementCount.putAll(this.maxElementCount); + this.lastElement = ""; + } + + @Override + public void visit(String element, boolean isStartElement) { + generateElementOptionalityMapIfNotPresent(); + if (isMiddleOfElement && isStartElement) { + return; + } + + isMiddleOfElement = isStartElement; + if (isStartElement) { + isCompleted = false; + return; + } + + checkElementOrderAndUpdateElementOccurences(element); + } + + @Override + public boolean isElementContains(String elementName) { + return this.allElements.contains(elementName); + } + + @Override + public boolean isMiddleOfModelGroup() { + return isMiddleOfElement; + } + + @Override + public boolean predictStartNewModelGroup(String element) { + generateElementOptionalityMapIfNotPresent(); + if (!isElementContains(element)) { + return false; + } + + boolean isFirstElement = element.equals(allElements.get(0)); + if (isFirstElement && currentIndex == 0 && remainingElementCount.get(allElements.get(0)) > 0) { + return false; + } + + return !isMiddleOfElement && isFirstElement + && (isCompleted || containsAllOptionalElements()) + && !(lastElement.equals(element) && remainingElementCount.get(element) > 0); + } + + private void validateCompletedSequences() { + if (!isCompleted && !containsAllOptionalElements()) { + throw DiagnosticLog.error(DiagnosticErrorCode + .REQUIRED_ELEMENT_NOT_FOUND, getUnvisitedElements(), fieldName); + } + updateOccurrences(); + } + + private boolean containsAllOptionalElements() { + for (int i = currentIndex; i < this.elementCount; i++) { + if (!elementOptionality.get(allElements.get(i))) { + return false; + } + } + return true; + } + + private void checkElementOrderAndUpdateElementOccurences(String element) { + String nextElement; + boolean isLastElement = false; + + if (element.equals(lastElement)) { + nextElement = lastElement; + isLastElement = true; + } else { + nextElement = allElements.get(currentIndex == this.elementCount ? currentIndex - 1 : currentIndex); + } + + while (!nextElement.equals(element)) { + if (!elementOptionality.get(nextElement)) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCORRECT_ELEMENT_ORDER, + xmlElementNameMap.get(element), fieldName); + } + currentIndex++; + nextElement = allElements.get(currentIndex); + + if (currentIndex == this.elementCount) { + throw DiagnosticLog.error(DiagnosticErrorCode.INCORRECT_ELEMENT_ORDER, + xmlElementNameMap.get(element), fieldName); + } + } + + if (remainingElementCount.get(nextElement) == 0) { + throw DiagnosticLog.error(DiagnosticErrorCode.ELEMENT_OCCURS_MORE_THAN_MAX_ALLOWED_TIMES_IN_SEQUENCES, + xmlElementNameMap.get(nextElement), fieldName); + } else { + remainingElementCount.put(element, remainingElementCount.get(nextElement) - 1); + int elementCount = maxElementCount.get(element) - remainingElementCount.get(element); + + if (elementCount >= minimumElementCount.get(element) && !isLastElement + && currentIndex != this.elementCount) { + currentIndex++; + } else { + if (elementCount == 1) { + currentIndex++; + } + } + + if (currentIndex == this.elementCount && elementCount >= minimumElementCount.get(element)) { + isCompleted = true; + } + } + lastElement = nextElement; + } + + private String getUnvisitedElements() { + StringBuilder unvisitedElementsStr = new StringBuilder(); + allElements.subList(currentIndex, this.elementCount).forEach(element -> { + if (!elementOptionality.get(element)) { + unvisitedElementsStr.append(xmlElementNameMap.get(element)).append(", "); + } + }); + String result = unvisitedElementsStr.toString(); + result = result.substring(0, result.length() - 2); + return result; + } + + private void updateUnvisitedElementsBasedOnPriorityOrder(RecordType fieldType) { + this.allElements.addAll(DataUtils.getXsdSequencePriorityOrder(fieldType, true).entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .toList()); + + this.currentIndex = 0; + } + + private void generateElementOptionalityMapIfNotPresent() { + if (elementOptionality.isEmpty()) { + if (!xmlElementInfo.isEmpty()) { + allElements.forEach(element -> { + HashMap elementInfo = xmlElementInfo.peek(); + if (elementInfo.containsKey(element)) { + ElementInfo info = elementInfo.get(element); + elementOptionality.put(element, info.minOccurs == 0); + remainingElementCount.put(element, (int) info.maxOccurs); + maxElementCount.put(element, (int) info.maxOccurs); + minimumElementCount.put(element, (int) info.minOccurs); + } else { + elementOptionality.put(element, false); + remainingElementCount.put(element, 1); + maxElementCount.put(element, 1); + minimumElementCount.put(element, 1); + } + }); + } else { + allElements.forEach(element -> { + elementOptionality.put(element, false); + remainingElementCount.put(element, 1); + maxElementCount.put(element, 1); + minimumElementCount.put(element, 1); + }); + } + } + } + + private void reOrderElementNamesBasedOnTheNameAnnotation() { + xmlElementNameMap.forEach((key, value) -> { + if (allElements.contains(value)) { + allElements.set(allElements.indexOf(value), key); + } + }); + allElements.forEach(element -> { + if (!xmlElementNameMap.containsKey(element)) { + xmlElementNameMap.put(element, element); + } + }); + } + + public long getMinOccurs() { + return minOccurs; + } + + public long getMaxOccurs() { + return maxOccurs; + } + + public String getFieldName() { + return fieldName; + } +} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 4631e40c..4b0d66a9 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -23,5 +23,7 @@ requires java.xml; requires junit; requires org.apache.commons.lang3; + requires io.ballerina.lang.xml; exports io.ballerina.lib.data.xmldata.xml; + requires io.ballerina.lang.map; } diff --git a/native/src/main/resources/error.properties b/native/src/main/resources/error.properties index 65e56eef..f155b3f1 100644 --- a/native/src/main/resources/error.properties +++ b/native/src/main/resources/error.properties @@ -76,3 +76,33 @@ error.cannot.convert.source.into.expected.type=\ error.field.cannot.convert.into.type=\ field ''{0}'' cannot be converted into the type ''{1}'' + +error.element.occurs.more.than.max.allowed.times=\ + ''{0}'' occurs more than the max allowed times + +error.element.occurs.less.than.min.required.times=\ + ''{0}'' occurs less than the min required times + +error.invalid.element.found=\ + Unexpected element ''{0}'' found in ''{1}'' + +error.required.element.not.found=\ + Element(s) ''{0}'' is not found in ''{1}'' + +error.incorrect.element.order=\ + Element ''{0}'' is not in the correct order in ''{1}'' + +error.element.occurs.more.than.max.allowed.times.in.sequence=\ + ''{0}'' occurs more than the max allowed times in ''{1}'' + +error.invalid.sequence.annotation=\ + Cannot include Sequence annotation into ''{0}'' of type ''{1}'' + +error.invalid.choice.annotation=\ + Cannot include Choice annotation into ''{0}'' of type ''{1}'' + +error.invalid.xsd.annotation=\ + Cannot include xsd annotation into ''{0}'' of type ''{1}'' + +error.invalid.xml=\ + Invalid XML found: ''{0}''