diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index b424cf2..05d726b 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -14,3 +14,9 @@ groupId = "io.ballerina.lib" artifactId = "data-native" version = "1.0.0" path = "../native/build/libs/data.xmldata-native-1.0.0-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "constraint-native" +version = "1.5.0" +path = "./lib/constraint-native-1.5.0.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index c3c6683..1209aa9 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,14 +7,28 @@ dependencies-toml-version = "2" distribution-version = "2201.10.0-20240724-114000-40e856f7" +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "constraint", moduleName = "constraint"} +] + [[package]] org = "ballerina" name = "data.xmldata" version = "1.0.0" dependencies = [ + {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "test"} + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "time"} ] modules = [ {org = "ballerina", packageName = "data.xmldata", moduleName = "data.xmldata"} @@ -99,3 +113,15 @@ modules = [ {org = "ballerina", packageName = "test", moduleName = "test"} ] +[[package]] +org = "ballerina" +name = "time" +version = "2.4.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "time", moduleName = "time"} +] + diff --git a/ballerina/build.gradle b/ballerina/build.gradle index c63d338..4fc312a 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -66,10 +66,25 @@ ballerina { testCoverageParam = "--code-coverage --coverage-format=xml --includes=io.ballerina.lib.data.xmldata*:ballerina.*" } +configurations { + externalJars +} + +dependencies { + externalJars(group: 'io.ballerina.stdlib', name: 'constraint-native', version: "${stdlibConstraintVersion}") { + transitive = false + } +} + task updateTomlFiles { doLast { + def stdlibDependentConstraintNativeVersion = stdlibConstraintVersion + def stdlibDependentConstraintVersion = stripBallerinaExtensionVersion("${stdlibDependentConstraintNativeVersion}") + def newConfig = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) newConfig = newConfig.replace("@toml.version@", tomlVersion) + newConfig = newConfig.replace("@stdlib.constraintnative.version@", stdlibDependentConstraintNativeVersion) + newConfig = newConfig.replace("@constraint.version@", stdlibDependentConstraintVersion) ballerinaTomlFile.text = newConfig def newCompilerPluginToml = compilerPluginTomlFilePlaceHolder.text.replace("@project.version@", project.version) diff --git a/ballerina/tests/constraint_annotation_test.bal b/ballerina/tests/constraint_annotation_test.bal new file mode 100644 index 0000000..ee01362 --- /dev/null +++ b/ballerina/tests/constraint_annotation_test.bal @@ -0,0 +1,570 @@ +// 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/constraint; +import ballerina/test; +import ballerina/time; + +public type ValidationPerson record {| + @constraint:String { + maxLength: 5, + minLength: 2 + } + string name; + @constraint:Int { + minValueExclusive: 5 + } + int age; + @constraint:Float { + minValue: 150, + maxValue: 185.20, + maxFractionDigits: 2 + } + float height; + @constraint:Date { + option: { + value: "PAST", + message: "Date of birth should be past value" + }, + message: "Invalid date found for date of birth" + } + time:Date dob; + Family family; +|}; + +public type Family record {| + @constraint:Array { + maxLength: 2, + minLength: 1 + } + string[] members; + @constraint:Int { + maxDigits: 4 + } + int id; +|}; + +@test:Config { + groups: ["constraint-validation"] +} +function testValidConstraintAnnotationForParseString() returns error? { + string xmlStr = string ` + + John + 6 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `; + + ValidationPerson person = check parseString(xmlStr); + test:assertEquals(person.name, "John"); + test:assertEquals(person.age, 6); + test:assertEquals(person.height, 180.20); + test:assertEquals(person.dob.year, 1990); + test:assertEquals(person.dob.month, 12); + test:assertEquals(person.dob.day, 31); + test:assertEquals(person.family.id, 2221); + test:assertEquals(person.family.members.length(), 2); + test:assertEquals(person.family.members[0], "John"); + test:assertEquals(person.family.members[1], "Doe"); +} + +@constraint:Array { + length: 2 +} +public type Weight decimal[]; + +public type ValidationItem record {| + Weight weight; +|}; + +@test:Config { + groups: ["constraint-validation"], + dataProvider: invalidConstraintAnnotation +} +function testInvalidConstraintAnnotationForParseString(string sourceData, typedesc expType, string expectedError) { + anydata|Error err = parseString(sourceData, {}, expType); + test:assertEquals(err is Error, true); + test:assertEquals((err).message(), expectedError); +} + +function invalidConstraintAnnotation() returns [string, typedesc, string][] { + return [ + [ + string ` + + John Doe + 6 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.name:maxLength' constraint(s)." + ], + [ + string ` + + John + 4 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.age:minValueExclusive' constraint(s)." + ], + [ + string ` + + John + 6 + 185.21 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.height:maxValue' constraint(s)." + ], + [ + string ` + + John + 6 + 167.252 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.height:maxFractionDigits' constraint(s)." + ], + [ + string ` + + John + 6 + 167.25 + + 5000 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Date of birth should be past value." + ], + [ + string ` + + John + 6 + 167.25 + + 1999 + 12 + 31 + + + 22213 + John + Doe + Ross + + + `, + ValidationPerson, + "Validation failed for '$.family.id:maxDigits','$.family.members:maxLength' constraint(s)." + ], + [ + string ` + + 1.2 + 2.3 + 3.4 + + `, + ValidationItem, + "Validation failed for '$.weight:length' constraint(s)." + ] + ]; +} + +@test:Config { + groups: ["constraint-validation"], + dataProvider: disableConstraintValidation +} +function testDisableConstraintValidationForParseString(string sourceData, typedesc expType, anydata result) returns error? { + anydata err = check parseString(sourceData, {enableConstraintValidation: false}, expType); + test:assertEquals(err, result); +} + +function disableConstraintValidation() returns [string, typedesc, anydata][] { + return [ + [ + string ` + + John Doe + 4 + 185.215 + + 5000 + 12 + 31 + + + 22213 + John + Doe + Ross + + + `, + ValidationPerson, + { + "name": "John Doe", + "age": 4, + "height": 185.215, + "dob": { + "year": 5000, + "month": 12, + "day": 31 + }, + "family": { + "id": 22213, + "members": [ + "John", + "Doe", + "Ross" + ] + } + } + ], + [ + string ` + + 1.2 + 2.3 + 3.4 + + `, + ValidationItem, + {"weight": [1.2, 2.3, 3.4]} + ] + ]; +} + +@test:Config { + groups: ["constraint-validation"] +} +function testValidConstraintAnnotationForParseAsType() returns error? { + xml xmlVal = xml ` + + John + 6 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `; + + ValidationPerson person = check parseAsType(xmlVal); + test:assertEquals(person.name, "John"); + test:assertEquals(person.age, 6); + test:assertEquals(person.height, 180.20); + test:assertEquals(person.dob.year, 1990); + test:assertEquals(person.dob.month, 12); + test:assertEquals(person.dob.day, 31); + test:assertEquals(person.family.members.length(), 2); + test:assertEquals(person.family.members[0], "John"); + test:assertEquals(person.family.members[1], "Doe"); +} + +@test:Config { + groups: ["constraint-validation"], + dataProvider: invalidConstraintAnnotationForParseAsType +} +function testInvalidConstraintAnnotationForParseAsType(xml sourceData, typedesc expType, string expectedError) { + anydata|Error err = parseAsType(sourceData, {}, expType); + test:assertEquals(err is Error, true); + test:assertEquals((err).message(), expectedError); +} + +function invalidConstraintAnnotationForParseAsType() returns [xml, typedesc, string][] { + return [ + [ + xml ` + + John Doe + 6 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.name:maxLength' constraint(s)." + ], + [ + xml ` + + John + 4 + 180.20 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.age:minValueExclusive' constraint(s)." + ], + [ + xml ` + + John + 6 + 185.21 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.height:maxValue' constraint(s)." + ], + [ + xml ` + + John + 6 + 167.252 + + 1990 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Validation failed for '$.height:maxFractionDigits' constraint(s)." + ], + [ + xml ` + + John + 6 + 167.25 + + 5000 + 12 + 31 + + + 2221 + John + Doe + + + `, + ValidationPerson, + "Date of birth should be past value." + ], + [ + xml ` + + John + 6 + 167.25 + + 1999 + 12 + 31 + + + 22213 + John + Doe + Ross + + + `, + ValidationPerson, + "Validation failed for '$.family.id:maxDigits','$.family.members:maxLength' constraint(s)." + ], + [ + xml ` + + 1.2 + 2.3 + 3.4 + + `, + ValidationItem, + "Validation failed for '$.weight:length' constraint(s)." + ] + ]; +} + +@test:Config { + groups: ["constraint-validation"], + dataProvider: disableConstraintValidationForParseAsType +} +function testDisableConstraintValidationForParseAsType(xml sourceData, typedesc expType, anydata result) returns error? { + anydata err = check parseAsType(sourceData, {enableConstraintValidation: false}, expType); + test:assertEquals(err, result); +} + +function disableConstraintValidationForParseAsType() returns [xml, typedesc, anydata][] { + return [ + [ + xml ` + + John Doe + 4 + 185.215 + + 5000 + 12 + 31 + + + 22213 + John + Doe + Ross + + + `, + ValidationPerson, + { + "name": "John Doe", + "age": 4, + "height": 185.215, + "dob": { + "year": 5000, + "month": 12, + "day": 31 + }, + "family": { + "id": 22213, + "members": [ + "John", + "Doe", + "Ross" + ] + } + } + ], + [ + xml ` + + 1.2 + 2.3 + 3.4 + + `, + ValidationItem, + {"weight": [1.2, 2.3, 3.4]} + ] + ]; +} diff --git a/ballerina/xml_api.bal b/ballerina/xml_api.bal index d2f7f98..697be47 100644 --- a/ballerina/xml_api.bal +++ b/ballerina/xml_api.bal @@ -69,6 +69,8 @@ public type SourceOptions record {| boolean allowDataProjection = true; # choose between semantic and syntactic equality boolean useSemanticEquality = true; + # enable or disable constraint validation + boolean enableConstraintValidation = true; |}; # Represents the error type of the ballerina/data.xmldata module. This error type represents any error that can occur diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 5f14962..e881ae9 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -14,3 +14,9 @@ groupId = "io.ballerina.lib" artifactId = "data-native" version = "@toml.version@" path = "../native/build/libs/data.xmldata-native-@project.version@.jar" + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "constraint-native" +version = "@constraint.version@" +path = "./lib/constraint-native-@stdlib.constraintnative.version@.jar" diff --git a/build.gradle b/build.gradle index a3939ce..a272ee8 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,8 @@ subprojects { dependencies { /* Standard libraries */ ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${stdlibIoVersion}" + ballerinaStdLibs "io.ballerina.stdlib:constraint-ballerina:${stdlibConstraintVersion}" + ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${stdlibTimeVersion}" } } diff --git a/gradle.properties b/gradle.properties index 89a09c9..8f52d97 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,4 +12,7 @@ githubJohnrengelmanShadowVersion=8.1.1 underCouchDownloadVersion=4.0.4 researchgateReleaseVersion=2.8.0 ballerinaGradlePluginVersion=2.0.1 + stdlibIoVersion=1.6.0 +stdlibTimeVersion=2.4.0 +stdlibConstraintVersion=1.5.0 diff --git a/native/build.gradle b/native/build.gradle index 250c984..ceb83f2 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: 'io.ballerina.stdlib', name: 'constraint-native', version: "${stdlibConstraintVersion}" } checkstyle { diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/io/DataReaderTask.java b/native/src/main/java/io/ballerina/lib/data/xmldata/io/DataReaderTask.java index 4ec6d0f..126d8ad 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/io/DataReaderTask.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/io/DataReaderTask.java @@ -87,7 +87,7 @@ public void run() { DataReaderTask.ResultConsumer resultConsumer = new DataReaderTask.ResultConsumer<>(future); try (var byteBlockSteam = new BallerinaByteBlockInputStream(env, iteratorObj, resolveNextMethod(iteratorObj), resolveCloseMethod(iteratorObj), resultConsumer)) { - Object result = XmlParser.parse(new InputStreamReader(byteBlockSteam), options, typed.getDescribingType()); + Object result = XmlParser.parse(new InputStreamReader(byteBlockSteam), options, typed); future.complete(result); } catch (Exception e) { future.complete(DiagnosticLog.error(DiagnosticErrorCode.STREAM_BROKEN, e.getMessage())); 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 e263ec7..55dea45 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 @@ -37,33 +37,37 @@ public class Constants { private Constants() {} - public static final String UNDERSCORE = "_"; - public static final String COLON = ":"; - 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 int NS_PREFIX_BEGIN_INDEX = BXmlItem.XMLNS_NS_URI_PREFIX.length(); + public static final int DEFAULT_TYPE_FLAG = 2049; + public static final String FIELD = "$field$."; public static final String NAMESPACE = "Namespace"; - public static final String MODULE_NAME = "ballerina/data.xmldata"; - public static final BString URI = StringUtils.fromString("uri"); - public static final BString PREFIX = StringUtils.fromString("prefix"); + public static final String UNDERSCORE = "_"; + public static final String COLON = ":"; public static final String ATTRIBUTE = "Attribute"; - public static final int DEFAULT_TYPE_FLAG = 2049; public static final String NAME = "Name"; - public static final BString VALUE = StringUtils.fromString("value"); public static final String CONTENT = "#content"; public static final String FIELD_REGEX = "\\$field\\$\\."; - public static final int NS_PREFIX_BEGIN_INDEX = BXmlItem.XMLNS_NS_URI_PREFIX.length(); public static final String RECORD = "record"; public static final String RECORD_OR_MAP = "record or map"; public static final String ANON_TYPE = "$anonType$"; - public static final QualifiedName EXIT_REST_POINT = QualifiedNameFactory.createQualifiedName("", - "$exitRestPoint$", "", true); - public static final BString ATTRIBUTE_PREFIX = StringUtils.fromString("attributePrefix"); - public static final BString TEXT_FIELD_NAME = StringUtils.fromString("textFieldName"); - public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); - public static final BString USE_SEMANTIC_EQUALITY = StringUtils.fromString("useSemanticEquality"); public static final String RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX = "[^a-zA-Z\\d\s_]"; public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$"; + public static final String MODULE_NAME = "ballerina/data.xmldata"; public static final String REGEXP_MODULE_NAME = "lang.regexp"; public static final String REGEXP_TYPE_NAME = "RegExp"; + + public static final BString URI = StringUtils.fromString("uri"); + 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 TEXT_FIELD_NAME = StringUtils.fromString("textFieldName"); + 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 QualifiedName EXIT_REST_POINT = QualifiedNameFactory.createQualifiedName("", + "$exitRestPoint$", "", true); } 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 378ae6b..0dccacd 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 @@ -43,6 +43,7 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.constraint.Constraints; import java.util.ArrayList; import java.util.HashMap; @@ -873,6 +874,27 @@ private static boolean isAttributeAnnotationKey(String key) { return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.ATTRIBUTE); } + public static Object validateConstraints(Object convertedValue, BTypedesc typed, boolean requireValidation) { + if (!requireValidation) { + return convertedValue; + } + + Object result = Constraints.validate(convertedValue, typed); + if (result instanceof BError bError) { + return DiagnosticLog.createXmlError(getPrintableErrorMsg(bError)); + } + return convertedValue; + } + + private static String getPrintableErrorMsg(BError err) { + String errorMsg = err.getMessage() != null ? err.getMessage() : ""; + Object details = err.getDetails(); + if (details != null && !details.toString().equals("{}")) { + errorMsg += ", " + details; + } + return errorMsg; + } + /** * Holds data required for the traversing. * diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticLog.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticLog.java index 4cfdd33..9243cdb 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticLog.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DiagnosticLog.java @@ -38,7 +38,7 @@ public class DiagnosticLog { public static BError error(DiagnosticErrorCode code, Object... args) { String msg = formatMessage(code, args); - return getXmlError(msg); + return createXmlError(msg); } private static String formatMessage(DiagnosticErrorCode code, Object[] args) { @@ -46,7 +46,7 @@ private static String formatMessage(DiagnosticErrorCode code, Object[] args) { return MessageFormat.format(msgKey, args); } - public static BError getXmlError(String message) { + public static BError createXmlError(String message) { return ErrorCreator.createError(ModuleUtils.getModule(), ERROR, StringUtils.fromString(message), null, null); } 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 dfcc6de..22de127 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 @@ -45,14 +45,14 @@ public class Native { public static Object parseAsType(BXml xml, BMap options, BTypedesc typed) { try { - return XmlTraversal.traverse(xml, options, typed.getDescribingType()); + return XmlTraversal.traverse(xml, options, typed); } catch (Exception e) { - return DiagnosticLog.getXmlError(e.getMessage()); + return DiagnosticLog.createXmlError(e.getMessage()); } } public static Object parseString(BString xml, BMap options, BTypedesc typed) { try { - return XmlParser.parse(new StringReader(xml.getValue()), options, typed.getDescribingType()); + return XmlParser.parse(new StringReader(xml.getValue()), options, typed); } catch (Exception e) { return DiagnosticLog.error(DiagnosticErrorCode.XML_PARSE_ERROR, e.getMessage()); } @@ -60,8 +60,7 @@ public static Object parseString(BString xml, BMap options, BTy public static Object parseBytes(BArray xml, BMap options, BTypedesc typed) { try { - return XmlParser.parse(new InputStreamReader(new ByteArrayInputStream(xml.getBytes())), options, - typed.getDescribingType()); + return XmlParser.parse(new InputStreamReader(new ByteArrayInputStream(xml.getBytes())), options, typed); } catch (Exception e) { return DiagnosticLog.error(DiagnosticErrorCode.XML_PARSE_ERROR, e.getMessage()); } 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 8ebd985..a310270 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 @@ -39,6 +39,7 @@ 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 java.io.Reader; import java.util.ArrayList; @@ -54,6 +55,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import static io.ballerina.lib.data.xmldata.utils.Constants.ENABLE_CONSTRAINT_VALIDATION; import static javax.xml.stream.XMLStreamConstants.CDATA; import static javax.xml.stream.XMLStreamConstants.CHARACTERS; import static javax.xml.stream.XMLStreamConstants.COMMENT; @@ -93,6 +95,15 @@ public XmlParser(Reader stringReader) { } } + public static Object parse(Reader reader, BMap options, BTypedesc typed) { + Object convertedValue = parse(reader, options, typed.getDescribingType()); + if (convertedValue instanceof BError) { + return convertedValue; + } + return DataUtils.validateConstraints(convertedValue, typed, + (Boolean) options.get(ENABLE_CONSTRAINT_VALIDATION)); + } + public static Object parse(Reader reader, BMap options, Type type) { try { XmlParserData xmlParserData = new XmlParserData(); @@ -116,9 +127,9 @@ private static void updateOptions(BMap options, XmlParserData x private void handleXMLStreamException(Exception e) { String reason = e.getCause() == null ? e.getMessage() : e.getCause().getMessage(); if (reason == null) { - throw DiagnosticLog.getXmlError(PARSE_ERROR); + throw DiagnosticLog.createXmlError(PARSE_ERROR); } - throw DiagnosticLog.getXmlError(PARSE_ERROR_PREFIX + reason); + throw DiagnosticLog.createXmlError(PARSE_ERROR_PREFIX + reason); } public Object parse(Type type, XmlParserData xmlParserData) { @@ -159,7 +170,7 @@ public Object parse(XmlParserData xmlParserData) { readNext = parseXmlElements(next, xmlParserData); } } catch (NumberFormatException e) { - throw DiagnosticLog.getXmlError(PARSE_ERROR_PREFIX + e); + throw DiagnosticLog.createXmlError(PARSE_ERROR_PREFIX + e); } catch (BError e) { throw e; } catch (Exception e) { 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 e0c2a27..21a0f0e 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 @@ -37,8 +37,10 @@ import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; 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 io.ballerina.runtime.api.values.BXmlItem; import io.ballerina.runtime.api.values.BXmlSequence; @@ -52,6 +54,8 @@ import javax.xml.namespace.QName; +import static io.ballerina.lib.data.xmldata.utils.Constants.ENABLE_CONSTRAINT_VALIDATION; + /** * Convert Xml value to a ballerina record. * @@ -61,6 +65,15 @@ public class XmlTraversal { private static final ThreadLocal tlXmlTree = ThreadLocal.withInitial(XmlTree::new); + public static Object traverse(BXml xml, BMap options, BTypedesc typed) { + Object convertedValue = traverse(xml, options, typed.getDescribingType()); + if (convertedValue instanceof BError) { + return convertedValue; + } + return DataUtils.validateConstraints(convertedValue, typed, + (Boolean) options.get(ENABLE_CONSTRAINT_VALIDATION)); + } + public static Object traverse(BXml xml, BMap options, Type type) { XmlTree xmlTree = tlXmlTree.get(); return xmlTree.traverseXml(xml, options, type); diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 8fd5c1b..4631e40 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -16,9 +16,10 @@ * under the License. */ -module io.ballerina.stdlib.data { +module io.ballerina.lib.data { requires io.ballerina.runtime; requires io.ballerina.lang.value; + requires io.ballerina.stdlib.constraint; requires java.xml; requires junit; requires org.apache.commons.lang3;