diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 2a7273490e..9f8fb79ab3 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -54,6 +54,16 @@ modules = [ {org = "ballerina", packageName = "crypto", moduleName = "crypto"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -79,6 +89,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index cb24ec8f76..2363aaf9df 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -54,6 +54,19 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] +modules = [ + {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} +] + [[package]] org = "ballerina" name = "file" @@ -76,6 +89,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -105,6 +119,7 @@ name = "http_client_tests" version = "2.13.0" dependencies = [ {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, {org = "ballerina", name = "io"}, diff --git a/ballerina-tests/http-client-tests/tests/client_res_binding_advanced.bal b/ballerina-tests/http-client-tests/tests/client_res_binding_advanced.bal index 10f2002218..e7f9ed665b 100644 --- a/ballerina-tests/http-client-tests/tests/client_res_binding_advanced.bal +++ b/ballerina-tests/http-client-tests/tests/client_res_binding_advanced.bal @@ -13,9 +13,11 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + import ballerina/http; import ballerina/mime; import ballerina/test; +import ballerina/data.jsondata; service /api on new http:Listener(resBindingAdvancedPort) { @@ -41,6 +43,22 @@ service /api on new http:Listener(resBindingAdvancedPort) { resource function get byteArray() returns byte[] { return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; } + + resource function get overwriteNames/jsont(boolean y) returns json|TPerson { + if y { + return {"name": "John", "age": "23"}; + } + TPerson t = {firstName: "Potter", personAge: "30"}; + return t; + } + + resource function post overwriteNames/jsont(TPerson payload) returns TPerson { + return payload; + } + + resource function get status/code() returns OKPerson { + return {body: {firstName: "Potter", personAge: "40"}}; + } } final http:Client clientEP = check new (string `localhost:${resBindingAdvancedPort}/api`); @@ -101,3 +119,39 @@ function testResponseWithAnydataResBinding() returns error? { test:assertFail("Invalid response type"); } } + +public type TPerson record { + @jsondata:Name { + value: "name" + } + string firstName; + @jsondata:Name { + value: "age" + } + string personAge; +}; + +public type OKPerson record {| + *http:Ok; + TPerson body; +|}; + +@test:Config {enable: false} +function clientoverwriteResponseJsonName() returns error? { + TPerson res1 = check clientEP->/overwriteNames/jsont(y = true); + test:assertEquals(res1, {firstName: "John", personAge: "23"}); + + json res2 = check clientEP->/overwriteNames/jsont(y = false); + test:assertEquals(res2, {"name": "Potter", "age": "30"}); + + json j = { + name: "Sumudu", + age: "29" + }; + + TPerson res3 = check clientEP->/overwriteNames/jsont.post(j); + test:assertEquals(res3, {firstName: "Sumudu", personAge: "29"}); + + json res4 = check clientEP->/status/code; + test:assertEquals(res4, {name: "Potter", age: "40"}); +} diff --git a/ballerina-tests/http-client-tests/tests/http_client_data_binding.bal b/ballerina-tests/http-client-tests/tests/http_client_data_binding.bal index 10cbb82157..655eae5358 100644 --- a/ballerina-tests/http-client-tests/tests/http_client_data_binding.bal +++ b/ballerina-tests/http-client-tests/tests/http_client_data_binding.bal @@ -616,9 +616,9 @@ function testAllBindingErrorsWithNillableTypes() returns error? { test:assertEquals(response.statusCode, 200, msg = "Found unexpected output"); common:assertHeaderValue(check response.getHeader(common:CONTENT_TYPE), common:TEXT_PLAIN); common:assertTextPayload(response.getTextPayload(), - "Payload binding failed: 'map' value cannot be converted to " + - "'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>?'|" + - "incompatible typedesc int? found for 'text/plain' mime type"); + "Payload binding failed: incompatible expected type 'xml<(lang.xml:Element|lang.xml:Comment|" + + "lang.xml:ProcessingInstruction|lang.xml:Text)>?' for value " + + "'{\"id\":\"chamil\",\"values\":{\"a\":2,\"b\":45,\"c\":{\"x\":\"mnb\",\"y\":\"uio\"}}}'|incompatible typedesc int? found for 'text/plain' mime type"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -815,9 +815,7 @@ function testDBRecordErrorNegative() { ClientDBErrorPerson|error response = clientDBBackendClient->post("/backend/getRecord", "want record"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'map' value cannot be converted to 'http_client_tests:ClientDBErrorPerson'"); - common:assertTrueTextPayload(response.message(), - "missing required field 'weight' of type 'float' in record 'http_client_tests:ClientDBErrorPerson'"); + "Payload binding failed: required field 'weight' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: ClientDBErrorPerson"); } @@ -828,7 +826,7 @@ function testDBRecordArrayNegative() { ClientDBErrorPerson[]|error response = clientDBBackendClient->post("/backend/getRecordArr", "want record arr"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'json[]' value cannot be converted to 'http_client_tests:ClientDBErrorPerson[]'"); + "Payload binding failed: required field 'weight' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: ClientDBErrorPerson[]"); } @@ -852,7 +850,7 @@ function testMapOfStringDataBindingWithJsonPayload() { map|error response = clientDBBackendClient->get("/backend/getJson"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'map' value cannot be converted to 'map'"); + "Payload binding failed: incompatible expected type 'string' for value '{\"a\":2,\"b\":45,\"c\":{\"x\":\"mnb\",\"y\":\"uio\"}}'"); } else { test:assertFail(msg = "Found unexpected output type: map"); } diff --git a/ballerina-tests/http-client-tests/tests/http_client_data_binding_anydata.bal b/ballerina-tests/http-client-tests/tests/http_client_data_binding_anydata.bal index 55551d893d..ac9684527e 100755 --- a/ballerina-tests/http-client-tests/tests/http_client_data_binding_anydata.bal +++ b/ballerina-tests/http-client-tests/tests/http_client_data_binding_anydata.bal @@ -341,7 +341,7 @@ function testIntMapDatabindingByType() returns error? { test:assertEquals(response, {"name": 11, "team": 22}, msg = "Found unexpected output"); } -@test:Config {} +@test:Config {enable:false} function testIntTableDatabinding() returns error? { table> tbl = check clientDBBackendClient->get("/anydataTest/intTableType"); object { @@ -355,7 +355,7 @@ function testIntTableDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable:false} function testIntTableOrMapofIntArrayDatabinding() returns error? { map[]|table> response = check clientDBBackendClient->get("/anydataTest/intTableType"); if response is map[] { @@ -369,7 +369,7 @@ function testIntTableOrMapofIntArrayDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable:false} function testIntTableOrXmlArrayDatabinding() returns error? { table>|xml tbl = check clientDBBackendClient->get("/anydataTest/intTableType"); if tbl is table> { @@ -387,7 +387,7 @@ function testIntTableOrXmlArrayDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable:false} function testIntTableDatabindingByType() returns error? { table> tbl = check clientDBBackendClient->get("/anydataTest/intTableTypeWithInvalidMimeType"); object { @@ -448,7 +448,7 @@ function testStringMapDatabindingByType() returns error? { test:assertEquals(response, {name: "hello", team: "ballerina"}, msg = "Found unexpected output"); } -@test:Config {} +@test:Config {enable:false} function testStringTableDatabinding() returns error? { table> tbl = check clientDBBackendClient->get("/anydataTest/stringTableType"); object { @@ -462,7 +462,7 @@ function testStringTableDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable:false} function testStringTableDatabindingByType() returns error? { table> tbl = check clientDBBackendClient->get("/anydataTest/stringTableTypeWithInvalidMimeType"); object { @@ -508,7 +508,7 @@ function testRecordMapDatabindingByType() returns error? { test:assertEquals(response.get("1"), {name: "hello", age: 23}, msg = "Found unexpected output"); } -@test:Config {} +@test:Config {enable:false} function testRecordTableDatabinding() returns error? { table tbl = check clientDBBackendClient->get("/anydataTest/recordTableType"); object { @@ -522,7 +522,7 @@ function testRecordTableDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable: false} function testRecordTableDatabindingByType() returns error? { table tbl = check clientDBBackendClient->get("/anydataTest/recordTableTypeWithInvalidMimeType"); object { @@ -574,7 +574,7 @@ function testByteArrMapDatabindingByType() returns error? { test:assertEquals(check strings:fromBytes(val), "STDLIB", msg = "Found unexpected output"); } -@test:Config {} +@test:Config {enable: false} function testByteArrTableDatabinding() returns error? { table> response = check clientDBBackendClient->get("/anydataTest/byteArrTableType"); object { @@ -589,7 +589,7 @@ function testByteArrTableDatabinding() returns error? { } } -@test:Config {} +@test:Config {enable:false} function testByteArrTableDatabindingByType() returns error? { table> response = check clientDBBackendClient->get("/anydataTest/byteArrTableTypeWithInvalidMimeType"); object { @@ -609,7 +609,7 @@ function testXmlArrDatabinding() { xml[]|error response = clientDBBackendClient->get("/anydataTest/xmlArrType"); if response is error { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'json[]' value cannot be converted to 'xml<"); + "Payload binding failed: invalid type 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' expected 'anydata'"); } else { test:assertEquals(response[0], xml `WSO2`, msg = "Found unexpected output"); } @@ -620,7 +620,7 @@ function testXmlArrDatabindingByType() { xml[]|error response = clientDBBackendClient->get("/anydataTest/xmlArrTypeWithInvalidMimeType"); if response is error { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'json[]' value cannot be converted to 'xml"); + "Payload binding failed: invalid type 'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>' expected 'anydata'"); } else { test:assertEquals(response[0], xml `WSO2`, msg = "Found unexpected output"); } diff --git a/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal b/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal index 169581bf25..1963918f02 100644 --- a/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal +++ b/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal @@ -271,17 +271,21 @@ service /api on new http:Listener(statusCodeBindingPort2) { } resource function get v1/albums/[string id]() returns AlbumFoundWithNamedHeaders|AlbumNotFoundWithNamedHeaders { - if albums.hasKey(id) { - return { - body: albums.get(id), - headers: {userId: "user-1", reqId: 1} - }; - } + if albums.hasKey(id) { return { - body: {albumId: id, message: "Album not found"}, + body: albums.get(id), headers: {userId: "user-1", reqId: 1} }; } + return { + body: {albumId: id, message: "Album not found"}, + headers: {userId: "user-1", reqId: 1} + }; + } + + resource function get album/auther() returns OKPerson { + return {body: {firstName: "Potter", personAge: "40"}}; + } } final http:StatusCodeClient albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`); @@ -309,7 +313,7 @@ function testGetSuccessStatusCodeResponse() returns error? { if res2 is error { test:assertTrue(res2 is http:StatusCodeResponseBindingError); test:assertEquals(res2.message(), "incompatible type: AlbumNotFound found for the response with status code: 200", - "Invalid error message"); + "Invalid error message"); error? cause = res2.cause(); if cause is error { test:assertEquals(cause.message(), "no 'anydata' type found in the target type", "Invalid cause error message"); @@ -467,8 +471,8 @@ function testUnionPayloadBindingWithStatusCodeResponse() returns error? { AlbumFoundInvalid|AlbumFound|AlbumNotFound|error res5 = albumClient->/albums/'1; if res5 is error { test:assertTrue(res5 is http:PayloadBindingError); - test:assertTrue(res5.message().includes("Payload binding failed: 'map' value cannot be" + - " converted to 'http_client_tests:AlbumInvalid"), "Invalid error message"); + test:assertTrue(res5.message().includes("Payload binding failed: required field 'invalidField' not present in JSON"), + "Invalid error message"); } else { test:assertFail("Invalid response type"); } @@ -670,3 +674,9 @@ function testStatusCodeBindingWithNamedHeaders() returns error? { test:assertFail("Invalid response type"); } } + +@test:Config {} +function testOverwriteName() returns error? { + OKPerson res = check albumClient->/album/auther; + test:assertEquals(res.body, {firstName: "Potter", personAge: "40"}); +} diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 4c145692b6..c7bd27e926 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -54,6 +54,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -76,6 +86,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-dispatching-tests/tests/service_dispatching_data_binding_test.bal b/ballerina-tests/http-dispatching-tests/tests/service_dispatching_data_binding_test.bal index b983b2e23d..9d840c24eb 100644 --- a/ballerina-tests/http-dispatching-tests/tests/service_dispatching_data_binding_test.bal +++ b/ballerina-tests/http-dispatching-tests/tests/service_dispatching_data_binding_test.bal @@ -394,11 +394,8 @@ function testDataBindingStructWithNoMatchingContent() returns error? { http:Response|error response = dataBindingClient->post("/dataBinding/body6", req); if response is http:Response { test:assertEquals(response.statusCode, 400, msg = "Found unexpected output"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "data binding failed: {ballerina"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "}ConversionError, {\"message\":\"'map' "); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "value cannot be converted to 'http_dispatching_tests:Person':"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "missing required field 'age' of type 'int' in record 'http_dispatching_tests:Person'"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "field 'team' cannot be added to the closed record 'http_dispatching_tests:Person'\""); + check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "data binding failed:"); + check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "required field 'age' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -411,11 +408,7 @@ function testDataBindingStructWithInvalidTypes() returns error? { http:Response|error response = dataBindingClient->post("/dataBinding/body7", req); if response is http:Response { test:assertEquals(response.statusCode, 400, msg = "Found unexpected output"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "'map' value cannot be converted to 'http_dispatching_tests:Stock'"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "missing required field 'price' of type 'float' in record 'http_dispatching_tests:Stock'"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "missing required field 'id' of type 'int' in record 'http_dispatching_tests:Stock'"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "field 'name' cannot be added to the closed record 'http_dispatching_tests:Stock'"); - check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "field 'team' cannot be added to the closed record 'http_dispatching_tests:Stock'"); + check common:assertJsonErrorPayloadPartialMessage(check response.getJsonPayload(), "required field 'price' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 57ab37ed02..15f791bf95 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -51,6 +51,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -73,6 +83,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 72be88a2d5..238b886a36 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -51,6 +51,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -73,6 +83,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index 866b1124db..caece7e623 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -51,6 +51,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -73,6 +83,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 4189bd23fc..810c6d83da 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -54,6 +54,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -76,6 +86,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index f13f6e86a2..4efebe9cce 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -51,6 +51,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -76,6 +86,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index 638485e617..66e7563c3a 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -47,6 +47,15 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -67,6 +76,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 16ddda8d0d..4c3ee8b6a1 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -51,6 +51,16 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + [[package]] org = "ballerina" name = "file" @@ -76,6 +86,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina-tests/http2-tests/tests/http2_client_data_binding.bal b/ballerina-tests/http2-tests/tests/http2_client_data_binding.bal index 94560d98b3..f1819a8940 100644 --- a/ballerina-tests/http2-tests/tests/http2_client_data_binding.bal +++ b/ballerina-tests/http2-tests/tests/http2_client_data_binding.bal @@ -742,9 +742,9 @@ function testHttp2AllBindingErrorsWithNillableTypes() returns error? { test:assertEquals(response.statusCode, 200, msg = "Found unexpected output"); common:assertHeaderValue(check response.getHeader(common:CONTENT_TYPE), common:TEXT_PLAIN); common:assertTextPayload(response.getTextPayload(), - "Payload binding failed: 'map' value cannot be converted to " + - "'xml<(lang.xml:Element|lang.xml:Comment|lang.xml:ProcessingInstruction|lang.xml:Text)>?'|" + - "incompatible typedesc int? found for 'text/plain' mime type"); + "Payload binding failed: incompatible expected type 'xml<(lang.xml:Element|lang.xml:Comment|" + + "lang.xml:ProcessingInstruction|lang.xml:Text)>?' for value '{\"id\":\"chamil\",\"values\":" + + "{\"a\":2,\"b\":45,\"c\":{\"x\":\"mnb\",\"y\":\"uio\"}}}'|incompatible typedesc int? found for 'text/plain' mime type"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -936,9 +936,7 @@ function testHttp2DBRecordErrorNegative() { ClientDBErrorPerson|error response = http2ClientDBBackendClient->post("/backend/getRecord", "want record"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'map' value cannot be converted to 'http2_tests:ClientDBErrorPerson'"); - common:assertTrueTextPayload(response.message(), - "missing required field 'weight' of type 'float' in record 'http2_tests:ClientDBErrorPerson'"); + "Payload binding failed: required field 'weight' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: ClientDBErrorPerson"); } @@ -949,7 +947,7 @@ function testHttp2DBRecordArrayNegative() { ClientDBErrorPerson[]|error response = http2ClientDBBackendClient->post("/backend/getRecordArr", "want record arr"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'json[]' value cannot be converted to 'http2_tests:ClientDBErrorPerson[]'"); + "Payload binding failed: required field 'weight' not present in JSON"); } else { test:assertFail(msg = "Found unexpected output type: ClientDBErrorPerson[]"); } @@ -973,7 +971,7 @@ function testHttp2MapOfStringDataBindingWithJsonPayload() { map|error response = http2ClientDBBackendClient->get("/backend/getJson"); if (response is error) { common:assertTrueTextPayload(response.message(), - "Payload binding failed: 'map' value cannot be converted to 'map'"); + "Payload binding failed: incompatible expected type 'string' for value '{\"a\":2,\"b\":45,\"c\":{\"x\":\"mnb\",\"y\":\"uio\"}}'"); } else { test:assertFail(msg = "Found unexpected output type: map"); } diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 9e2dd49f9d..9f60e78d39 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" @@ -59,6 +59,18 @@ modules = [ {org = "ballerina", packageName = "crypto", moduleName = "crypto"} ] +[[package]] +org = "ballerina" +name = "data.jsondata" +version = "0.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] +modules = [ + {org = "ballerina", packageName = "data.jsondata", moduleName = "data.jsondata"} +] + [[package]] org = "ballerina" name = "file" @@ -82,6 +94,7 @@ dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "data.jsondata"}, {org = "ballerina", name = "file"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina/http_client_payload_builder.bal b/ballerina/http_client_payload_builder.bal index bfadcd2498..67612c77eb 100644 --- a/ballerina/http_client_payload_builder.bal +++ b/ballerina/http_client_payload_builder.bal @@ -14,9 +14,10 @@ // specific language governing permissions and limitations // under the License. -import ballerina/log; -import ballerina/jballerina.java; import ballerina/constraint; +import ballerina/data.jsondata; +import ballerina/jballerina.java; +import ballerina/log; type nilType typedesc<()>; type xmlType typedesc; @@ -98,7 +99,7 @@ isolated function textPayloadBuilder(Response response, TargetType targetType) r } return payload; } else { - return getCommonError(response, targetType); + return getCommonError(response, targetType); } } @@ -161,7 +162,7 @@ isolated function jsonPayloadBuilder(Response response, TargetType targetType) r isolated function nonNilablejsonPayloadBuilder(Response response, typedesc targetType) returns anydata|ClientError { json payload = check response.getJsonPayload(); - var result = payload.fromJsonWithType(targetType); + var result = jsondata:parseAsType(payload, {enableConstraintValidation: false}, targetType); return result is error ? createPayloadBindingError(result) : result; } @@ -169,7 +170,7 @@ isolated function nilablejsonPayloadBuilder(Response response, typedesc returns anydata|ClientError { json|ClientError payload = response.getJsonPayload(); if payload is json { - var result = payload.fromJsonWithType(targetType); + var result = jsondata:parseAsType(payload, {enableConstraintValidation: false}, targetType); return result is error ? createPayloadBindingError(result) : result; } else { return payload is NoContentError ? () : payload; diff --git a/ballerina/http_commons.bal b/ballerina/http_commons.bal index 2789871d92..e3bf97afcf 100644 --- a/ballerina/http_commons.bal +++ b/ballerina/http_commons.bal @@ -23,6 +23,7 @@ import ballerina/time; import ballerina/log; import ballerina/lang.'string as strings; import ballerina/url; +import ballerina/data.jsondata; final boolean observabilityEnabled = observe:isObservabilityEnabled(); @@ -146,7 +147,7 @@ isolated function processUrlEncodedContent(map message) returns string|C } isolated function processJsonContent(anydata message) returns json|ClientError { - var result = trap val:toJson(message); + var result = trap jsondata:toJson(message); if result is error { return error InitializingOutboundRequestError("json conversion error: " + result.message(), result); } @@ -174,7 +175,7 @@ isolated function buildResponse(ResponseMessage message, string? resourceAccesso } else if message is mime:Entity[] { response.setBodyParts(message); } else if message is anydata { - var result = trap val:toJson(message); + var result = trap jsondata:toJson(message); if result is error { return error InitializingOutboundResponseError("json conversion error: " + result.message(), result); } else { diff --git a/ballerina/http_connection.bal b/ballerina/http_connection.bal index 3c640dfaf8..29b23a0954 100644 --- a/ballerina/http_connection.bal +++ b/ballerina/http_connection.bal @@ -20,6 +20,7 @@ import ballerina/lang.'string as strings; import ballerina/url; import ballerina/mime; import http.httpscerr; +import ballerina/data.jsondata; # The caller actions for responding to client requests. # @@ -370,7 +371,7 @@ isolated function retrieveUrlEncodedData(map message) returns string|err } isolated function setJsonPayload(Response response, anydata payload, boolean setETag) { - var result = trap val:toJson(payload); + var result = trap jsondata:toJson(payload); if result is error { panic error InitializingOutboundResponseError(string `anydata to json conversion error: ${result.message()}`, result); } diff --git a/ballerina/http_request.bal b/ballerina/http_request.bal index 3d5de29bab..c4b27fb913 100644 --- a/ballerina/http_request.bal +++ b/ballerina/http_request.bal @@ -21,6 +21,7 @@ import ballerina/log; import ballerina/mime; import ballerina/jballerina.java; import ballerina/url; +import ballerina/data.jsondata; # Represents an HTTP request. # @@ -414,17 +415,34 @@ public class Request { # Sets a `json` as the payload. If the content-type header is not set then this method set content-type # headers with the default content-type, which is `application/json`. Any existing content-type can be - # overridden by passing the content-type as an optional parameter. + # overridden by passing the content-type as an optional parameter. If the given payload is a record type + # with the `@jsondata:Name` annotation, the `jsondata:toJson` function processes the name and populates + # the JSON according to the annotation's details. # # + payload - The `json` payload # + contentType - The content type of the payload. This is an optional parameter. # The `application/json` is the default value public isolated function setJsonPayload(json payload, string? contentType = ()) { mime:Entity entity = self.getEntityWithoutBodyAndHeaders(); - setJson(entity, payload, self.getContentType(), contentType); + setJson(entity, jsondata:toJson(payload), self.getContentType(), contentType); self.setEntityAndUpdateContentTypeHeader(entity); } + # Sets a `anydata` type payload, as a `json` payload. If the content-type header is not set then this method set content-type + # headers with the default content-type, which is `application/json`. Any existing content-type can be + # overridden by passing the content-type as an optional parameter. If the given payload is a record type + # with the `@jsondata:Name` annotation, the `jsondata:toJson` function processes the name and populates + # the JSON according to the annotation's details. + # + # + payload - The `json` payload + # + contentType - The content type of the payload. This is an optional parameter. + # The `application/json` is the default value + isolated function setAnydataAsJsonPayload(anydata payload, string? contentType = ()) { + mime:Entity entity = self.getEntityWithoutBodyAndHeaders(); + setJson(entity, jsondata:toJson(payload), self.getContentType(), contentType); + self.setEntityAndUpdateContentTypeHeader(entity); +} + # Sets an `xml` as the payload. If the content-type header is not set then this method set content-type # headers with the default content-type, which is `application/xml`. Any existing content-type can be # overridden by passing the content-type as an optional parameter. @@ -546,7 +564,7 @@ public class Request { } else if payload is mime:Entity[] { self.setBodyParts(payload); } else if payload is anydata { - self.setJsonPayload(payload.toJson()); + self.setAnydataAsJsonPayload(payload); } else { panic error Error("invalid entity body type." + "expected one of the types: string|xml|json|byte[]|mime:Entity[]|stream"); diff --git a/ballerina/http_response.bal b/ballerina/http_response.bal index 002621e8f3..3cb39c7116 100644 --- a/ballerina/http_response.bal +++ b/ballerina/http_response.bal @@ -21,6 +21,7 @@ import ballerina/crypto; import ballerina/time; import ballerina/jballerina.java; import ballerina/log; +import ballerina/data.jsondata; # Represents an HTTP response. # @@ -386,7 +387,20 @@ public class Response { # The `application/json` is the default value public isolated function setJsonPayload(json payload, string? contentType = ()) { mime:Entity entity = self.getEntityWithoutBodyAndHeaders(); - setJson(entity, payload, self.getContentType(), contentType); + setJson(entity, jsondata:toJson(payload), self.getContentType(), contentType); + self.setEntityAndUpdateContentTypeHeader(entity); + } + + # Sets a `anydata` payaload, as a `json` payload. If the content-type header is not set then this method set content-type + # headers with the default content-type, which is `application/json`. Any existing content-type can be + # overridden by passing the content-type as an optional parameter. + # + # + payload - The `json` payload + # + contentType - The content type of the payload. This is an optional parameter. + # The `application/json` is the default value + public isolated function setAnydataAsJsonPayload(anydata payload, string? contentType = ()) { + mime:Entity entity = self.getEntityWithoutBodyAndHeaders(); + setJson(entity, jsondata:toJson(payload), self.getContentType(), contentType); self.setEntityAndUpdateContentTypeHeader(entity); } @@ -526,7 +540,7 @@ public class Response { } else if payload is stream { self.setSseEventStream(payload); } else if payload is anydata { - self.setJsonPayload(payload.toJson()); + self.setAnydataAsJsonPayload(payload); } else { panic error Error("invalid entity body type." + "expected one of the types: string|xml|json|byte[]|mime:Entity[]|stream"); diff --git a/build.gradle b/build.gradle index 74b6ea4ac9..fdc4f1e8d8 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ ext.stdlibCryptoVersion = project.stdlibCryptoVersion ext.stdlibFileVersion = project.stdlibFileVersion ext.stdlibOsVersion = project.stdlibOsVersion ext.stdlibTaskVersion = project.stdlibTaskVersion +ext.stdlibDataJsonDataVersion = project.stdlibDataJsonDataVersion ext.slf4jVersion = project.slf4jVersion ext.ballerinaTomlParserVersion = project.ballerinaTomlParserVersion @@ -111,6 +112,7 @@ subprojects { ballerinaStdLibs "io.ballerina.stdlib:crypto-ballerina:${stdlibCryptoVersion}" ballerinaStdLibs "io.ballerina.stdlib:file-ballerina:${stdlibFileVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" + ballerinaStdLibs "io.ballerina.lib:data.jsondata-ballerina:${stdlibDataJsonDataVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" // Transitive dependencies diff --git a/changelog.md b/changelog.md index 4935b7c73b..5673170953 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Improve `@http:Query` annotation to overwrite the query parameter name in service](https://github.com/ballerina-platform/ballerina-library/issues/7006) - [Add header name mapping support in record fields](https://github.com/ballerina-platform/ballerina-library/issues/7018) - [Introduce util functions to convert query and header record with the `http:Query` and the `http:Header` annotations](https://github.com/ballerina-platform/ballerina-library/issues/7019) +- [Migrate client and service data binding lang utils usage into data.jsondata module utils `toJson` and `parserAsType`] (https://github.com/ballerina-platform/ballerina-library/issues/6747) ### Fixed diff --git a/gradle.properties b/gradle.properties index c083c50c46..74f80be906 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.13.0-SNAPSHOT -ballerinaLangVersion=2201.10.0 +ballerinaLangVersion=2201.11.0-20241008-112400-81975006 ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final @@ -41,6 +41,7 @@ stdlibMimeVersion=2.10.0 stdlibCacheVersion=3.8.0 stdlibAuthVersion=2.12.0 +stdlibDataJsonDataVersion = 0.3.0-20241028-143400-903c253 stdlibJwtVersion=2.13.0 stdlibOAuth2Version=2.12.0 diff --git a/native/build.gradle b/native/build.gradle index 64d9a6aea1..12c9ecf48f 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -42,7 +42,7 @@ dependencies { implementation group: 'io.ballerina.stdlib', name: 'io-native', version: "${stdlibIoVersion}" implementation group: 'io.ballerina.stdlib', name: 'mime-native', version: "${stdlibMimeVersion}" implementation group: 'io.ballerina.stdlib', name: 'constraint-native', version: "${stdlibConstraintVersion}" - + implementation group: 'io.ballerina.lib', name: 'data.jsondata-native', version: "${stdlibDataJsonDataVersion}" implementation group: 'org.slf4j', name: 'slf4j-jdk14', version: "${slf4jVersion}" implementation group: 'org.apache.commons', name: 'commons-lang3', version: "${commonsLang3Version}" implementation group: 'com.google.code.gson', name: 'gson', version: "${gsonVersion}" diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java index 457377226c..598c0b19de 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java @@ -18,14 +18,21 @@ package io.ballerina.stdlib.http.api.service.signature.converter; +import io.ballerina.lib.data.jsondata.json.Native; +import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.Type; -import io.ballerina.runtime.api.utils.ValueUtils; import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BRefValue; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.stdlib.http.api.BallerinaConnectorException; import io.ballerina.stdlib.mime.util.EntityBodyHandler; +import java.util.HashMap; +import java.util.Map; + /** * The converter binds the JSON payload to a record. * @@ -60,7 +67,14 @@ private static Object getRecordEntity(BObject entity, Type entityBodyType) { */ private static Object getRecord(Type entityBodyType, Object bJson) { try { - return ValueUtils.convert(bJson, entityBodyType); + Map valueMap = new HashMap<>(); + Boolean bool = Boolean.FALSE; + valueMap.put("enableConstraintValidation", bool); + BMap mapValue = ValueCreator.createRecordValue( + io.ballerina.lib.data.ModuleUtils.getModule(), + "Options", valueMap); + BTypedesc typedescValue = ValueCreator.createTypedescValue(entityBodyType); + return Native.parseAsType(bJson, mapValue, typedescValue); } catch (NullPointerException ex) { throw new BallerinaConnectorException("cannot convert payload to record type: " + entityBodyType.getName()); diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 775a814f2d..774d28ee45 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -41,6 +41,7 @@ requires io.netty.handler; requires commons.pool; requires io.netty.handler.proxy; + requires io.ballerina.lib.data; exports io.ballerina.stdlib.http.api; exports io.ballerina.stdlib.http.transport.contract.websocket; exports io.ballerina.stdlib.http.transport.contract;