Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce status code response binding in the client side #1915

Merged
merged 36 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f1a79b3
Update the minor version
TharmiganK Mar 24, 2024
7eda0cd
Add initial support for status code response binding
TharmiganK Mar 24, 2024
25512b3
Refactor the logic for getting anydata types
TharmiganK Mar 24, 2024
5c2ef00
[Automated] Update the native jar versions
TharmiganK Mar 27, 2024
2fa4541
[Automated] Update the native jar versions
TharmiganK Mar 27, 2024
fa3a79a
Merge remote-tracking branch 'origin/master' into status-code-respons…
TharmiganK Mar 28, 2024
1eadf7b
[Automated] Update the native jar versions
TharmiganK Mar 28, 2024
929b1ed
[Automated] Update the native jar versions
TharmiganK Mar 28, 2024
52326d3
Fix code quality issues
TharmiganK Apr 3, 2024
e580a01
Merge branch 'master' into status-code-response-binding
TharmiganK Apr 3, 2024
222cb20
Update resiliency clients
TharmiganK Apr 3, 2024
2742415
[Automated] Update the native jar versions
TharmiganK Apr 3, 2024
e1454b7
[Automated] Update the native jar versions
TharmiganK Apr 3, 2024
cb1bb75
Remove the unused method
TharmiganK Apr 3, 2024
9a9856e
Fix issues in the extern response processor
TharmiganK Apr 4, 2024
01bf7f3
Add basic test case matching all the status code responses
TharmiganK Apr 4, 2024
bdd8d74
Change the error type for success response with unsupported target type
TharmiganK Apr 4, 2024
93a148f
Add status code response binding general tests
TharmiganK Apr 4, 2024
763020d
Fix test failure
TharmiganK Apr 4, 2024
fb08044
Merge branch 'master' into status-code-response-binding
TharmiganK Apr 4, 2024
5306c96
Add remote function tests
TharmiganK Apr 4, 2024
bce164f
Add suggestions from code review
TharmiganK Apr 4, 2024
e0686d1
Refactor Optional usage with streams
ayeshLK Apr 4, 2024
ae3056a
Merge pull request #1926 from ayeshLK/http-pr-imp
ayeshLK Apr 4, 2024
da225d5
Introduce proper errors for status code binding
TharmiganK Apr 4, 2024
3fa55c0
Add tests for payload and header binding with status code response
TharmiganK Apr 4, 2024
0e3deaf
Add tests for media type binding and constraint validation
TharmiganK Apr 4, 2024
4777b66
Update lang version
TharmiganK Apr 4, 2024
ce5b91c
[Automated] Update the native jar versions
TharmiganK Apr 4, 2024
56182b6
[Automated] Update the native jar versions
TharmiganK Apr 4, 2024
3a228e8
Remove unnecessary check for nilable header
TharmiganK Apr 4, 2024
1991d4f
Add license header
TharmiganK Apr 4, 2024
30f1bdc
Update the change log
TharmiganK Apr 4, 2024
bec14b5
Merge branch 'master' into status-code-response-binding
TharmiganK Apr 5, 2024
ba35986
Add java doc comment
TharmiganK Apr 5, 2024
4e49fc3
Merge branch 'master' into status-code-response-binding
TharmiganK Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ballerina-tests/http-advanced-tests/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[package]
org = "ballerina"
name = "http_advanced_tests"
version = "2.10.13"
version = "2.11.0"

[[dependency]]
org = "ballerina"
name = "http_test_common"
repository = "local"
version = "2.10.13"
version = "2.11.0"

[platform.java17]
graalvmCompatible = true

[[platform.java17.dependency]]
scope = "testOnly"
path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar"
path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar"
10 changes: 5 additions & 5 deletions ballerina-tests/http-advanced-tests/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "constraint"
version = "1.4.0"
version = "1.5.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
Expand All @@ -44,7 +44,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "crypto"
version = "2.5.0"
version = "2.6.2"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
Expand Down Expand Up @@ -72,7 +72,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http"
version = "2.10.13"
version = "2.11.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "auth"},
Expand Down Expand Up @@ -105,7 +105,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http_advanced_tests"
version = "2.10.13"
version = "2.11.0"
dependencies = [
{org = "ballerina", name = "crypto"},
{org = "ballerina", name = "file"},
Expand All @@ -125,7 +125,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http_test_common"
version = "2.10.13"
version = "2.11.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "lang.string"},
Expand Down
6 changes: 3 additions & 3 deletions ballerina-tests/http-client-tests/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
[package]
org = "ballerina"
name = "http_client_tests"
version = "2.10.13"
version = "2.11.0"

[[dependency]]
org = "ballerina"
name = "http_test_common"
repository = "local"
version = "2.10.13"
version = "2.11.0"

[platform.java17]
graalvmCompatible = true

[[platform.java17.dependency]]
scope = "testOnly"
path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar"
path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar"
10 changes: 5 additions & 5 deletions ballerina-tests/http-client-tests/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "constraint"
version = "1.4.0"
version = "1.5.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
Expand All @@ -47,7 +47,7 @@ modules = [
[[package]]
org = "ballerina"
name = "crypto"
version = "2.5.0"
version = "2.6.2"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
Expand All @@ -69,7 +69,7 @@ dependencies = [
[[package]]
org = "ballerina"
name = "http"
version = "2.10.13"
version = "2.11.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "auth"},
Expand Down Expand Up @@ -102,7 +102,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http_client_tests"
version = "2.10.13"
version = "2.11.0"
dependencies = [
{org = "ballerina", name = "constraint"},
{org = "ballerina", name = "http"},
Expand All @@ -121,7 +121,7 @@ modules = [
[[package]]
org = "ballerina"
name = "http_test_common"
version = "2.10.13"
version = "2.11.0"
scope = "testOnly"
dependencies = [
{org = "ballerina", name = "lang.string"},
Expand Down
267 changes: 267 additions & 0 deletions ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org).
//
// 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/http;
import ballerina/test;

type Album record {|
readonly string id;
string name;
string artist;
string genre;
|};

table<Album> key(id) albums = table [
{id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"},
{id: "2", name: "Back in Black", artist: "AC/DC", genre: "Hard Rock"},
{id: "3", name: "The Wall", artist: "Pink Floyd", genre: "Progressive Rock"}
];

type ErrorMessage record {|
string albumId;
string message;
|};

type Headers record {|
string user\-id;
int req\-id;
|};

type AlbumNotFound record {|
*http:NotFound;
ErrorMessage body;
Headers headers;
|};

type AlbumFound record {|
*http:Ok;
Album body;
Headers headers;
|};

service /api on new http:Listener(statusCodeBindingPort2) {

resource function get albums/[string id]() returns AlbumFound|AlbumNotFound {
if albums.hasKey(id) {
return {
body: albums.get(id),
headers: {user\-id: "user-1", req\-id: 1}
};
}
return {
body: {albumId: id, message: "Album not found"},
headers: {user\-id: "user-1", req\-id: 1}
};
}
}

final http:Client albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`);

public function main() returns error? {
Album _ = check albumClient->/albums/'1;

AlbumFound _ = check albumClient->/albums/'1;

Album|AlbumNotFound _ = check albumClient->/albums/'1;

AlbumFound|AlbumNotFound _ = check albumClient->/albums/'1;

Album|http:Response _ = check albumClient->/albums/'1;
}

@test:Config {}
function testGetSuccessStatusCodeResponse() returns error? {
Album album = check albumClient->/albums/'1;
Album expectedAlbum = albums.get("1");
test:assertEquals(album, expectedAlbum, "Invalid album returned");

AlbumFound albumFound = check albumClient->/albums/'1;
test:assertEquals(albumFound.body, expectedAlbum, "Invalid album returned");
test:assertEquals(albumFound.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(albumFound.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(albumFound.mediaType, "application/json", "Invalid media type");

http:Response res = check albumClient->/albums/'1;
test:assertEquals(res.statusCode, 200, "Invalid status code");
json payload = check res.getJsonPayload();
album = check payload.fromJsonWithType();
test:assertEquals(album, expectedAlbum, "Invalid album returned");

Album|AlbumFound res1 = check albumClient->/albums/'1;
if res1 is AlbumFound {
test:assertEquals(res1.body, expectedAlbum, "Invalid album returned");
test:assertEquals(res1.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res1.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res1.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

AlbumFound|Album res2 = check albumClient->/albums/'1;
if res2 is AlbumFound {
test:assertEquals(res2.body, expectedAlbum, "Invalid album returned");
test:assertEquals(res2.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res2.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res2.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

Album|AlbumNotFound res3 = check albumClient->/albums/'1;
if res3 is Album {
test:assertEquals(res3, expectedAlbum, "Invalid album returned");
} else {
test:assertFail("Invalid response type");
}

AlbumFound|AlbumNotFound res4 = check albumClient->/albums/'1;
if res4 is AlbumFound {
test:assertEquals(res4.body, expectedAlbum, "Invalid album returned");
test:assertEquals(res4.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res4.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res4.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

Album|AlbumFound|AlbumNotFound res5 = check albumClient->/albums/'1;
if res5 is AlbumFound {
test:assertEquals(res5.body, expectedAlbum, "Invalid album returned");
test:assertEquals(res5.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res5.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res5.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

Album|AlbumNotFound|http:Response res6 = check albumClient->/albums/'1;
if res6 is Album {
test:assertEquals(res6, expectedAlbum, "Invalid album returned");
} else {
test:assertFail("Invalid response type");
}

AlbumNotFound|http:Response res7 = check albumClient->/albums/'1;
if res7 is http:Response {
test:assertEquals(res.statusCode, 200, "Invalid status code");
payload = check res.getJsonPayload();
album = check payload.fromJsonWithType();
test:assertEquals(album, expectedAlbum, "Invalid album returned");
} else {
test:assertFail("Invalid response type");
}

AlbumNotFound|error res8 = albumClient->/albums/'1;
if res8 is error {
test:assertTrue(res8 is http:PayloadBindingError);
test:assertEquals(res8.message(), "incompatible http_client_tests:AlbumNotFound found for response with 200",
"Invalid error message");
error? cause = res8.cause();
if cause is error {
test:assertEquals(cause.message(), "no 'anydata' type found in the target type", "Invalid cause error message");
}
} else {
test:assertFail("Invalid response type");
}
}

@test:Config {}
function testGetFailureStatusCodeResponse() returns error? {
AlbumNotFound albumNotFound = check albumClient->/albums/'4;
ErrorMessage expectedErrorMessage = {albumId: "4", message: "Album not found"};
test:assertEquals(albumNotFound.body, expectedErrorMessage, "Invalid error message");
test:assertEquals(albumNotFound.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(albumNotFound.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(albumNotFound.mediaType, "application/json", "Invalid media type");

http:Response res = check albumClient->/albums/'4;
test:assertEquals(res.statusCode, 404, "Invalid status code");
json payload = check res.getJsonPayload();
ErrorMessage errorMessage = check payload.fromJsonWithType();
test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message");

Album|AlbumNotFound res1 = check albumClient->/albums/'4;
if res1 is AlbumNotFound {
test:assertEquals(res1.body, expectedErrorMessage, "Invalid error message");
test:assertEquals(res1.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res1.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res1.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

AlbumNotFound|http:Response res2 = check albumClient->/albums/'4;
if res2 is AlbumNotFound {
test:assertEquals(res2.body, expectedErrorMessage, "Invalid error message");
test:assertEquals(res2.headers.user\-id, "user-1", "Invalid user-id header");
test:assertEquals(res2.headers.req\-id, 1, "Invalid req-id header");
test:assertEquals(res2.mediaType, "application/json", "Invalid media type");
} else {
test:assertFail("Invalid response type");
}

Album|http:Response res3 = check albumClient->/albums/'4;
if res3 is http:Response {
test:assertEquals(res3.statusCode, 404, "Invalid status code");
payload = check res3.getJsonPayload();
errorMessage = check payload.fromJsonWithType();
test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message");
} else {
test:assertFail("Invalid response type");
}

http:Response|AlbumFound res4 = check albumClient->/albums/'4;
if res4 is http:Response {
test:assertEquals(res4.statusCode, 404, "Invalid status code");
payload = check res4.getJsonPayload();
errorMessage = check payload.fromJsonWithType();
test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message");
} else {
test:assertFail("Invalid response type");
}

Album|error res5 = albumClient->/albums/'4;
if res5 is error {
test:assertTrue(res5 is http:ClientRequestError);
test:assertEquals(res5.message(), "Not Found", "Invalid error message");
test:assertEquals(res5.detail()["statusCode"], 404, "Invalid status code");
test:assertEquals(res5.detail()["body"], expectedErrorMessage, "Invalid error message");
if res5.detail()["headers"] is map<string[]> {
map<string[]> headers = check res5.detail()["headers"].ensureType();
test:assertEquals(headers.get("user-id")[0], "user-1", "Invalid user-id header");
test:assertEquals(headers.get("req-id")[0], "1", "Invalid req-id header");
}

} else {
test:assertFail("Invalid response type");
}

AlbumFound|error res6 = albumClient->/albums/'4;
if res6 is error {
test:assertTrue(res6 is http:ClientRequestError);
test:assertEquals(res6.message(), "Not Found", "Invalid error message");
test:assertEquals(res6.detail()["statusCode"], 404, "Invalid status code");
test:assertEquals(res6.detail()["body"], expectedErrorMessage, "Invalid error message");
if res6.detail()["headers"] is map<string[]> {
map<string[]> headers = check res6.detail()["headers"].ensureType();
test:assertEquals(headers.get("user-id")[0], "user-1", "Invalid user-id header");
test:assertEquals(headers.get("req-id")[0], "1", "Invalid req-id header");
}

} else {
test:assertFail("Invalid response type");
}
}
Loading
Loading