Skip to content

Commit

Permalink
Merge pull request #23 from prakanth97/issue_6557
Browse files Browse the repository at this point in the history
Add constraint validation
  • Loading branch information
SasinduDilshara authored Jul 3, 2024
2 parents 552cf40 + 6185ec6 commit d894eda
Show file tree
Hide file tree
Showing 21 changed files with 436 additions and 26 deletions.
6 changes: 6 additions & 0 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ path = "./lib/accessors-smart-2.4.7.jar"
groupId = "net.minidev.json"
artifactId = "accessors-smart"
version = "2.4.7"

[[platform.java17.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "constraint-native"
version = "1.5.0"
path = "./lib/constraint-native-1.5.0.jar"
19 changes: 18 additions & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,33 @@
dependencies-toml-version = "2"
distribution-version = "2201.9.0"

[[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.jsondata"
version = "0.1.1"
dependencies = [
{org = "ballerina", name = "constraint"},
{org = "ballerina", name = "file"},
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.float"},
{org = "ballerina", name = "lang.int"},
{org = "ballerina", name = "lang.object"},
{org = "ballerina", name = "lang.value"},
{org = "ballerina", name = "test"}
{org = "ballerina", name = "test"},
{org = "ballerina", name = "time"}
]
modules = [
{org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"}
Expand Down Expand Up @@ -168,4 +182,7 @@ scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
modules = [
{org = "ballerina", packageName = "time", moduleName = "time"}
]

8 changes: 8 additions & 0 deletions ballerina/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,23 @@ dependencies {
externalJars(group: 'net.minidev', name: 'accessors-smart', version: "${javaAccessorsSmartVersion}") {
transitive = false
}
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("@jsonpath.version@", project.javaJsonPathVersion)
newConfig = newConfig.replace("@jsonsmart.version@", project.javaJsonSmartVersion)
newConfig = newConfig.replace("@accessors.version@", project.javaAccessorsSmartVersion)
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)
Expand Down
2 changes: 2 additions & 0 deletions ballerina/json_api.bal
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,15 @@ public isolated function prettify(json value, int indentation = 4) returns strin
# Represent the options that can be used to modify the behaviour of the projection.
#
# + allowDataProjection - Enable or disable projection
# + enableConstraintValidation - Enable or disable constraint validation
public type Options record {
record {
# If `true`, nil values will be considered as optional fields in the projection.
boolean nilAsOptionalField = false;
# If `true`, absent fields will be considered as nilable types in the projection.
boolean absentAsNilableType = false;
}|false allowDataProjection = {};
boolean enableConstraintValidation = true;
};

# Defines the name of the JSON Object key.
Expand Down
293 changes: 293 additions & 0 deletions ballerina/tests/constraint_annotation_test.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// 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;
|};

@test:Config {
groups: ["constraint-validation"]
}
function testValidConstraintAnnotationForParseString() returns error? {
string jsonStr = string `{
"name": "John",
"age": 6,
"height": 180.20,
"dob": {
"year": 1990,
"month": 12,
"day": 31
}
}`;

ValidationPerson person = check parseString(jsonStr);
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);
}

@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<record {}> expType, string expectedError) {
anydata|Error err = parseString(sourceData, {}, expType);
test:assertEquals(err is Error, true);
test:assertEquals((<error>err).message(), expectedError);
}

function invalidConstraintAnnotation() returns [string, typedesc<record {}>, string][] {
return [
[
string `
{
"name": "John Doe",
"age": 6,
"height": 180.20,
"dob": {
"year": 1990,
"month": 12,
"day": 31
}
}`,
ValidationPerson,
"Validation failed for '$.name:maxLength' constraint(s)."
],
[
string `
{
"name": "John",
"age": 4,
"height": 180.20,
"dob": {
"year": 1990,
"month": 12,
"day": 31
}
}`,
ValidationPerson,
"Validation failed for '$.age:minValueExclusive' constraint(s)."
],
[
string `
{
"name": "John",
"age": 6,
"height": 185.21,
"dob": {
"year": 1990,
"month": 12,
"day": 31
}
}`,
ValidationPerson,
"Validation failed for '$.height:maxValue' constraint(s)."
],
[
string `
{
"name": "John",
"age": 6,
"height": 167.252,
"dob": {
"year": 1990,
"month": 12,
"day": 31
}
}`,
ValidationPerson,
"Validation failed for '$.height:maxFractionDigits' constraint(s)."
],
[
string `
{
"name": "John",
"age": 6,
"height": 180.20,
"dob": {
"year": 5000,
"month": 12,
"day": 31
}
}`,
ValidationPerson,
"Date of birth should be past value."
],
[
string `
{
"weight": [1.2, 2.3, 3.4]
}
`,
ValidationItem,
"Validation failed for '$.weight:length' constraint(s)."
]
];
}

@test:Config {
groups: ["constraint-validation"]
}
function testValidConstraintAnnotationForParseAsType() returns error? {
json jsonVal = {
name: "John",
age: 6,
height: 180.20,
dob: {
year: 1990,
month: 12,
day: 31
}
};

ValidationPerson person = check parseAsType(jsonVal);
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:Config {
groups: ["constraint-validation"],
dataProvider: invalidConstraintAnnotationForParseAsType
}
function testInvalidConstraintAnnotationForParseAsType(json sourceData, typedesc<record {}> expType, string expectedError) {
anydata|Error err = parseAsType(sourceData, {}, expType);
test:assertEquals(err is Error, true);
test:assertEquals((<error> err).message(), expectedError);
}

function invalidConstraintAnnotationForParseAsType() returns [json, typedesc<record {}>, string][] {
return [
[
{
name: "John Doe",
age: 6,
height: 180.20,
dob: {
year: 1990,
month: 12,
day: 31
}
},
ValidationPerson,
"Validation failed for '$.name:maxLength' constraint(s)."
],
[
{
name: "John",
age: 4,
height: 180.20,
dob: {
year: 1990,
month: 12,
day: 31
}
},
ValidationPerson,
"Validation failed for '$.age:minValueExclusive' constraint(s)."
],
[
{
name: "John",
age: 6,
height: 185.21,
dob: {
year: 1990,
month: 12,
day: 31
}
},
ValidationPerson,
"Validation failed for '$.height:maxValue' constraint(s)."
],
[
{
name: "John",
age: 6,
height: 167.252,
dob: {
year: 1990,
month: 12,
day: 31
}
},
ValidationPerson,
"Validation failed for '$.height:maxFractionDigits' constraint(s)."
],
[
{
name: "John",
age: 6,
height: 180.20,
dob: {
year: 5000,
month: 12,
day: 31
}
},
ValidationPerson,
"Date of birth should be past value."
],
[
{
"weight": [1.2, 2.3, 3.4]
},
ValidationItem,
"Validation failed for '$.weight:length' constraint(s)."
]
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import ballerina/test;

const options = {allowDataProjection: false};
const options = {allowDataProjection: false, enableConstraintValidation: true};

@test:Config
isolated function testDisableDataProjectionInArrayTypeForParseString() {
Expand Down
Loading

0 comments on commit d894eda

Please sign in to comment.