Skip to content

Commit

Permalink
Support T? as expected field type
Browse files Browse the repository at this point in the history
  • Loading branch information
prakanth97 committed Mar 28, 2024
1 parent dcfd414 commit 684f000
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 51 deletions.
29 changes: 1 addition & 28 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0-20240326-110600-aca0cc0c"
distribution-version = "2201.8.6"

[[package]]
org = "ballerina"
Expand Down Expand Up @@ -42,26 +42,6 @@ modules = [
{org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"}
]

[[package]]
org = "ballerina"
name = "lang.__internal"
version = "0.0.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.object"}
]

[[package]]
org = "ballerina"
name = "lang.array"
version = "0.0.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.__internal"}
]

[[package]]
org = "ballerina"
name = "lang.error"
Expand All @@ -71,12 +51,6 @@ dependencies = [
{org = "ballerina", name = "jballerina.java"}
]

[[package]]
org = "ballerina"
name = "lang.object"
version = "0.0.0"
scope = "testOnly"

[[package]]
org = "ballerina"
name = "lang.value"
Expand All @@ -96,7 +70,6 @@ version = "0.0.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.array"},
{org = "ballerina", name = "lang.error"}
]
modules = [
Expand Down
111 changes: 106 additions & 5 deletions ballerina/tests/from_json_string_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/test;

@test:Config {
dataProvider: basicTypeDataProviderForParseString
}
isolated function testJsonStringToBasicTypes(string sourceData, typedesc<anydata> expType,
isolated function testJsonStringToBasicTypes(string sourceData, typedesc<anydata> expType,
anydata expectedData) returns Error? {
anydata val1 = check parseString(sourceData, {}, expType);
test:assertEquals(val1, expectedData);
Expand All @@ -38,7 +37,7 @@ function basicTypeDataProviderForParseString() returns [string, typedesc<anydata
@test:Config
isolated function testNilAsExpectedTypeWithParseString() returns error? {
() val = check parseString("null");
test:assertEquals(val, null);
test:assertEquals(val, ());
}

@test:Config
Expand Down Expand Up @@ -125,7 +124,7 @@ isolated function testJsonStringToRecordWithOptionalFields() returns Error? {
record {|string a; int b?;|} recA = check parseString(str);
test:assertEquals(recA.length(), 1);
test:assertEquals(recA.a, "hello");
test:assertEquals(recA.b, null);
test:assertEquals(recA.b, ());
}

@test:Config
Expand Down Expand Up @@ -312,7 +311,7 @@ isolated function testParseString6() returns Error? {
test:assertEquals(x.teacher.length(), 2);
test:assertEquals(x.teacher.id, 3);
test:assertEquals(x.teacher.name, "Jane Smith");
test:assertEquals(x.monitor, null);
test:assertEquals(x.monitor, ());
}

@test:Config
Expand Down Expand Up @@ -1380,6 +1379,104 @@ isolated function testUnalignedJsonContent() returns error? {
test:assertEquals(val.b, 1);
}

@test:Config
isolated function testNilableTypeAsFieldTypeForParseString() returns error? {
string jsonStr1 = string `
{
"id": 0,
"name": "Anne",
"address": {
"street": "Main",
"city": "94",
"id": 4294967295
}
}
`;
record {|
int? id;
string? name;
anydata? address;
|} val1 = check parseString(jsonStr1);
test:assertEquals(val1.id, 0);
test:assertEquals(val1.name, "Anne");
test:assertEquals(val1.address, {street: "Main", city: 94, id: 4294967295});

string jsonStr2 = string `{
"company": "wso2",
"employees": [
{
"name": "Walter White",
"age": 55
},
{
"name": "Jesse Pinkman",
"age": 25
}
]
}`;
record {|
anydata? company;
record {|
string name;
int age;
|}?[] employees;
|} val2 = check parseString(jsonStr2);
test:assertEquals(val2.company, "wso2");
test:assertEquals(val2.employees[0]?.name, "Walter White");
test:assertEquals(val2.employees[0]?.age, 55);
test:assertEquals(val2.employees[1]?.name, "Jesse Pinkman");
test:assertEquals(val2.employees[1]?.age, 25);


}

@test:Config
isolated function testNilableTypeAsFieldTypeForParseAsType() returns error? {
json jsonVal1 = {
"id": 0,
"name": "Anne",
"address": {
"street": "Main",
"city": 94,
"id": 4294967295
}
};
record {|
int? id;
string? name;
anydata? address;
|} val1 = check parseAsType(jsonVal1);
test:assertEquals(val1.id, 0);
test:assertEquals(val1.name, "Anne");
test:assertEquals(val1.address, {street: "Main", city: 94, id: 4294967295});

json jsonVal2 = {
"company": "wso2",
"employees": [
{
"name": "Walter White",
"age": 55
},
{
"name": "Jesse Pinkman",
"age": 25
}
]
};
record {|
anydata? company;
record {|
string name;
int age;
|}?[] employees;
|} val2 = check parseAsType(jsonVal2);
test:assertEquals(val2.company, "wso2");
test:assertEquals(val2.employees[0]?.name, "Walter White");
test:assertEquals(val2.employees[0]?.age, 55);
test:assertEquals(val2.employees[1]?.name, "Jesse Pinkman");
test:assertEquals(val2.employees[1]?.age, 25);
}

@test:Config
isolated function testParseStringNegative1() returns Error? {
string str = string `{
Expand Down Expand Up @@ -1617,4 +1714,8 @@ isolated function testConvertNonStringValueNegative() {
()|Error err5 = parseString("Null");
test:assertTrue(err5 is Error);
test:assertEquals((<Error>err5).message(), "incompatible expected type '()' for value 'Null'");

()|Error err6 = parseString("()");
test:assertTrue(err6 is Error);
test:assertEquals((<Error>err6).message(), "incompatible expected type '()' for value '()'");
}
92 changes: 91 additions & 1 deletion ballerina/tests/from_json_with_projection_options_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseString()
@test:Config {
groups: ["options"]
}
isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseAsString() returns error? {
isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseString2() returns error? {
string jsonData = check io:fileReadString(PATH + "product_list_response.json");
json productJson = check value:fromJsonString(jsonData);
ResponseEcom response = check parseAsType(productJson, options3);
Expand Down Expand Up @@ -410,3 +410,93 @@ isolated function testDisableOptionsOfProjectionTypeForParseAsType2() returns er
test:assertTrue(err is Error);
test:assertEquals((<Error>err).message(), "required field 'email' not present in JSON");
}

@test:Config
isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseString3() returns error? {
record {|
string name;
|}|Error val1 = parseString(string `{"name": null}`, options3);
test:assertTrue(val1 is Error);
test:assertEquals((<Error>val1).message(), "incompatible value 'null' for type 'string' in field 'name'");

record {|
string? name;
|} val2 = check parseString(string `{"name": null}`, options3);
test:assertEquals(val2.name, ());

record {|
string name?;
|} val3 = check parseString(string `{"name": null}`, options3);
test:assertEquals(val3, {});

record {|
string? name?;
|} val4 = check parseString(string `{"name": null}`, options3);
test:assertEquals(val4?.name, ());

record {|
string name;
|}|Error val5 = parseString(string `{}`, options3);
test:assertTrue(val5 is Error);
test:assertEquals((<Error>val5).message(), "required field 'name' not present in JSON");

record {|
string? name;
|} val6 = check parseString(string `{}`, options3);
test:assertEquals(val6.name, ());

record {|
string name?;
|} val7 = check parseString(string `{}`, options3);
test:assertEquals(val7, {});

record {|
string? name?;
|} val8 = check parseString(string `{}`, options3);
test:assertEquals(val8?.name, ());
}

@test:Config
isolated function testAbsentAsNilableTypeAndAbsentAsNilableTypeForParseAsType2() returns error? {
record {|
string name;
|}|Error val1 = parseAsType({name: null}, options3);
test:assertTrue(val1 is Error);
test:assertEquals((<Error>val1).message(), "incompatible value 'null' for type 'string' in field 'name'");

record {|
string? name;
|} val2 = check parseAsType({name: null}, options3);
test:assertEquals(val2.name, ());

record {|
string name?;
|} val3 = check parseAsType({name: null}, options3);
test:assertEquals(val3, {});

record {|
string? name?;
|} val4 = check parseAsType({name: null}, options3);
test:assertEquals(val4?.name, ());

record {|
string name;
|}|Error val5 = parseAsType({}, options3);
test:assertTrue(val5 is Error);
test:assertEquals((<Error>val5).message(), "required field 'name' not present in JSON");

record {|
string? name;
|} val6 = check parseAsType({}, options3);
test:assertEquals(val6.name, ());

record {|
string name?;
|} val7 = check parseAsType({}, options3);
test:assertEquals(val7, {});

record {|
string? name?;
|} val8 = check parseAsType({}, options3);
test:assertEquals(val8?.name, ());
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,25 @@ private void validateRecordFieldType(TypeSymbol typeSymbol, Optional<Location> l
private void validateUnionType(UnionTypeSymbol unionTypeSymbol, Optional<Location> location,
SyntaxNodeAnalysisContext ctx) {
int nonPrimitiveMemberCount = 0;
boolean isNilOrErrorPresent = false;
List<TypeSymbol> memberTypeSymbols = unionTypeSymbol.memberTypeDescriptors();
for (TypeSymbol memberTypeSymbol : memberTypeSymbols) {
if (isSupportedUnionMemberType(memberTypeSymbol)) {
TypeSymbol referredSymbol = getReferredSymbol(memberTypeSymbol);
if (referredSymbol.typeKind() == TypeDescKind.NIL || referredSymbol.typeKind() == TypeDescKind.ERROR) {
isNilOrErrorPresent = true;
continue;
}
if (isSupportedUnionMemberType(referredSymbol)) {
continue;
}
nonPrimitiveMemberCount++;
}

if (nonPrimitiveMemberCount > 1) {
if (isNilOrErrorPresent && memberTypeSymbols.size() == 2) {
return;
}

if (nonPrimitiveMemberCount >= 1) {
reportDiagnosticInfo(ctx, location, JsondataDiagnosticCodes.UNSUPPORTED_UNION_TYPE);
}
}
Expand All @@ -205,6 +215,11 @@ private boolean isSupportedUnionMemberType(TypeSymbol typeSymbol) {
}
}

private TypeSymbol getReferredSymbol(TypeSymbol typeSymbol) {
return typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE ?
((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor() : typeSymbol;
}

private void reportDiagnosticInfo(SyntaxNodeAnalysisContext ctx, Optional<Location> location,
JsondataDiagnosticCodes diagnosticsCodes) {
Location pos = location.orElseGet(() -> currentLocation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public static Object fromStringWithType(BString string, Type expType) {
case TypeTags.UNION_TAG:
return stringToUnion(string, (UnionType) expType);
case TypeTags.JSON_TAG:
case TypeTags.ANYDATA_TAG:
return stringToUnion(string, JSON_TYPE_WITH_BASIC_TYPES);
case TypeTags.TYPE_REFERENCED_TYPE_TAG:
return fromStringWithType(string, ((ReferenceType) expType).getReferredType());
Expand Down
Loading

0 comments on commit 684f000

Please sign in to comment.